如何获得相对 package.json 依赖项以在 Windows 上使用 AWS sam build 命令?
How do I get relative package.json dependencies to work with AWS's sam build command on Windows?
我的目标是使用层 和 在我们的几个 lambda 函数之间共享库代码能够在本地调试和 运行 测试。
npm 能够从本地文件系统安装依赖项。当我们更改我们的库代码时,我们希望该库的所有用户都能获得更新后的代码,而无需设置专用的 npm 服务器。
我可以使用相对路径在本地进行调试,但那是在我涉及 sam build
之前。
sam build
在存储库的根目录创建一个隐藏文件夹并构建文件夹,最终 运行s npm install
,但是这次文件夹结构不同。 package.json
文件中使用的相对路径现已损坏。我们不能使用显式路径,因为我们的存储库位于我们的用户文件夹下,这当然因开发人员而异。
这是我所做的:
我使用 sam init
创建了一个项目,并为 nodejs 12.x
项目采用了默认值(sam-app-2
的名称除外)(选项 1
和 1
).
该命令创建了一个名为 sam-app-2
的文件夹,它是以下所有文件名的参考。
我创建了一个 dependencies/nodejs
文件夹。
我将 dep.js
添加到该文件夹:
exports.x = 'It works!!';
我也添加了package.json
到同一个文件夹:
{
"name": "dep",
"version": "1.0.0",
"description": "",
"main": "dep.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
在 hello-world(包含 lambda 函数的文件夹)下,我将以下内容添加到 package.json
中的依赖项中:
"dep": "file:../dependencies/nodejs"
I 运行 npm install
under hello-world
并且它复制了 node_modules/dep
下的依赖项。
通常,您不会在这里这样做。这纯粹是为了允许我在本地 运行 而无需使用 sam CLI。这只是纯 nodejs 代码。我可以 运行 测试,我可以调试,而不必等待 20 秒或更长时间,而 sam 将所有内容打包并调用我的函数。在这种状态下开发非常棒,因为它非常快。但是,它最终需要在野外正确 运行。
我编辑了 ./hello-world/app.js
:
const dep = require('dep');
let response;
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
response = {
'statusCode': 200,
'dep.x': dep.x,
'body': JSON.stringify({
message: 'Hello, World!!',
// location: ret.data.trim()
})
}
} catch (err) {
console.log(err);
return err;
}
return response
};
if(require.main === module){
(async () => {
var result = await exports.lambdaHandler(process.argv[1]);
console.log(result);
})();
}
我编辑了 template.yml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app-2
Sample SAM Template for sam-app-2
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Layers:
- !Ref DepLayer
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
DepLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: sam-app-dependencies-2
Description: Dependencies for sam app [temp-units-conv]
ContentUri: ./dependencies/
CompatibleRuntimes:
- nodejs12.x
LicenseInfo: 'MIT'
RetentionPolicy: Retain
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
运行 直接从命令行运行:
sam-app-2> node hello-world\app.js
{
statusCode: 200,
'dep.x': 'It works!!',
body: '{"message":"Hello, World!!"}'
}
甚至 sam deploy
也行!是的,它将代码部署到云端,当我在云端调用 lambda 函数时,它给出了与上面相同的结果。
但是,当我 运行 sam build
时,它失败了:
Building resource 'HelloWorldFunction'
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrc
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Build Failed
Error: NodejsNpmBuilder:NpmInstall - NPM Failed: npm ERR! code ENOLOCAL
npm ERR! Could not install from "..\dependencies\nodejs" as it does not contain a package.json file.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Brandon\AppData\Roaming\npm-cache\_logs20-03-04T19_34_01_873Z-debug.log
当我尝试在本地调用 lambda 时:
sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambdaHandler (nodejs12.x)
DepLayer is a local Layer in the template
Building image...
Requested to skip pulling images ...
Mounting C:\Users\Brandon\source\repos\sam-app-2\hello-world as /var/task:ro,delegated inside runtime container
2020-03-03T19:34:28.824Z undefined ERROR Uncaught Exception {"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'dep'\nRequire stack:\n- /var/task/app.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js","stack":["Runtime.ImportModuleError: Error: Cannot find module 'dep'","Require stack:","- /var/task/app.js","- /var/runtime/UserFunction.js","- /var/runtime/index.js"," at _loadUserApp (/var/runtime/UserFunction.js:100:13)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/index.js:43:30)"," at Module._compile (internal/modules/cjs/loader.js:955:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)"," at Module.load (internal/modules/cjs/loader.js:811:32)"," at Function.Module._load (internal/modules/cjs/loader.js:723:14)"," at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)"," at internal/main/run_main_module.js:17:11"]}
?[32mSTART RequestId: b6f39717-746d-1597-9838-3b6472ec8843 Version: $LATEST?[0m
?[32mEND RequestId: b6f39717-746d-1597-9838-3b6472ec8843?[0m
?[32mREPORT RequestId: b6f39717-746d-1597-9838-3b6472ec8843 Init Duration: 237.77 ms Duration: 3.67 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 38 MB ?[0m
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'dep'\nRequire stack:\n- /var/task/app.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js"}
当我尝试使用 sam local start-api
在本地启动 API 时,它失败并出现与上述相同的错误。
我在想,如果不是因为在构建阶段关闭了相对文件路径,我就可以吃蛋糕(非常快速地在本地调试)并吃掉它(运行 sam build
, sam local start-api
).
我该怎么办?
在经历了很多挫折和焦虑之后,产生了这个:
https://github.com/blmille1/aws-sam-layers-template
尽情享受吧!
我跟进了你批准的答案,我相信我找到了正确的答案(自从我通过 Google 到达这里后就在这里发帖,其他人可能会在这里徘徊)。
1.按照以下方式组织您的模块(my-layers 和 my-module 可以调整,但 nodejs/node_modules必须保留)
+ my-layers
| + my-module
| + nodejs
| + node_modules
| + my-module
| + index.js
| + package.json
+ my-serverless
+ template.yaml
+ package.json
我不知道 package.json 的理想 设置。指定 "main": "index.js"
对我来说就足够了。
{
"name": "my-module",
"version": "1.0.0",
"main": "index.js"
}
这是 index.js
exports.ping = () => console.log('pong');
2。在SAM模板lambda层指向../my-layers/
MyModuleLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-module
ContentUri: ../my-layers/my-module/
CompatibleRuntimes:
- nodejs14.x
到目前为止一切顺利。但是现在 - 获得代码完成并摆脱你在 func2internals.js
中的疯狂 require(process.env.AWS ? '/opt/nodejs/common' : '../../layers/layer1/nodejs/common');
3。将依赖项添加到 my-serverless
一个。从 CLI 安装:
npm i --save ../my-layers/my-module/nodejs/node_modules/my-module
b。或添加 package.json 依赖项
"dependencies": {
"my-module": "file:../my-layers/my-module/nodejs/node_modules/my-module",
}
4.在你的无服务器函数中使用我的模块
var myModule = require('my-module');
exports.handler = async (event) => {
myModule.ping();
};
就是这样。你有代码完成,它在本地环境和 sam local invoke
和 sam local start-api
中工作
并且不要忘记从 .gitignore
中排除 my-layers/my-module/nodejs/node_modules
:)
我也遇到了同样的问题,最终想出了一个替代你的解决方案。它提供了您正在寻找的开发人员体验,但避免了解决方案中的轻微不便和维护开销(也就是对访问共享层的每个导入使用三元运算符)。我提出的解决方案使用与您类似的方法,但每个 lambda 函数只需要一个 one-time 初始化调用。在幕后,它使用 module-alias 在 运行 时间内解决依赖关系。
这是一个 link 到带有示例模板的存储库:https://github.com/dangpg/aws-sam-shared-layers-template
(tl;dr) 使用 linked 模板时,您将获得:
- 使用层在多个 lambda 函数之间共享公共代码或依赖关系
- 不使用任何模块打包器(例如 Webpack)
- 相反,在 运行 时间
期间使用 module-alias 解决依赖关系
- 通过AWS Toolkit
支持VSCode内的本地调试
- 代码可以在 AWS 沙箱外执行 (
node app.js
)
- 支持使用 Jest 进行单元测试
- Intellisense/Autocomplete VSCode
以内
- 兼容 SAM CLI(
sam build
、sam deploy
、sam local invoke
、sam local start-api
等)。
- 可以作为通用 lambda
在云中部署和 运行
1.文件夹结构
+ lambdas
| + func1
| - app.js
| - package.json
| + func2
| - app.js
| - package.json
+ layers
| + common // can be any name, I chose common
| + nodejs // needs to be nodejs due to how SAM handles layers
| - index.js // file for own custom code that you want to share
| - package.json // list any dependencies you want to share
- template.yaml
这是我最终得到的文件夹结构。但请记住,它非常灵活,不需要硬性规则来满足可能的相对文件路径(但是,如果您的结构不同,您将需要调整一些文件)。
如果你想在 lambda 函数之间共享 npm 包,只需将它们添加到层文件夹中的 package.json
:
{
"name": "common",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"lorem-ipsum": "^2.0.4"
}
}
为了完整起见,这里是 index.js
的内容:
exports.ping = () => {
return "pong";
};
2。 Template.yaml
[...]
Globals:
Function:
Timeout: 3
Runtime: nodejs14.x
Environment:
Variables:
AWS: true
Resources:
Func1:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambdas/func1/
Handler: app.lambdaHandler
Layers:
- !Ref CommonLayer
[...]
Func2:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambdas/func2/
Handler: app.lambdaHandler
Layers:
- !Ref CommonLayer
[...]
CommonLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ./layers/common/
CompatibleRuntimes:
- nodejs14.x
RetentionPolicy: Retain
非常简单,只需按照 official example 了解如何在 lambda 中包含层即可。就像在您的解决方案中一样,添加一个 gloval env 变量,这样我们就可以区分我们是否在 AWS 沙箱中 运行ning 代码。
3。拉姆达 package.json
将 module-alias
添加为 dependency
并将您的本地公用文件夹添加为 devDependency
到每个 lambda 函数:
...
"dependencies": {
"module-alias": "^2.2.2"
},
"devDependencies": {
"common__internal": "file:../../layers/common/nodejs" // adapt relative path according to your folder structure
},
...
稍后我们将需要对公共文件夹的本地引用(例如用于测试)。我们将其添加为 devDependency,因为我们只需要它用于本地开发,因此我们不会 运行 在 运行 宁 sam build
时出现问题(因为它忽略了 devDependencies)。我选择 common__internal
作为包名,但是你可以自由选择任何你喜欢的。确保在进行任何本地开发之前 运行 npm install
。
4. Lambda 处理程序
在您的处理程序源代码中,在您从共享层导入任何包之前,初始化 module-alias
以执行以下操作:
const moduleAlias = require("module-alias");
moduleAlias.addAlias("@common", (fromPath, request) => {
if (process.env.AWS && request.startsWith("@common/")) {
// AWS sandbox and reference to dependency
return "/opt/nodejs/node_modules";
}
if (process.env.AWS) {
// AWS sandbox and reference to custom shared code
return "/opt/nodejs";
}
if (request.startsWith("@common/")) {
// Local development and reference to dependency
return "../../layers/common/nodejs/node_modules";
}
// Local development and reference to custom shared code
return "../../layers/common/nodejs";
});
const { ping } = require("@common"); // your custom shared code
const { loremIpsum } = require("@common/lorem-ipsum"); // shared dependency
exports.lambdaHandler = async (event, context) => {
try {
const response = {
statusCode: 200,
body: JSON.stringify({
message: "hello world",
ping: ping(),
lorem: loremIpsum(),
}),
};
return response;
} catch (err) {
console.log(err);
return err;
}
};
if (require.main === module) {
(async () => {
var result = await exports.lambdaHandler(process.argv[1]);
console.log(result);
})();
}
您可以将 module-alias
代码部分移动到一个单独的文件中,然后只在开头导入那个文件(或者甚至发布您自己的自定义包,然后您可以正确导入;这样您就可以引用它在每个 lambda 函数中,不必重复代码)。同样,根据您的文件夹结构调整相对文件路径。与您的方法类似,它会检查 AWS
环境变量并相应地调整导入路径。但是,这只需执行一次。之后,所有后续导入都可以使用您定义的别名:const { ping } = require("@common");
和 const { loremIpsum } = require("@common/lorem-ipsum");
。同样在这里,您可以随意定义您自己的关于如何处理别名的自定义逻辑。这只是我想出的对我有用的解决方案。
从现在开始,您应该能够通过 node app.js
在本地或通过 SAM CLI(sam build
、sam local invoke
等)执行您的 lambda 代码。但是,如果您想要本地测试和智能感知,还有一些额外的工作要做。
5.智能感知
对于 VSCode,您只需添加一个 jsconfig.json
文件,其中包含您定义的别名的相应路径映射。将其指向之前的内部 devdependency:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@common": ["./node_modules/common__internal/index.js"],
"@common/*": ["./node_modules/common__internal/node_modules/*"]
}
}
}
6.测试
为了测试,我个人使用 Jest。幸运的是,Jest 也提供了 option 来提供路径映射:
// within your lambda package.json
"jest": {
"moduleNameMapper": {
"@common/(.*)": "<rootDir>/node_modules/common__internal/node_modules/",
"@common": "<rootDir>/node_modules/common__internal/"
}
}
最终免责声明
此解决方案目前仅在使用 CommonJS 模块系统时有效。我在使用 ES 模块时无法重现相同的结果(主要是由于 module-alias
的 lacking support)。但是,如果有人能想出一个使用 ES 模块的解决方案,我很高兴听到!
就是这样!希望我没有遗漏任何东西。总的来说,我对该解决方案提供的开发人员体验非常满意。请随时查看 linked 模板存储库以获取更多详细信息。我知道自您最初提出问题以来已经有一段时间了,但我将其留在此处,希望它也能对其他开发人员有所帮助。干杯
我的目标是使用层 和 在我们的几个 lambda 函数之间共享库代码能够在本地调试和 运行 测试。
npm 能够从本地文件系统安装依赖项。当我们更改我们的库代码时,我们希望该库的所有用户都能获得更新后的代码,而无需设置专用的 npm 服务器。
我可以使用相对路径在本地进行调试,但那是在我涉及 sam build
之前。
sam build
在存储库的根目录创建一个隐藏文件夹并构建文件夹,最终 运行s npm install
,但是这次文件夹结构不同。 package.json
文件中使用的相对路径现已损坏。我们不能使用显式路径,因为我们的存储库位于我们的用户文件夹下,这当然因开发人员而异。
这是我所做的:
我使用 sam init
创建了一个项目,并为 nodejs 12.x
项目采用了默认值(sam-app-2
的名称除外)(选项 1
和 1
).
该命令创建了一个名为 sam-app-2
的文件夹,它是以下所有文件名的参考。
我创建了一个 dependencies/nodejs
文件夹。
我将 dep.js
添加到该文件夹:
exports.x = 'It works!!';
我也添加了package.json
到同一个文件夹:
{
"name": "dep",
"version": "1.0.0",
"description": "",
"main": "dep.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
在 hello-world(包含 lambda 函数的文件夹)下,我将以下内容添加到 package.json
中的依赖项中:
"dep": "file:../dependencies/nodejs"
I 运行 npm install
under hello-world
并且它复制了 node_modules/dep
下的依赖项。
通常,您不会在这里这样做。这纯粹是为了允许我在本地 运行 而无需使用 sam CLI。这只是纯 nodejs 代码。我可以 运行 测试,我可以调试,而不必等待 20 秒或更长时间,而 sam 将所有内容打包并调用我的函数。在这种状态下开发非常棒,因为它非常快。但是,它最终需要在野外正确 运行。
我编辑了 ./hello-world/app.js
:
const dep = require('dep');
let response;
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
response = {
'statusCode': 200,
'dep.x': dep.x,
'body': JSON.stringify({
message: 'Hello, World!!',
// location: ret.data.trim()
})
}
} catch (err) {
console.log(err);
return err;
}
return response
};
if(require.main === module){
(async () => {
var result = await exports.lambdaHandler(process.argv[1]);
console.log(result);
})();
}
我编辑了 template.yml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app-2
Sample SAM Template for sam-app-2
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Layers:
- !Ref DepLayer
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
DepLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: sam-app-dependencies-2
Description: Dependencies for sam app [temp-units-conv]
ContentUri: ./dependencies/
CompatibleRuntimes:
- nodejs12.x
LicenseInfo: 'MIT'
RetentionPolicy: Retain
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
运行 直接从命令行运行:
sam-app-2> node hello-world\app.js
{
statusCode: 200,
'dep.x': 'It works!!',
body: '{"message":"Hello, World!!"}'
}
甚至 sam deploy
也行!是的,它将代码部署到云端,当我在云端调用 lambda 函数时,它给出了与上面相同的结果。
但是,当我 运行 sam build
时,它失败了:
Building resource 'HelloWorldFunction'
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrc
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Build Failed
Error: NodejsNpmBuilder:NpmInstall - NPM Failed: npm ERR! code ENOLOCAL
npm ERR! Could not install from "..\dependencies\nodejs" as it does not contain a package.json file.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Brandon\AppData\Roaming\npm-cache\_logs20-03-04T19_34_01_873Z-debug.log
当我尝试在本地调用 lambda 时:
sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambdaHandler (nodejs12.x)
DepLayer is a local Layer in the template
Building image...
Requested to skip pulling images ...
Mounting C:\Users\Brandon\source\repos\sam-app-2\hello-world as /var/task:ro,delegated inside runtime container
2020-03-03T19:34:28.824Z undefined ERROR Uncaught Exception {"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'dep'\nRequire stack:\n- /var/task/app.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js","stack":["Runtime.ImportModuleError: Error: Cannot find module 'dep'","Require stack:","- /var/task/app.js","- /var/runtime/UserFunction.js","- /var/runtime/index.js"," at _loadUserApp (/var/runtime/UserFunction.js:100:13)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/index.js:43:30)"," at Module._compile (internal/modules/cjs/loader.js:955:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)"," at Module.load (internal/modules/cjs/loader.js:811:32)"," at Function.Module._load (internal/modules/cjs/loader.js:723:14)"," at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)"," at internal/main/run_main_module.js:17:11"]}
?[32mSTART RequestId: b6f39717-746d-1597-9838-3b6472ec8843 Version: $LATEST?[0m
?[32mEND RequestId: b6f39717-746d-1597-9838-3b6472ec8843?[0m
?[32mREPORT RequestId: b6f39717-746d-1597-9838-3b6472ec8843 Init Duration: 237.77 ms Duration: 3.67 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 38 MB ?[0m
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'dep'\nRequire stack:\n- /var/task/app.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js"}
当我尝试使用 sam local start-api
在本地启动 API 时,它失败并出现与上述相同的错误。
我在想,如果不是因为在构建阶段关闭了相对文件路径,我就可以吃蛋糕(非常快速地在本地调试)并吃掉它(运行 sam build
, sam local start-api
).
我该怎么办?
在经历了很多挫折和焦虑之后,产生了这个: https://github.com/blmille1/aws-sam-layers-template
尽情享受吧!
我跟进了你批准的答案,我相信我找到了正确的答案(自从我通过 Google 到达这里后就在这里发帖,其他人可能会在这里徘徊)。
1.按照以下方式组织您的模块(my-layers 和 my-module 可以调整,但 nodejs/node_modules必须保留)
+ my-layers
| + my-module
| + nodejs
| + node_modules
| + my-module
| + index.js
| + package.json
+ my-serverless
+ template.yaml
+ package.json
我不知道 package.json 的理想 设置。指定 "main": "index.js"
对我来说就足够了。
{
"name": "my-module",
"version": "1.0.0",
"main": "index.js"
}
这是 index.js
exports.ping = () => console.log('pong');
2。在SAM模板lambda层指向../my-layers/
MyModuleLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-module
ContentUri: ../my-layers/my-module/
CompatibleRuntimes:
- nodejs14.x
到目前为止一切顺利。但是现在 - 获得代码完成并摆脱你在 func2internals.js
中的疯狂require(process.env.AWS ? '/opt/nodejs/common' : '../../layers/layer1/nodejs/common');
3。将依赖项添加到 my-serverless
一个。从 CLI 安装:
npm i --save ../my-layers/my-module/nodejs/node_modules/my-module
b。或添加 package.json 依赖项
"dependencies": {
"my-module": "file:../my-layers/my-module/nodejs/node_modules/my-module",
}
4.在你的无服务器函数中使用我的模块
var myModule = require('my-module');
exports.handler = async (event) => {
myModule.ping();
};
就是这样。你有代码完成,它在本地环境和 sam local invoke
和 sam local start-api
并且不要忘记从 .gitignore
中排除 my-layers/my-module/nodejs/node_modules
:)
我也遇到了同样的问题,最终想出了一个替代你的解决方案。它提供了您正在寻找的开发人员体验,但避免了解决方案中的轻微不便和维护开销(也就是对访问共享层的每个导入使用三元运算符)。我提出的解决方案使用与您类似的方法,但每个 lambda 函数只需要一个 one-time 初始化调用。在幕后,它使用 module-alias 在 运行 时间内解决依赖关系。
这是一个 link 到带有示例模板的存储库:https://github.com/dangpg/aws-sam-shared-layers-template
(tl;dr) 使用 linked 模板时,您将获得:
- 使用层在多个 lambda 函数之间共享公共代码或依赖关系
- 不使用任何模块打包器(例如 Webpack)
- 相反,在 运行 时间 期间使用 module-alias 解决依赖关系
- 通过AWS Toolkit 支持VSCode内的本地调试
- 代码可以在 AWS 沙箱外执行 (
node app.js
) - 支持使用 Jest 进行单元测试
- Intellisense/Autocomplete VSCode 以内
- 兼容 SAM CLI(
sam build
、sam deploy
、sam local invoke
、sam local start-api
等)。 - 可以作为通用 lambda 在云中部署和 运行
1.文件夹结构
+ lambdas
| + func1
| - app.js
| - package.json
| + func2
| - app.js
| - package.json
+ layers
| + common // can be any name, I chose common
| + nodejs // needs to be nodejs due to how SAM handles layers
| - index.js // file for own custom code that you want to share
| - package.json // list any dependencies you want to share
- template.yaml
这是我最终得到的文件夹结构。但请记住,它非常灵活,不需要硬性规则来满足可能的相对文件路径(但是,如果您的结构不同,您将需要调整一些文件)。
如果你想在 lambda 函数之间共享 npm 包,只需将它们添加到层文件夹中的 package.json
:
{
"name": "common",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"lorem-ipsum": "^2.0.4"
}
}
为了完整起见,这里是 index.js
的内容:
exports.ping = () => {
return "pong";
};
2。 Template.yaml
[...]
Globals:
Function:
Timeout: 3
Runtime: nodejs14.x
Environment:
Variables:
AWS: true
Resources:
Func1:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambdas/func1/
Handler: app.lambdaHandler
Layers:
- !Ref CommonLayer
[...]
Func2:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambdas/func2/
Handler: app.lambdaHandler
Layers:
- !Ref CommonLayer
[...]
CommonLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ./layers/common/
CompatibleRuntimes:
- nodejs14.x
RetentionPolicy: Retain
非常简单,只需按照 official example 了解如何在 lambda 中包含层即可。就像在您的解决方案中一样,添加一个 gloval env 变量,这样我们就可以区分我们是否在 AWS 沙箱中 运行ning 代码。
3。拉姆达 package.json
将 module-alias
添加为 dependency
并将您的本地公用文件夹添加为 devDependency
到每个 lambda 函数:
...
"dependencies": {
"module-alias": "^2.2.2"
},
"devDependencies": {
"common__internal": "file:../../layers/common/nodejs" // adapt relative path according to your folder structure
},
...
稍后我们将需要对公共文件夹的本地引用(例如用于测试)。我们将其添加为 devDependency,因为我们只需要它用于本地开发,因此我们不会 运行 在 运行 宁 sam build
时出现问题(因为它忽略了 devDependencies)。我选择 common__internal
作为包名,但是你可以自由选择任何你喜欢的。确保在进行任何本地开发之前 运行 npm install
。
4. Lambda 处理程序
在您的处理程序源代码中,在您从共享层导入任何包之前,初始化 module-alias
以执行以下操作:
const moduleAlias = require("module-alias");
moduleAlias.addAlias("@common", (fromPath, request) => {
if (process.env.AWS && request.startsWith("@common/")) {
// AWS sandbox and reference to dependency
return "/opt/nodejs/node_modules";
}
if (process.env.AWS) {
// AWS sandbox and reference to custom shared code
return "/opt/nodejs";
}
if (request.startsWith("@common/")) {
// Local development and reference to dependency
return "../../layers/common/nodejs/node_modules";
}
// Local development and reference to custom shared code
return "../../layers/common/nodejs";
});
const { ping } = require("@common"); // your custom shared code
const { loremIpsum } = require("@common/lorem-ipsum"); // shared dependency
exports.lambdaHandler = async (event, context) => {
try {
const response = {
statusCode: 200,
body: JSON.stringify({
message: "hello world",
ping: ping(),
lorem: loremIpsum(),
}),
};
return response;
} catch (err) {
console.log(err);
return err;
}
};
if (require.main === module) {
(async () => {
var result = await exports.lambdaHandler(process.argv[1]);
console.log(result);
})();
}
您可以将 module-alias
代码部分移动到一个单独的文件中,然后只在开头导入那个文件(或者甚至发布您自己的自定义包,然后您可以正确导入;这样您就可以引用它在每个 lambda 函数中,不必重复代码)。同样,根据您的文件夹结构调整相对文件路径。与您的方法类似,它会检查 AWS
环境变量并相应地调整导入路径。但是,这只需执行一次。之后,所有后续导入都可以使用您定义的别名:const { ping } = require("@common");
和 const { loremIpsum } = require("@common/lorem-ipsum");
。同样在这里,您可以随意定义您自己的关于如何处理别名的自定义逻辑。这只是我想出的对我有用的解决方案。
从现在开始,您应该能够通过 node app.js
在本地或通过 SAM CLI(sam build
、sam local invoke
等)执行您的 lambda 代码。但是,如果您想要本地测试和智能感知,还有一些额外的工作要做。
5.智能感知
对于 VSCode,您只需添加一个 jsconfig.json
文件,其中包含您定义的别名的相应路径映射。将其指向之前的内部 devdependency:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@common": ["./node_modules/common__internal/index.js"],
"@common/*": ["./node_modules/common__internal/node_modules/*"]
}
}
}
6.测试
为了测试,我个人使用 Jest。幸运的是,Jest 也提供了 option 来提供路径映射:
// within your lambda package.json
"jest": {
"moduleNameMapper": {
"@common/(.*)": "<rootDir>/node_modules/common__internal/node_modules/",
"@common": "<rootDir>/node_modules/common__internal/"
}
}
最终免责声明
此解决方案目前仅在使用 CommonJS 模块系统时有效。我在使用 ES 模块时无法重现相同的结果(主要是由于 module-alias
的 lacking support)。但是,如果有人能想出一个使用 ES 模块的解决方案,我很高兴听到!
就是这样!希望我没有遗漏任何东西。总的来说,我对该解决方案提供的开发人员体验非常满意。请随时查看 linked 模板存储库以获取更多详细信息。我知道自您最初提出问题以来已经有一段时间了,但我将其留在此处,希望它也能对其他开发人员有所帮助。干杯