簡體   English   中英

如何獲得相對 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在存儲庫的根目錄創建一個隱藏文件夾並構建該文件夾並最終運行npm install ,但是這次文件夾結構不同。 package.json文件中使用的相對路徑現已損壞。 我們不能使用顯式路徑,因為我們的存儲庫位於我們的用戶文件夾下,這當然因開發人員而異。

這是我所做的:

我使用sam init創建了一個項目,並為nodejs 12.x項目(選項11 )采用了默認值( 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 buildsam local start-api )。

我應該怎么辦?

我也遇到了同樣的問題,最終想出了一個替代解決方案。 它提供了您正在尋找的開發人員體驗,但避免了解決方案中的輕微不便和維護開銷(也就是對訪問共享層的每個導入使用三元運算符)。 我提出的解決方案使用與您的類似的方法,但只需要按 lambda function 進行一次初始化調用。在幕后,它使用模塊別名在運行時解決依賴關系。

這是帶有示例模板的存儲庫鏈接: https://github.com/dangpg/aws-sam-shared-layers-template

( tl; dr ) 使用鏈接模板時,您將獲得:

  • 使用層在多個 lambda 函數之間共享公共代碼或依賴項
  • 不使用任何模塊捆綁器(例如,Webpack)
  • 相反,使用模塊別名在運行時解決依賴關系
  • 支持通過AWS 工具包在 VSCode 中進行本地調試
  • 代碼可以在 AWS 沙箱之外執行 ( node app.js )
  • 支持使用 Jest 進行單元測試
  • VSCode 中的智能感知/自動完成
  • 兼容 SAM CLI( sam buildsam deploysam local invokesam 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.模板.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 buildsam 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-layersmy-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 invokesam local start-api

並且不要忘記從.gitignore排除my-layers/my-module/nodejs/node_modules :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM