[英]AWS CDK Code Pipeline Development - How Do I Install NodejsFunction Package.json Dependencies?
[英]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
在存储库的根目录创建一个隐藏文件夹并构建该文件夹并最终运行npm install
,但是这次文件夹结构不同。 package.json
文件中使用的相对路径现已损坏。 我们不能使用显式路径,因为我们的存储库位于我们的用户文件夹下,这当然因开发人员而异。
这是我所做的:
我使用sam init
创建了一个项目,并为nodejs 12.x
项目(选项1
和1
)采用了默认值( sam-app-2
的名称除外)。
该命令创建了一个名为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"
我在hello-world
下运行了npm install
,它复制了node_modules/dep
下的依赖项。 通常,您不会在这里这样做。 这纯粹是为了让我在不涉及 sam CLI 的情况下在本地运行。 这只是纯 nodejs 代码。 我可以运行测试,我可以调试,而不必等待 20 秒或更长时间,而 sam 将所有内容打包并调用我的 function。在这个 state 中进行开发非常棒,因为它非常快。 但是,它最终需要在野外正确运行。
我编辑了./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 function 时。 它给出了与上面相同的结果。
但是,当我运行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\_logs\2020-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
)。
我应该怎么办?
我也遇到了同样的问题,最终想出了一个替代解决方案。 它提供了您正在寻找的开发人员体验,但避免了解决方案中的轻微不便和维护开销(也就是对访问共享层的每个导入使用三元运算符)。 我提出的解决方案使用与您的类似的方法,但只需要按 lambda function 进行一次初始化调用。在幕后,它使用模块别名在运行时解决依赖关系。
这是带有示例模板的存储库链接: https://github.com/dangpg/aws-sam-shared-layers-template
( tl; dr ) 使用链接模板时,您将获得:
node app.js
)sam build
、 sam deploy
、 sam local invoke
、 sam local start-api
等)。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.模板.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
非常简单,只需按照有关如何在 lambda 中包含层的官方示例进行操作。 就像在您的解决方案中一样,添加一个 gloval env 变量,这样我们就可以区分我们是否在 AWS 沙箱中运行代码。
3. Lambda package.json
将module-alias
作为dependency
项添加,并将本地公共文件夹作为devDependency
到每个 lambda function:
...
"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
作为 package 名称,但是您可以自由选择任何您喜欢的名称。 在进行任何本地开发之前,请确保运行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
代码部分移动到一个单独的文件中,然后只在开头导入那个文件(或者甚至发布您自己的自定义 package 然后您可以正确导入;这样您就可以在每个 lambda function 和不必重复代码)。 同样,根据您的文件夹结构调整相对文件路径。 与您的方法类似,它检查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 也提供了提供路径映射的选项:
// within your lambda package.json
"jest": {
"moduleNameMapper": {
"@common/(.*)": "<rootDir>/node_modules/common__internal/node_modules/$1",
"@common": "<rootDir>/node_modules/common__internal/"
}
}
最终免责声明
该解决方案目前仅在使用 CommonJS 模块系统时有效。 使用 ES 模块时,我无法重现相同的结果(主要是由于缺乏对module-alias
的支持)。 然而,如果有人能想出一个使用 ES 模块的解决方案,我很高兴听到!
就是这样。 总的来说,希望我没有遗漏任何东西。 我对该解决方案提供的开发人员体验非常满意。 请随时查看链接的模板存储库以获取更多详细信息。 我知道自您最初提出问题以来已经有一段时间了,但我将其留在此处,希望它也能对其他开发人员有所帮助。 干杯
经过多次挫折和焦虑,这已经产生: https : //github.com/blmille1/aws-sam-layers-template
享受!
我跟进了你批准的答案,我相信我找到了正确的答案(并且在我通过谷歌到达这里后发布在这里,其他人可能会在这里徘徊)。
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
到现在为止还挺好。 但是现在 - 要完成代码并摆脱疯狂的require(process.env.AWS ? '/opt/nodejs/common' : '../../layers/layer1/nodejs/common');
你在func2internals.js
3.在my-serverless中添加依赖
一种。 从 CLI 安装:
npm i --save ../my-layers/my-module/nodejs/node_modules/my-module
湾或添加 package.json 依赖项
"dependencies": {
"my-module": "file:../my-layers/my-module/nodejs/node_modules/my-module",
}
4. 在你的无服务器功能中使用 my-module
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
:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.