使用 TypeScript 和 Jest 支持:AWS SAM 教程

已发表: 2022-03-11

作为构建无服务器应用程序的强大工具,AWS 无服务器应用程序模型 (SAM) 经常与 JavaScript 搭配使用:大中型公司中有 62% 的开发人员选择 JavaScript 作为其无服务器代码。 然而,TypeScript 的受欢迎程度飙升,远远超过 JavaScript,成为开发人员第三大最受欢迎的语言。

虽然 JavaScript 样板文件不难找到,但使用 TypeScript 启动 AWS SAM 项目更为复杂。 以下教程展示了如何从头开始创建 AWS SAM TypeScript 项目,以及不同部分如何协同工作。 读者只需对 AWS Lambda 函数稍有熟悉即可了解。

启动我们的 AWS SAM TypeScript 项目

我们的无服务器应用程序的基础包括各种组件。 我们将首先配置 AWS 环境、我们的 npm 包和 Webpack 功能——然后我们可以创建、调用和测试我们的 Lambda 函数以查看我们的应用程序的运行情况。

准备环境

要设置 AWS 环境,我们需要安装以下内容:

  1. AWS CLI
  2. AWS SAM CLI
  3. Node.js 和 npm

请注意,本教程需要在上面的第 2 步中安装 Docker 以在本地测试我们的应用程序。

初始化一个空项目

让我们创建项目目录aws-sam-typescript-boilerplate和一个src子文件夹来保存代码。 从项目目录中,我们将设置一个新的 npm 包:

 npm init -y # -y option skips over project questionnaire

该命令将在我们的项目中创建一个package.json文件。

添加 Webpack 配置

Webpack 是一个模块打包器,主要用于 JavaScript 应用程序。 由于 TypeScript 编译为纯 JavaScript,Webpack 将有效地为 Web 浏览器准备我们的代码。 我们将安装两个库和一个自定义加载器:

  • webpack:核心库
  • webpack-cli:Webpack 的命令行实用程序
  • ts-loader:用于 Webpack 的 TypeScript 加载器
npm i --save-dev webpack webpack-cli ts-loader

AWS SAM CLI 构建命令sam build会减慢开发过程,因为它会尝试为每个函数运行npm install ,从而导致重复。 我们将使用 aws-sam-webpack-plugin 库中的替代构建命令来加速我们的环境。

 npm i --save-dev aws-sam-webpack-plugin

默认情况下,Webpack 不提供配置文件。 让我们在根文件夹中创建一个名为webpack.config.js的自定义配置文件:

 /* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path'); const AwsSamPlugin = require('aws-sam-webpack-plugin'); const awsSamPlugin = new AwsSamPlugin(); module.exports = { entry: () => awsSamPlugin.entry(), output: { filename: (chunkData) => awsSamPlugin.filename(chunkData), libraryTarget: 'commonjs2', path: path.resolve('.') }, devtool: 'source-map', resolve: { extensions: ['.ts', '.js'] }, target: 'node', mode: process.env.NODE_ENV || 'development', module: { rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }] }, plugins: [awsSamPlugin] };

现在让我们检查各个部分:

  • entry :这会从AWS::Serverless::Function资源加载 entry 对象(Webpack 开始构建包的地方)。
  • output :这指向构建输出的目的地(在本例中为.aws-sam/build )。 这里我们还将目标库指定为commonjs2 ,它将入口点的返回值分配给module.exports 。 此入口点是 Node.js 环境的默认入口点。
  • devtool :这会在我们的构建输出目标中创建一个源映射app.js.map 。 它将我们的原始代码映射到在 Web 浏览器中运行的代码,如果我们将环境变量NODE_OPTIONS设置为 Lambda 的--enable-source-maps ,它将有助于调试。
  • resolve :这告诉 Webpack 在 JavaScript 文件之前处理 TypeScript 文件。
  • target :这告诉 Webpack 将 Node.js 作为我们的环境。 这意味着 Webpack 在编译时将使用 Node.js 的require函数来加载块。
  • module :这会将 TypeScript 加载器应用于所有满足test条件的文件。 换句话说,它确保所有具有.ts.tsx扩展名的文件都将由加载程序处理。
  • plugins :这有助于 Webpack 识别和使用我们的aws-sam-webpack-plugin

在第一行,我们为这个文件禁用了一个特定的 ESLint 规则。 我们稍后将配置的标准 ESLint 规则不鼓励使用require语句。 我们更喜欢在 Webpack 中使用require import ,所以我们会例外。

设置 TypeScript 支持

添加 TypeScript 支持将通过以下方式改善开发人员体验:

  • 防止有关缺少类型声明的警告消息。
  • 提供类型验证。
  • 在 IDE 中提供自动完成功能。

首先,我们将在本地为我们的项目安装 TypeScript(如果您已全局安装了 TypeScript,请跳过此步骤):

 npm i --save-dev typescript

我们将包括我们正在使用的库的类型:

 npm i --save-dev @types/node @types/webpack @types/aws-lambda

现在,我们将在项目根目录中创建 TypeScript 配置文件tsconfig.json

 { "compilerOptions": { "target": "ES2015", "module": "commonjs", "sourceMap": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, }, "include": ["src/**/*.ts", "src/**/*.js"], "exclude": ["node_modules"] }

