![](/img/trans.png)
[英]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.