添加依赖后AWS SAM TypeScript应用EsBundleStep失败的解决方法
AWS SAM + TypeScript 引入 hanzi-tools 构建失败问题解决
问题现象
使用TypeScript开发AWS SAM应用,引入hanzi-tools库后,执行sam build时出现依赖解析错误;将报错依赖标记为外部后构建成功,但运行Lambda时失败。
相关代码与配置
Lambda 代码
import fetch from 'node-fetch'; import { Headers } from 'node-fetch'; var pinyinify = require("hanzi-tools").pinyinify; type AnkiCardField = { value: string } type AnkiCardFields = { Front: AnkiCardField; Back: AnkiCardField; Pinyin: AnkiCardField; } type AnkiCard = { cardId: number; fields: AnkiCardFields; } function getListOfCards() { console.log(process.env.ANKI_CONNECT_URL); console.log(process.env.ANKI_CONNECT_PORT); let endpoint = 'http://' + process.env.ANKI_CONNECT_URL + ':' + process.env.ANKI_CONNECT_PORT; var requestHeaders = new Headers(); requestHeaders.append("Content-Type", "application/json"); var requestBody = JSON.stringify({ "action": "findCards", "version": 6, "params": { "query": "deck:TestDeck" } }); var requestOptions = { method: 'POST', headers: requestHeaders, body: requestBody }; fetch(endpoint, requestOptions) .then(response => response.json()) .then(ankiFindCardsResponse => { var cardIdArray = ankiFindCardsResponse.result; var cardInfoRequestBody = JSON.stringify({ "action": "cardsInfo", "version": 6, "params": { "cards": cardIdArray } }); var cardInfoRequestOptions = { method: 'POST', headers: requestHeaders, body: cardInfoRequestBody }; return fetch(endpoint, cardInfoRequestOptions); }) .then(cardInfoResponse => cardInfoResponse.json()) .then(cardInfoResponseJson => { var ankiCardArray = cardInfoResponseJson.result; let singleCharacterMap = new Map<string, AnkiCard>(); let multipleChararacterCards: AnkiCard[] = []; let singleCardCount = 0; let doubleCardCount = 0; console.log(`总共有 ${ankiCardArray.length} 张卡片`); ankiCardArray.forEach((ankiCard: AnkiCard) => { let chinese: string = ankiCard.fields.Front.value; if (chinese.length == 1) { singleCharacterMap.set(chinese, ankiCard); singleCardCount++; } else { multipleChararacterCards.push(ankiCard); doubleCardCount++; } }); console.log(`总共有 ${singleCardCount} 张单字卡片`); console.log(`总共有 ${doubleCardCount} 张多字卡片`); multipleChararacterCards.forEach((multiCharacterCard: AnkiCard) => { let chinese: string = multiCharacterCard.fields.Front.value; chinese.split('').forEach(chineseCharacter => { if (!singleCharacterMap.has(chineseCharacter)) { //pinyinify(chineseCharacter); console.log(`${chineseCharacter} 需要添加`) } }); }); } ) .catch(error => console.log('错误', error)); } export const lambdaHandler = (event: Object): String => { getListOfCards(); return "完成"; };
构建错误信息
执行sam build时抛出以下错误:
Error: NodejsNpmEsbuildBuilder:EsbuildBundle - Esbuild Failed: ✘ [ERROR] Could not resolve "mock-aws-s3" node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:43:28: 43 │ const AWSMock = require('mock-aws-s3'); ╵ ~~~~~~~~~~~~~ You can mark the path "mock-aws-s3" as external to exclude it from the bundle, which will remove this error. You can also surround this "require" call with a try/catch block to handle this failure at run-time instead of bundle-time. ✘ [ERROR] Could not resolve "aws-sdk" node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:76:22: 76 │ const AWS = require('aws-sdk'); ╵ ~~~~~~~~~ You can mark the path "aws-sdk" as external to exclude it from the bundle, which will remove this error. You can also surround this "require" call with a try/catch block to handle this failure at run-time instead of bundle-time. ✘ [ERROR] Could not resolve "nock" node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:112:23: 112 │ const nock = require('nock'); ╵ ~~~~~~ You can mark the path "nock" as external to exclude it from the bundle, which will remove this error. You can also surround this "require" call with a try/catch block to handle this failure at run-time instead of bundle-time.
package.json 配置
{ "name": "hello_world", "version": "1.0.0", "description": "hello world sample for NodeJS", "main": "app.js", "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", "author": "SAM CLI", "license": "MIT", "scripts": { "unit": "jest", "lint": "eslint '*.ts' --quiet --fix", "compile": "tsc", "test": "npm run compile && npm run unit" }, "devDependencies": { "@types/aws-lambda": "^8.10.92", "@types/jest": "^29.2.0", "@types/node": "^18.11.4", "@types/node-fetch": "^2.6.2", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "esbuild-jest": "^0.5.0", "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^29.2.1", "prettier": "^2.5.1", "ts-node": "^10.9.1", "typescript": "^4.8.4" }, "dependencies": { "esbuild": "^0.14.14", "hanzi-tools": "^1.2.26", "node-fetch": "^2.6.1" } }
template.yaml 配置
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > anki-connect-lambda Sample SAM Template for anki-connect-lambda Globals: Function: Timeout: 60 Tracing: Active Api: TracingEnabled: true Resources: AnkiConnectLambda: Type: AWS::Serverless::Function Properties: CodeUri: lambda/ Handler: app.lambdaHandler Runtime: nodejs18.x MemorySize: 2048 Architectures: - x86_64 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get Environment: Variables: ANKI_CONNECT_URL : localhost ANKI_CONNECT_PORT: 8765 Metadata: BuildMethod: esbuild BuildProperties: Minify: true Target: es2020 Sourcemap: true EntryPoints: - app.ts ApplicationResourceGroup: Type: AWS::ResourceGroups::Group Properties: Name: Fn::Join: - '' - - ApplicationInsights-SAM- - Ref: AWS::StackName ResourceQuery: Type: CLOUDFORMATION_STACK_1_0 ApplicationInsightsMonitoring: Type: AWS::ApplicationInsights::Application Properties: ResourceGroupName: Fn::Join: - '' - - ApplicationInsights-SAM- - Ref: AWS::StackName AutoConfigurationEnabled: 'true' DependsOn: ApplicationResourceGroup Outputs: HelloWorldApi: Description: API Gateway生产环境Hello World函数端点URL Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" AnkiConnectLambda: Description: Hello World Lambda函数ARN Value: !GetAtt AnkiConnectLambda.Arn AnkiConnectLambdaIamRole: Description: Hello World函数自动创建的IAM角色 Value: !GetAtt AnkiConnectLambdaRole.Arn
问题原因
hanzi-tools依赖的包使用了@mapbox/node-pre-gyp,而@mapbox/node-pre-gyp引入了mock-aws-s3、aws-sdk、nock这些仅用于开发测试的依赖。esbuild打包时会尝试解析所有依赖,导致构建失败;标记为外部后,打包时会忽略这些依赖,但Lambda运行环境中没有这些包,触发运行时错误。
解决方案
方案1:配置esbuild排除无关依赖
修改template.yaml中的BuildProperties,添加External字段排除这些仅用于测试的依赖。因为这些依赖是@mapbox/node-pre-gyp的测试代码,正常运行hanzi-tools功能时不会被调用,所以可以安全排除:
Metadata: BuildMethod: esbuild BuildProperties: Minify: true Target: es2020 Sourcemap: true EntryPoints: - app.ts External: - mock-aws-s3 - aws-sdk - nock
方案2:替换为纯JavaScript替代库
寻找功能类似的纯JavaScript汉字处理库,避免引入依赖node-pre-gyp的包,从根源解决依赖问题。
方案3:生产环境安装时排除开发依赖
在构建部署前执行npm install --omit=dev,确保不会安装开发依赖,但需注意区分本地开发和生产构建流程,避免影响开发环境。
验证步骤
- 修改template.yaml添加External配置后,重新执行
sam build - 部署Lambda后测试功能,确认hanzi-tools的
pinyinify函数能正常调用
内容的提问来源于stack exchange,提问作者KennyBartMan
相关产品推荐
相关产品推荐