这里我们遵循 TypeScript 社区推荐的默认配置。 我们添加了include以将src文件夹下的文件附加到程序中,并添加exclude以避免对node_modules文件夹进行 TypeScript 编译——我们不会直接接触此代码。

创建 Lambda 函数

到目前为止,我们还没有为我们的无服务器应用程序编写任何 Lambda 代码,所以让我们开始吧。在我们之前创建的src文件夹中,我们将创建一个test-lambda子文件夹,其中包含一个带有此 Lambda 函数的app.ts文件:

 import { APIGatewayEvent } from 'aws-lambda'; export const handler = async (event: APIGatewayEvent) => { console.log('incoming event is', JSON.stringify(event)); const response = { statusCode: 200, body: JSON.stringify({ message: 'Request was successful.' }) }; return response; };

这个简单的占位符函数返回一个带有正文的 200 响应。 再经过一步,我们将能够运行代码。

包括 AWS 模板文件

AWS SAM 需要一个模板文件来转译我们的代码并将其部署到云中。 在根文件夹中创建文件template.yaml

 AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: AWS SAM Boilerplate Using TypeScript Globals: Function: Runtime: nodejs14.x # modify the version according to your need Timeout: 30 Resources: TestLambda: Type: AWS::Serverless::Function Properties: Handler: app.handler FunctionName: "Test-Lambda" CodeUri: src/test-lambda/ Events: ApiEvent: Type: Api Properties: Path: /test Method: get

此模板文件生成可从 HTTP GET API 访问的 Lambda 函数。 请注意,在Runtime:行中引用的版本可能需要自定义。

运行应用程序

要运行应用程序,我们必须在package.json文件中添加一个新脚本,以便使用 Webpack 构建项目。 该文件可能有现有的脚本,例如一个空的测试脚本。 我们可以像这样添加构建脚本:

 "scripts": { "build": "webpack-cli" }

如果您从项目的根目录运行npm run build ,您应该会看到构建文件夹.aws-sam创建。 我们这些在 Mac 环境中的人可能需要通过按Command + Shift + 使隐藏文件可见。 查看文件夹。

我们现在将启动一个本地 HTTP 服务器来测试我们的功能:

 sam local start-api

当我们在 Web 浏览器中访问测试端点时,我们应该会看到一条成功消息。

Web 浏览器在地址栏中显示链接“127.0.0.1:3000/test”。地址栏下方的网页是空白的,除了一条消息“{“message”:“请求成功。”}。

控制台应该显示该函数在运行之前已安装在 Docker 容器中,这就是我们之前安装 Docker 的原因:

 Invoking app.handler (nodejs14.x) Skip pulling image and use local one: public.ecr.aws/sam/emulation-nodejs14.x:rapid-1.37.0-x86_64. Mounting /Users/mohammadfaisal/Documents/learning/aws-sam-typescript-boilerplate/.aws-sam/build/TestLambda as /var/task:ro, delegated inside runtime container

为专业环境增强我们的开发工作流程

我们的项目已启动并正在运行,添加一些收尾工作将确保卓越的开发人员体验,从而提高生产力和协作。

使用热重载优化构建

每次代码更改后运行构建命令很乏味。 热重载将解决此问题。 我们可以在package.json中添加另一个脚本来监视文件更改:

 "watch": "webpack-cli -w"

打开一个单独的终端并运行npm run watch 。 现在,当您更改任何代码时,您的项目将自动编译。 修改代码的消息,刷新你的网页,看看更新的结果。

使用 ESLint 和 Prettier 提高代码质量

没有 ESLint 和 Prettier,任何 TypeScript 或 JavaScript 项目都是不完整的。 这些工具将保持您项目的代码质量和一致性。

让我们先安装核心依赖项:

 npm i --save-dev eslint prettier

我们将添加一些辅助依赖项,以便 ESLint 和 Prettier 可以在我们的 TypeScript 项目中协同工作:

 npm i --save-dev \ eslint-config-prettier \ eslint-plugin-prettier \ @typescript-eslint/parser \ @typescript-eslint/eslint-plugin

接下来,我们将通过在项目根目录中创建一个 ESLint 配置文件.eslintrc来添加我们的 linter:

 { "root": true, "env": { "es2020": true, "node": true, "jest": true }, "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ], "ignorePatterns": ["src/**/*.test.ts", "dist/", "coverage/", "test/"], "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "impliedStrict": true } }, "rules": { "quotes": ["error", "single", { "allowTemplateLiterals": true }], "default-case": "warn", "no-param-reassign": "warn", "no-await-in-loop": "warn", "@typescript-eslint/no-unused-vars": [ "error", { "vars": "all", "args": "none" } ] }, "settings": { "import/resolver": { "node": { "extensions": [".js", ".jsx", ".ts", ".tsx"] } } } }

