【问题标题】:NodeJS 14.x - Native AWS Lambda Import/Export SupportNodeJS 14.x - 原生 AWS Lambda 导入/导出支持
【发布时间】:2021-06-14 23:53:58
【问题描述】:

我希望使用 ES6 自带的原生 import/export

我在 AWS Lambda 中使用无服务器容器。

我的Dockerfile 看起来像这样:

FROM public.ecr.aws/lambda/nodejs:14

COPY app ./

RUN npm install

CMD [ "app.handler" ]

然后我有一个app 目录,其中包含我的应用程序代码。 app.js 代码如下所示:

import { success } from './utils/log';

exports.handler = async () => {
  success('lambda invoked');
  const response = 'Hello World';
  return {
    statusCode: 200,
    body: JSON.stringify(response),
    isBase64Encoded: false,
  };
};

import { success } from './utils/log';这一行可以看出,我正在使用本地导入。

在我的 package.json 中我指定了这个:

  "type": "module"

因为我需要告诉我的应用程序这是一个模块,我想自然地导入。如果我不指定这一点,我会得到:

{
    "errorType": "Runtime.UserCodeSyntaxError",
    "errorMessage": "SyntaxError: Cannot use import statement outside a module",
    "stack": [
        "Runtime.UserCodeSyntaxError: SyntaxError: Cannot use import statement outside a module",
        "    at _loadUserApp (/var/runtime/UserFunction.js:98: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:1063:30)",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)",
        "    at Module.load (internal/modules/cjs/loader.js:928:32)",
        "    at Function.Module._load (internal/modules/cjs/loader.js:769:14)",
        "    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)",
        "    at internal/main/run_main_module.js:17:47"
    ]
}

所以,我指定它,告诉 Lambda 这是一个模块。但是,我这辈子都无法让它工作,我看到了这个错误:

{
    "errorType": "Error",
    "errorMessage": "Must use import to load ES Module: /var/task/app.js\nrequire() of ES modules is not supported.\nrequire() of /var/task/app.js from /var/runtime/UserFunction.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.\nInstead rename app.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /var/task/package.json.\n",
    "code": "ERR_REQUIRE_ESM",
    "stack": [
        "Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /var/task/app.js",
        "require() of ES modules is not supported.",
        "require() of /var/task/app.js from /var/runtime/UserFunction.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.",
        "Instead rename app.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /var/task/package.json.",
        "",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1080:13)",
        "    at Module.load (internal/modules/cjs/loader.js:928:32)",
        "    at Function.Module._load (internal/modules/cjs/loader.js:769:14)",
        "    at Module.require (internal/modules/cjs/loader.js:952:19)",
        "    at require (internal/modules/cjs/helpers.js:88:18)",
        "    at _tryRequire (/var/runtime/UserFunction.js:75:12)",
        "    at _loadUserApp (/var/runtime/UserFunction.js:95:12)",
        "    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:1063:30)"
    ]
}

看起来/var/runtime/UserFunction.js 正在调用我的应用程序处理程序作为一个需求和一个模块。但是,我无法控制/var/runtime/UserFunction.js(我不相信?)。在我的Dockerfile 中,我指定了Node14。我不太清楚自己哪里出错了?

我想要做的是运行最新最好的 Node14(例如导入),而不需要“膨胀”我的代码的 Babel/Transpiler。如果有人能指出我出错的正确方向,将不胜感激。

