Estoy buscando hacer uso de la importación/exportación nativa que viene con ES6.
Estoy usando contenedores sin servidor dentro de AWS Lambda.
Tengo mi Dockerfile
que se ve así:
FROM public.ecr.aws/lambda/nodejs:14 COPY app ./ RUN npm install CMD [ "app.handler" ]
Luego tengo un directorio de app
con mi código de aplicación. El código app.js
se ve así:
import { success } from './utils/log'; exports.handler = async () => { success('lambda invoked'); const response = 'Hello World'; return { statusCode: 200, body: JSON.stringify(response), isBase64Encoded: false, }; };
Como puede ver en esta línea, import { success } from './utils/log';
Estoy haciendo uso de importaciones nativas.
En mi paquete.json especifico esto:
"type": "module"
Como necesito decirle a mi aplicación que este es un módulo y me gustaría importarlo de forma nativa. Si no especifico esto, obtengo:
{ "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" ] }
Entonces, lo especifico y le digo a Lambda que este es un módulo. Sin embargo, por mi vida, no puedo hacer que funcione, estoy viendo este error:
{ "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)" ] }
Parece que /var/runtime/UserFunction.js
está llamando a mi controlador de aplicaciones como un requisito y un módulo. Sin embargo, no tengo control sobre /var/runtime/UserFunction.js
(¿no lo creo?). En mi Dockerfile
he especificado Node14
. No sé muy bien dónde me he equivocado.
Lo que busco hacer es ejecutar el último y mejor de Node14 (como las importaciones) sin Babel/Transpiler que "inflan" mi código. Si alguien pudiera señalarme en la dirección correcta de dónde me he equivocado, sería apreciado.
Si alguien ve esto, se encuentra con el mismo problema. Consulte lo siguiente del soporte técnico oficial de AWS:
"Sus instrucciones para usar package.json { "type": "module" }
son correctas, pero los módulos ECMAScript no son compatibles con el tiempo de ejecución de Lambda Node.js 14 en este momento".
Publicaré una actualización de esta publicación cuando escuche más sobre cuándo hay soporte disponible. Dejo esta pregunta aquí en caso de que otras personas tengan el mismo problema.
Esto funcionó para mí en Lambda Node 14.x -
en aplicación.js
exports.lambdaHandler = async (event, context) => { const { App } = await import('./lib/app.mjs'); return new App(event, context); }
Y luego en lib/app.mjs -
class App { constructor(event, context) { return { 'statusCode': 200, 'body': JSON.stringify({ 'message': 'hello world' }) } } } export {App}
AWS Lambda no es compatible oficialmente con ESM, pero con las siguientes soluciones funciona sin problemas.
Esta respuesta es un resumen de diferentes soluciones inspiradas en las respuestas/comentarios de Evan Sosenko y Dan Kantor y algunas ideas adicionales mías. Esto incluye un manejo más agradable para proyectos mecanografiados, pero partes de él también se pueden usar para proyectos simples de javascript.
Asumo lo siguiente:
FROM public.ecr.aws/lambda/nodejs:14
FROM public.ecr.aws/lambda/nodejs:14
import { success } from './utils/log';
import AWS from 'aws-sdk';
(También proporciono información para .js simple en lugar de mecanografiado al final)
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"]
Dado que AWS Lambda solo admite commonJS, el punto de entrada de Lambda es un archivo commonJS. Esto se especifica mediante un paquete.json vacío que sobrescribe el paquete.json desde la raíz del proyecto. Como este archivo está vacío, no contiene: "type":"module"
y por defecto todos los archivos en esa carpeta y subcarpetas son commonJS. Un archivo commonJS puede acceder a los archivos ESM si tiene la extensión .mjs, pero como el mecanografiado se compila en .js, uso algunos comandos de Unix para cambiar el nombre de todos los archivos con ".*js" después de llamar a tsc. El handler.js tiene que permanecer como ".js", así que le cambio el nombre de .mjs.
Más sobre ".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
exports.handler = async (event) => { const {App} = await import('../app.js'); const app = new App(); return await app.run(event); };
// 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};
{ "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/**/*" ] }
Supongamos que no está utilizando mecanografiado, pero javascript como se menciona en la pregunta que cambia lo siguiente:
Parece que desde ayer finalmente hay soporte nativo para la sintaxis del módulo ES6 en Node 14 lambdas - consulte https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level -esperar-en-aws-lambda