请注意,我们文件的extends部分必须将 Prettier 插件配置保留在最后一行,以便将 Prettier 错误显示为我们编辑器中可见的 ESLint 错误。 我们遵循 ESLint 为 TypeScript 推荐的设置,并在rules部分添加了一些自定义首选项。 随意浏览可用规则并进一步自定义您的设置。 我们选择包括:

  • 如果我们不使用单引号字符串,则会出错。
  • 当我们在switch语句中不提供default情况时发出警告。
  • 如果我们重新分配函数的任何参数,则会发出警告。
  • 如果我们在循环中调用await语句,则会发出警告。
  • 未使用变量的错误,这会使代码随着时间的推移变得不可读且容易出错。

我们已经设置了 ESLint 配置以使用 Prettier 格式。 (更多信息可以在eslint-config-prettier GitHub 项目中找到。)现在,我们可以创建 Prettier 配置文件.prettierrc

 { "trailingComma": "none", "tabWidth": 4, "semi": true, "singleQuote": true }

这些设置来自 Prettier 的官方文档; 您可以根据需要修改它们。 我们更新了以下属性:

  • trailingComma :我们将其从es5更改为none以避免尾随逗号。
  • semi :我们将其从false更改为true ,因为我们更喜欢在每行的末尾有一个分号。

最后,是时候看看 ESLint 和 Prettier 的实际应用了。 在我们的app.ts文件中,我们将response变量类型从const更改为let 。 在这种情况下使用let不是一个好习惯,因为我们不修改response的值。 编辑器应显示错误、损坏的规则和修复代码的建议。 如果尚未设置,请不要忘记在您的编辑器上启用 ESLint 和 Prettier。

编辑器显示一行代码,为变量“let response”赋值。该行旁边显示一个黄色灯泡,“响应”一词有一个红色下划线和一个错误弹出窗口。错误弹窗首先定义了变量“response”,内容为:“let response: { statusCode: number; body: string; }。”在定义下方,错误消息显示:“'response' 永远不会重新分配。请改用 'const'。eslint(prefer-const)。”在错误消息下方,有两个选项显示:“查看问题”或“快速修复”。

使用 Jest 测试维护代码

许多库可用于测试,例如 Jest、Mocha 和 Storybook。 我们将在我们的项目中使用 Jest 有几个原因:

  • 学起来很快。
  • 它需要最少的设置。
  • 它提供易于使用的快照测试。

让我们安装所需的依赖项:

 npm i --save-dev jest ts-jest @types/jest

接下来,我们将在项目根目录中创建一个 Jest 配置文件jest.config.js

 module.exports = { roots: ['src'], testMatch: ['**/__tests__/**/*.+(ts|tsx|js)'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest' } };

我们在我们的文件中自定义了三个选项:

  • roots :这个数组包含将要搜索测试文件的文件夹——它只检查我们的src子文件夹下。
  • testMatch :这个 glob 模式数组包括将被视为 Jest 文件的文件扩展名。
  • transform :这个选项让我们可以使用ts-jest包在 TypeScript 中编写测试。

让我们在src/test-lambda中创建一个新的__tests__文件夹。 在其中,我们将添加文件handler.test.ts ,我们将在其中创建我们的第一个测试:

 import { handler } from '../app'; const event: any = { body: JSON.stringify({}), headers: {} }; describe('Demo test', () => { test('This is the proof of concept that the test works.', async () => { const res = await handler(event); expect(res.statusCode).toBe(200); }); });

我们将返回到我们的package.json文件并使用测试脚本对其进行更新:

 "test": "jest"

当我们去终端并运行npm run test时,我们应该会看到一个通过测试:

控制台顶部显示一个绿色的“通过”指示器和测试文件名“src/test-lambda/__tests__/handler.test.ts”。下一行是“演示测试”。下一行显示一个绿色复选标记,后跟“这是测试有效的概念证明。(1 毫秒)”。在空行之后,第一行显示:“测试套件:1 个通过,总共 1 个。”第二个内容是:“测试:1 次通过,总共 1 次。”第三个内容是:“快照:总共 0 个。”第四个写着:“时间:0.959 秒。”最后一行显示:“运行所有测试套件。”

使用.gitignore处理源代码管理

我们应该配置 Git 从源代码控制中排除某些文件。 我们可以使用 gitignore.io 创建一个.gitignore文件来跳过不需要的文件:

 # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Runtime data pids *.pid *.seed *.pid.lock npm-debug.log package.lock.json /node_modules .aws-sam .vscode # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional ESLint cache .eslintcache

准备、设置、构建:我们的成功蓝图

我们现在有了一个完整的带有 TypeScript 的 AWS SAM 样板项目。 我们专注于通过 ESLint、Prettier 和 Jest 支持获得正确的基础知识并保持高代码质量。 此 AWS SAM 教程中的示例可以作为蓝图,让您的下一个大项目从一开始就走上正轨。

Toptal 工程博客对 Christian Loef 对本文中提供的代码示例的审阅表示感谢。

带有“PARTNER”字样的 AWS 徽标和下方的“Advanced Tier Services”文本。
作为亚马逊合作伙伴网络 (APN) 中的高级咨询合作伙伴,Toptal 为公司提供在世界任何地方按需访问 AWS 认证专家的机会。