【问题讨论】:

    标签: javascript node.js amazon-web-services aws-lambda containers


    【解决方案1】:

    似乎从昨天开始,Node 14 lambdas 中终于有了对 ES6 模块语法的原生支持 - 请参阅 https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda

    【讨论】:

    • 我真的很难让它发挥作用。有没有人成功过,或者看过任何完整的代码示例?特别是,在尝试导入 AWS SDK v3 包时,我总是收到 Cannot find package 错误,例如 import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
    • @farski 我遇到了同样的问题,您是否将您的部门部署为一个层?如果是,那可能是问题twitter.com/coderbyheart/status/1486470882008645634(请参阅@pfried 上面的评论)
    • 是的!我昨天才发现这一点,打算回来更新我的评论。
    • 当您的处理程序有一个“影子”文件夹时,请注意存在错误:repost.aws/questions/QUG3mryU5bRJ6ieTR-IiowCg/…
    【解决方案2】:

    AWS Lambda 不官方支持 ESM,但通过以下变通方法可以顺利运行。

    这个答案是受Evan SosenkoDan Kantor 的答案/cmets 以及我的一些其他想法启发的不同解决方法的总结。这包括对 typescript 项目的一些更好的处理,但其中的一部分也可以用于普通的 javascript 项目。

    我假设如下:

    1. Node.js 版本 14 使用:FROM public.ecr.aws/lambda/nodejs:14
    2. 使用无服务器容器:FROM public.ecr.aws/lambda/nodejs:14
    3. 本地导入应该可以在没有文件结尾的情况下工作:import { success } from './utils/log';
    4. 从库导入应使用 ESM 语法:import AWS from 'aws-sdk';
    5. lambda 是用 typescript 编写的:(.ts 是文件后缀)。

    (我还在末尾提供了普通 .js 而非 typescript 的信息)

    Dockerfile

    FROM public.ecr.aws/lambda/nodejs:14
    
    # copy only package.json + package-lock.json 
    COPY package*.json ./
    
    # install all npm dependencies including dev dependencies
    RUN npm install
    
    # copy all files not excluded by .dockerignore of current directory to docker container
    COPY .  ./
    
    # build typescript
    RUN tsc
    
    # remove npm dev dependencies as they are not needed anymore
    RUN npm prune --production
    
    # remove typescript sources
    # RUN rm -r src
    
    # rename all .js files to .mjs and fix imports except for handler.js
    RUN find ./dist -type f -name "*.js" -exec sed -i 's/\.js/\.mjs/g' {} \;
    RUN find ./dist -type f -name -and -not -name "handler.js" "*.js" -exec sh -c 'mv "$0" "${0%.js}.mjs"' {} \;
    
    # allow local imports without file ending - see: https://nodejs.org/api/esm.html
    ENV NODE_OPTIONS="--experimental-specifier-resolution=node"
    
    # set handler function
    CMD ["dist/lambda/handler.handler"]
    

    说明

    由于 AWS Lambda 仅支持 commonJS,因此 Lambda 入口点是一个 commonJS 文件。这是由一个空的 package.json 指定的,它会覆盖项目根目录中的 package.json。由于此文件为空,因此它不包含:"type":"module",并将该文件夹和子文件夹中的所有文件默认为 commonJS。如果具有 .mjs 扩展名,commonJS 文件可以访问 ESM 文件,但是当 typescript 编译为 .js 时,我使用一些 unix 命令在调用 tsc 后重命名所有文件数学为“.*js”。 handler.js 必须保留“.js”,所以我将它从 .mjs 重命名回来。
    More about ".js/.mjs"

    文件夹结构

    - src
    - - lambda
    - - - handler.ts (commonJS)
    - - - package.json (contains only: `{}`)
    - - app.ts (ESM)
    - - services
    - - - (other .ts files ESM)
    - package.json (contains `{"type": "module"}`, but also other settings)
    - Dockerfile
    - tsconfig.json
    

    handler.ts

    exports.handler = async (event) => {
        const {App} = await import('../app.js');
        const app = new App();
        return await app.run(event);
    };
    

    app.ts

    // example import library
    import AWS from 'aws-sdk';
    // example import local file
    import {FileService} from './services/file.service';
    
    class App {
      constructor() {
      }
    
      async run(event) {
         // write your logic here
         return {
           'statusCode': 200,
           'body': JSON.stringify({'message': 'hello world'})
         }
      }
    }
    export {App};
    

    tsconfig.json

    {
      "compilerOptions": {
        "module": "ESNext",
        "moduleResolution": "Node",
        "esModuleInterop": true,
        "declaration": true,
        "removeComments": true,
        "allowSyntheticDefaultImports": true,
        "target": "es6",
        "sourceMap": true,
        "outDir": "./dist",
        "baseUrl": "./src",
        "lib": [
          "es6",
          "dom"
        ]
      },
      "include": [
        "src/**/*"
      ]
    }
    

    不使用打字稿的差异

    假设您没有使用打字稿,而是使用问题中提到的 javascript,而不是更改以下内容:

    • 不要在 Dockerfile 中使用 tsc 命令
    • 没有 tsconfig.json 文件
    • 所有文件的名称应为 *.js 而不是 *.ts
    • 显然不要使用任何打字稿打字(在我的示例中没有)

    【讨论】:

      【解决方案3】:

      这在 Lambda Node 14.x 上对我有用 -

      在 app.js 中

      exports.lambdaHandler = async (event, context) => {
        const { App } = await import('./lib/app.mjs');
        return new App(event, context);
      }
      

      然后在 lib/app.mjs -

      class App {
      
        constructor(event, context) {
          return {
            'statusCode': 200,
            'body': JSON.stringify({
              'message': 'hello world'
            })
          }
        }
      } 
      
      export {App}
      

      【讨论】:

      • lib/app.mjs 来自哪里?我在我的项目中找不到它。
      • 您需要创建它。上面的代码是它的最基本版本。
      【解决方案4】:

      如果有人看到这个,就会遇到同样的问题。请参阅 AWS 官方技术支持提供的以下内容:

      “您使用 package.json { "type": "module" } 的说明正确,但目前 Lambda Node.js 14 运行时不支持 ECMAScript 模块”。

      当我听到更多关于何时可以获得支持的信息时,我将发布此帖子的更新。我把这个问题留在这里,以防其他人遇到同样的问题。

      【讨论】:

      • 我也被困在这一点上。解决方法是什么?
      • 没有解决方法,您必须使用 TypeScript 或 Polyfill 之类的。没有使用模块的嵌入式方式。
      • 您可以通过将所有处理程序入口点放在它们自己的文件夹中并使用“空”package.json 来解决此问题。如果您希望 ES 模块与处理程序位于同一文件夹中,则可以使用 .mjs 扩展名。有关工作示例,请参阅github.com/makenew/serverless-nodejs/blob/…
      • 现在 Lambda Node.js 14 支持 ECMAScript 模块! aws.amazon.com/blogs/compute/…
      • @MayNoppadol 根据twitter.com/coderbyheart/status/1486470882008645634 使用 ESM 时,当前不支持层中的模块
      猜你喜欢
      • 2020-02-25
      • 2016-05-28
      • 2020-03-16
      • 2018-12-18
      • 2019-11-01
      • 1970-01-01
      • 2019-07-16
      • 2020-12-19
      相关资源
      最近更新 更多