尝试使用aws-nodejs-typescript模板
首先
在业务中,我经常使用ServerlessFramework,当使用typescript的模板时,serverless.yml变为serverless.ts,并且构建工具从webpack改为esbuild,这一点让我感到困惑。因此,我想分享一下这些变化,并总结一下。
Serverless Framework是什么?
Serverless Framework是什么?
根据https://github.com/serverless/serverless,Serverless Framework是一个什么?
无服务器框架 – 在AWS Lambda和其他新一代云服务上构建应用程序,自动扩展并且只在运行时收费。这降低了运行和操作应用程序的总成本,使您能够更多地构建和少量管理。
听说如此。因为用英语听起来很费劲,所以转成日语翻译一下(来自DeepL翻译)。
在AWS Lambda和其他下一代云服务上构建应用程序,实现自动扩展,并仅在运行时收费,这样可以降低应用程序的运行和运维成本,实现更多的构建和更少的管理。
简而言之,Serverless Framework 是一个可以利用 AWS Lambda 和 Amazon API Gateway 等服务来快速构建和部署无服务器应用的框架。
准备工作
实际运行环境如下所示。
关于serverless和awscli,我们使用brew命令进行安装。
❯ node -v
v17.3.0
❯ npm --version
8.3.0
❯ serverless --version
Framework Core: 2.71.0
Plugin: 5.5.3
SDK: 4.3.0
Components: 3.18.1
❯ aws --version
aws-cli/2.4.11 Python/3.9.9 Darwin/20.6.0 source/x86_64 prompt/off
通过执行aws configure命令,设置AWS的认证信息,按照提示进行操作即可。
$ aws configure
AWS Access Key ID [None]: your_access_key_id
AWS Secret Access Key [None]: your_secret_access_key
Default region name [None]:
Default output format [None]:
如果没有访问密钥ID和秘密访问密钥,
请在设置的基本 – AWS命令行接口的访问密钥ID和秘密访问密钥部分中,按照所述的创建步骤进行创建,并进行注册。
请参考以下链接
-
- 設定の基本 – AWS Command Line Interface
- Serverless Framework – AWS Lambda Guide – Credentials
创建项目
那么,让我们立即开始创建项目。
❯ sls create --template aws-nodejs-typescript --path nodejs-typescript-test
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/**/nodejs-typescript-test"
Serverless: Successfully generated boilerplate for template: "aws-nodejs-typescript"
以下为由模板生成的代码。
稍微引起了我的兴趣点1。
根据使用模板创建的README.md文件,目录结构似乎是这样的。
.
├── src
│ ├── functions # Lambda configuration and source code folder
│ │ ├── hello
│ │ │ ├── handler.ts # `Hello` lambda source code
│ │ │ ├── index.ts # `Hello` lambda Serverless configuration
│ │ │ ├── mock.json # `Hello` lambda input parameter, if any, for local invocation
│ │ │ └── schema.ts # `Hello` lambda input event JSON-Schema
│ │ │
│ │ └── index.ts # Import/export of all lambda configurations
│ │
│ └── libs # Lambda shared code
│ └── apiGateway.ts # API Gateway specific helpers
│ └── handlerResolver.ts # Sharable library for resolving lambda handlers
│ └── lambda.ts # Lambda middleware
│
├── package.json
├── serverless.ts # Serverless service file
├── tsconfig.json # Typescript compiler configuration
├── tsconfig.paths.json # Typescript paths
└── webpack.config.js # Webpack configuration
然而,当查看serverless.ts时,可以看到plugins中有serverless-esbuild,这意味着构建工具使用的是esbuild。因此,最下面的webpack.config.js文件不会被生成。
根据releases的记录,确认版本为v2.60.0。
切换到在 AWS 上使用 esbuild 的 aws-nodejs-typescript
因此,根据某个源头,似乎在这个版本中有一些修改,但是 README.md 文件似乎没有改变。所以,暂时不用太担心这个问题,应该没什么大问题。
执行yarn安装
以下的步骤是执行 yarn install 命令来安装库。
❯ cd nodejs-typescript-test/
❯ yarn
yarn install v1.22.17
info No lockfile found.
[1/5] ? Validating package.json...
[2/5] ? Resolving packages...
warning serverless > aws-sdk > uuid@3.3.2: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
warning serverless > @serverless/cli > @serverless/utils > uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
warning serverless > aws-sdk > querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
warning serverless > @serverless/platform-client > querystring@0.2.1: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
warning serverless > aws-sdk > url > querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
warning serverless > @serverless/components > @serverless/platform-client-china > querystring@0.2.1: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
warning serverless > json-refs > path-loader > superagent@3.8.3: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>. Thanks to @shadowgate15, @spence-s, and @niftylettuce. Superagent is sponsored by Forward Email at <https://forwardemail.net>.
warning serverless > json-refs > path-loader > superagent > formidable@1.2.6: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau
warning serverless > @serverless/components > @serverless/platform-client-china > @serverless/utils-china > kafka-node > uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
warning serverless > @serverless/components > @serverless/platform-client-china > @serverless/utils-china > @tencent-sdk/capi > request-promise-native@1.0.9: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
warning serverless > @serverless/components > @serverless/platform-client-china > @serverless/utils-china > @tencent-sdk/capi > request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
warning serverless > @serverless/components > @serverless/platform-client-china > @serverless/utils-china > @tencent-sdk/capi > request > uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
warning serverless > @serverless/components > @serverless/platform-client-china > @serverless/utils-china > @tencent-sdk/capi > request > har-validator@5.1.5: this library is no longer supported
[3/5] ? Fetching packages...
[4/5] ? Linking dependencies...
warning "serverless > @serverless/components > inquirer-autocomplete-prompt@1.4.0" has unmet peer dependency "inquirer@^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0".
warning " > serverless-esbuild@1.23.3" has unmet peer dependency "esbuild@>=0.8 <0.15".
[5/5] ? Building fresh packages...
success Saved lockfile.
✨ Done in 137.15s.
有一点我稍微担心,点②。
生成的serverless.ts如下所示。
import type { AWS } from '@serverless/typescript';
import hello from '@functions/hello';
const serverlessConfiguration: AWS = {
service: 'nodejs-typescript-test',
frameworkVersion: '2',
plugins: ['serverless-esbuild'],
provider: {
name: 'aws',
runtime: 'nodejs14.x',
apiGateway: {
minimumCompressionSize: 1024,
shouldStartNameWithService: true,
},
environment: {
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000',
},
lambdaHashingVersion: '20201221',
},
// import the function via paths
functions: { hello },
package: { individually: true },
custom: {
esbuild: {
bundle: true,
minify: false,
sourcemap: true,
exclude: ['aws-sdk'],
target: 'node14',
define: { 'require.resolve': undefined },
platform: 'node',
concurrency: 10,
},
},
};
module.exports = serverlessConfiguration;
让我们将生成的 serverless.yml 与 aws-nodejs 模板进行比较,以确认在从 yml 到 ts 的转变中有哪些变化。
# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
# docs.serverless.com
#
# Happy Coding!
service: aws-nodejs # NOTE: update this with your service name
# app and org for use with dashboard.serverless.com
#app: your-app-name
#org: your-org-name
# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs12.x
lambdaHashingVersion: 20201221
# you can overwrite defaults here
# stage: dev
# region: us-east-1
# you can add statements to the Lambda function's IAM Role here
# iam:
# role:
# statements:
# - Effect: "Allow"
# Action:
# - "s3:ListBucket"
# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] }
# - Effect: "Allow"
# Action:
# - "s3:PutObject"
# Resource:
# Fn::Join:
# - ""
# - - "arn:aws:s3:::"
# - "Ref" : "ServerlessDeploymentBucket"
# - "/*"
# you can define service wide environment variables here
# environment:
# variable1: value1
# you can add packaging information here
#package:
# patterns:
# - '!exclude-me.js'
# - '!exclude-me-dir/**'
# - include-me.js
# - include-me-dir/**
functions:
hello:
handler: handler.hello
# The following are a few example events you can configure
# NOTE: Please make sure to change your handler code to work with those events
# Check the event documentation for details
# events:
# - httpApi:
# path: /users/create
# method: get
# - websocket: $connect
# - s3: ${env:BUCKET}
# - schedule: rate(10 minutes)
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:
# event:
# source:
# - "aws.ec2"
# detail-type:
# - "EC2 Instance State-change Notification"
# detail:
# state:
# - pending
# - cloudwatchLog: '/aws/lambda/hello'
# - cognitoUserPool:
# pool: MyUserPool
# trigger: PreSignUp
# - alb:
# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/
# priority: 1
# conditions:
# host: example.com
# path: /hello
# Define function environment variables here
# environment:
# variable2: value2
# you can add CloudFormation resource templates here
#resources:
# Resources:
# NewResource:
# Type: AWS::S3::Bucket
# Properties:
# BucketName: my-new-bucket
# Outputs:
# NewOutput:
# Description: "Description for the output"
# Value: "Some output value"
我认为大致上可以掌握从yml到ts的写作方式,可以理解大致的感觉。我稍微担心的是,在yml中,
stage: ${opt:stage, 'dev'}
我在想,对于在ts中如何定义这个地方会有什么感觉。
我发现建立在 Serverless Framework `serverless.yaml` 基础上的 `serverless.ts`,通过这个人创建的 CLI,可以轻松进行转换操作。我也想试试用这个 CLI 进行转换,所以在 GitHub 上查看了一下大家是如何实现 serverless.ts 的(https://github.com/search?l=TypeScript&q=%22%24%7Bopt%3A+stage%2C+%27dev%27%7D%22&type=Code)。
stage: '${opt:stage, "dev"}'
看起来是这样实现的,嗯,我明白了。
功能
正好,我正在比较 serverless.yml 和 serverless.ts,所以让我们来看一下 functions 的配置。在 TypeScript 中,
import schema from './schema';
import { handlerPath } from '@libs/handlerResolver';
export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
http: {
method: 'post',
path: 'hello',
request: {
schemas: {
'application/json': schema
}
}
}
}
]
}
在serverless.ts中记录了在serverless.yml中记录的配置。这样,责任被分离出来。
请参考以下链接
-
- Serverless Framework Commands – AWS Lambda – Create
- Serverless Frameworkの`serverless.yaml`を`serverless.ts`に変換する | WP Kyoto
确认构成
让我们简要查看代码,以确认使用模板创建的服务如何运作。就像我们之前确认过的那样,在 serverless.ts 文件中。
import hello from '@functions/hello';
functions: { hello },
因为被定义为这样,所以我们来看一下“你好”。
└── hello
├── handler.ts # `Hello` lambda source code
├── index.ts # `Hello` lambda Serverless configuration
├── mock.json # `Hello` lambda input parameter, if any, for local invocation
└── schema.ts # `Hello` lambda input event JSON-Schema
首先让我们再次查看 index.ts。
import schema from './schema';
import { handlerPath } from '@libs/handlerResolver';
export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
http: {
method: 'post',
path: 'hello',
request: {
schemas: {
'application/json': schema
}
}
}
}
]
}
作为处理程序,函数被传递给了handler,并且事件被传递给了events,这些事件将触发函数的执行。
接下来,让我们看一下 handler.ts 文件。
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/apiGateway';
import { formatJSONResponse } from '@libs/apiGateway';
import { middyfy } from '@libs/lambda';
import schema from './schema';
const hello: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
return formatJSONResponse({
message: `Hello ${event.body.name}, welcome to the exciting Serverless world!`,
event,
});
}
export const main = middyfy(hello);
刚才我们已经注册了,当有API请求时,会返回JSON响应。
根据上述内容,发送POST请求到路径’hello’将返回一个具有message和event属性的JSON。
接下来,我们来进行本地执行并进行确认。
本地执行
根据 README.md 中的说明,让我们在本地试着运行一下。
❯ yarn sls invoke local -f hello --path src/functions/hello/mock.json
yarn run v1.22.17
$ /Users/**/nodejs-typescript-test/node_modules/.bin/sls invoke local -f hello --path src/functions/hello/mock.json
Error ---------------------------------------------------
Error: Cannot find module 'esbuild'
Require stack:
- /Users/**/nodejs-typescript-test/node_modules/serverless-esbuild/dist/index.js
- /Users/**/nodejs-typescript-test/node_modules/serverless/lib/classes/PluginManager.js
- /Users/**/nodejs-typescript-test/node_modules/serverless/lib/Serverless.js
- /Users/**/nodejs-typescript-test/node_modules/serverless/scripts/serverless.js
- /Users/**/nodejs-typescript-test/node_modules/serverless/bin/serverless.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue (/Users/**/nodejs-typescript-test/node_modules/@cspotcode/source-map-support/source-map-support.js:679:30)
at Function.Module._resolveFilename (/Users/**/nodejs-typescript-test/node_modules/tsconfig-paths/lib/register.js:75:40)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/Users/**/nodejs-typescript-test/node_modules/serverless-esbuild/dist/index.js:13:19)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at PluginManager.requireServicePlugin (/Users/**/nodejs-typescript-test/node_modules/serverless/lib/classes/PluginManager.js:164:14)
at /Users/**/nodejs-typescript-test/node_modules/serverless/lib/classes/PluginManager.js:186:27
at Array.map (<anonymous>)
at PluginManager.resolveServicePlugins (/Users/**/nodejs-typescript-test/node_modules/serverless/lib/classes/PluginManager.js:183:10)
at PluginManager.loadAllPlugins (/Users/**/nodejs-typescript-test/node_modules/serverless/lib/classes/PluginManager.js:139:10)
at Serverless.init (/Users/**/nodejs-typescript-test/node_modules/serverless/lib/Serverless.js:213:30)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async /Users/**/nodejs-typescript-test/node_modules/serverless/scripts/serverless.js:589:7
For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
Get Support --------------------------------------------
Docs: docs.serverless.com
Bugs: github.com/serverless/serverless/issues
Issues: forum.serverless.com
Your Environment Information ---------------------------
Operating System: darwin
Node Version: 17.3.1
Framework Version: 2.71.0 (local)
Plugin Version: 5.5.3
SDK Version: 4.3.0
Components Version: 3.18.1
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
有人告诉我要添加esbuild,所以我会加上去。
❯ yarn add esbuild
yarn add v1.22.17
[1/5] ? Validating package.json...
[2/5] ? Resolving packages...
[3/5] ? Fetching packages...
[4/5] ? Linking dependencies...
warning "serverless > @serverless/components > inquirer-autocomplete-prompt@1.4.0" has unmet peer dependency "inquirer@^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0".
[5/5] ? Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
└─ esbuild@0.14.11
info All dependencies
├─ esbuild-darwin-64@0.14.11
└─ esbuild@0.14.11
✨ Done in 18.79s.
我再次挑战。
❯ yarn sls invoke local -f hello --path src/functions/hello/mock.json
yarn run v1.22.17
$ /Users/**/nodejs-typescript-test/node_modules/.bin/sls invoke local -f hello --path src/functions/hello/mock.json
Serverless: Compiling to node14 bundle with esbuild...
Serverless: Compiling with concurrency: 10
Serverless: Compiling completed.
{
"statusCode": 200,
"body": "{\"message\":\"Hello Frederic, welcome to the exciting Serverless world!\",\"event\":{\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"name\":\"Frederic\"},\"rawBody\":\"{\\\"name\\\": \\\"Frederic\\\"}\"}}"
}
✨ Done in 6.22s.
看起来收到了正确的回应,所以本地执行似乎很顺利。
部署
讓我們按照 README.md 中的說明,部署到 AWS 上吧。
❯ yarn sls deploy
yarn run v1.22.17
$ /Users/**/nodejs-typescript-test/node_modules/.bin/sls deploy
Serverless: Compiling to node14 bundle with esbuild...
Serverless: Compiling with concurrency: 10
Serverless: Compiling completed.
(node:57802) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
(Use `node --trace-deprecation ...` to show where the warning was created)
Serverless: Zip function: hello - 13.66 KB [36 ms]
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Ensuring that deployment bucket exists
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service hello.zip file to S3 (13.99 kB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
....................................
Serverless: Stack update finished...
Service Information
service: nodejs-typescript-test
stage: dev
region: us-east-1
stack: nodejs-typescript-test-dev
resources: 13
api keys:
None
endpoints:
POST - https://myApiEndpoint/dev/hello
functions:
hello: nodejs-typescript-test-dev-hello
layers:
None
Monitor APIs by route with the Serverless Dashboard: run "serverless"
✨ Done in 121.06s.
似乎已完成部署。打开AWS控制台来确认一下吧。
我确认Lambda已安全部署,并可通过控制台检查编译和压缩的代码。
我们已经部署完成了,现在让我们按照README.md中所描述的方式来确认它的运行行为。
❯ curl --location --request POST 'https://myApiEndpoint/dev/hello' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Frederic"
}'
{"message":"Hello Frederic, welcome to the exciting Serverless world!","event":{"resource":"/hello","path":"/hello","httpMethod":"POST","headers":{"Accept":"*/*","CloudFront-Forwarded-Proto":"https",...
部署完成后,您可以在本地目录中找到一个名为“.serverless”的文件夹。其中包含了CloudFormation的json文件和Lambda函数的zip压缩包。
另外,在部署后,您可以检查S3,确认已经创建了类似于nodejs-typescript-test-d-serverlessdeploymentbuck-**的存储桶。
当检查内容时,发现有两个文件:compiled-cloudformation-template.json和hello.zip被存储其中。hello.zip文件中是经过编译和压缩的代码。
删除项目
删除已部署的服务。
❯ yarn sls remove
yarn run v1.22.17
$ /Users/**/nodejs-typescript-test/node_modules/.bin/sls remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack delete progress...
........................
Serverless: Stack delete finished...
Serverless: Stack delete finished...
✨ Done in 50.42s.
您可以通过这一操作确认 AWS 上的资源已被删除。
总结
我已经使用ServerlessFramework和aws-nodejs-typescript模板检查了一下基本运行情况。虽然说是模板,但已经实现了一些功能,因此需要逐一理解和解读。这与常规模板不同,可能会让人感到困惑,但如果能对此有所帮助就是我的荣幸。
所有的参考链接
Serverless FrameworkをTypeScriptで設定する【serverless.ts】 – Selfs Ryo Com
こちらのブログをとても参考にさせていただきました?ありがとうございます!
Serverless FrameworkでTypeScriptのサーバーレスAPIをAWSにデプロイしてみた – Qiita
Serverless Frameworkの使い方まとめ | Serverless Operations
初めてのサーバーレスアプリケーション開発 ~Serverless Framework を使ってAWSリソースをデプロイする~ | DevelopersIO