Does serverless-esbuild support ESM/ES Modules?
Opened this issue · 3 comments
My code consists of ES Modules, and I use "type": "module"
in package.json
to make that clear. I do have a few config files (jest, eslint, and prettier) and a script file that are .cjs
extensions. They shouldn't execute when a request is sent to my endpoint, however. Everything works until I tried adding serverless-esbuild
to my project, at which point I get the below error (when I send a request to the endpoint running offline). Am I doing something wrong?
Error
× Unhandled exception in handler 'get'.
× ReferenceError: module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and 'C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\.esbuild\.build\package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///C:/Users/dwthomps/Documents/projects/omniact/genesys-cloud-admin-portal-audit-api/.esbuild/.build/src/routes/get/handler.js:92:99071
at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
at async ESMLoader.import (node:internal/modules/esm/loader:526:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:438:15)
at async _tryAwaitImport (C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\node_modules\.pnpm\serverless-offline@12.0.4_serverless@3.34.0\node_modules\serverless-offline\src\lambda\handler-runner\in-process-runner\aws-lambda-ric\UserFunction.js:215:14)
at async _tryRequire (C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\node_modules\.pnpm\serverless-offline@12.0.4_serverless@3.34.0\node_modules\serverless-offline\src\lambda\handler-runner\in-process-runner\aws-lambda-ric\UserFunction.js:275:24)
at async _loadUserApp (C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\node_modules\.pnpm\serverless-offline@12.0.4_serverless@3.34.0\node_modules\serverless-offline\src\lambda\handler-runner\in-process-runner\aws-lambda-ric\UserFunction.js:304:14)
at async module.exports.load (C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\node_modules\.pnpm\serverless-offline@12.0.4_serverless@3.34.0\node_modules\serverless-offline\src\lambda\handler-runner\in-process-runner\aws-lambda-ric\UserFunction.js:341:21)
at async InProcessRunner.run (file:///C:/Users/dwthomps/Documents/projects/omniact/genesys-cloud-admin-portal-audit-api/node_modules/.pnpm/serverless-offline@12.0.4_serverless@3.34.0/node_modules/serverless-offline/src/lambda/handler-runner/in-process-runner/InProcessRunner.js:41:21)
× module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and 'C:\Users\dwthomps\Documents\projects\omniact\genesys-cloud-admin-portal-audit-api\.esbuild\.build\package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
serverless.yaml
service: genesys-cloud-admin-portal-audit-api
plugins:
- serverless-esbuild
- serverless-dotenv-plugin
- serverless-offline
custom:
serverless-offline:
httpPort: ${env:PORT, 4000}
noTimeout: -t
reloadHandler: true
esbuild:
bundle: true
minify: true
external:
- aws-sdk
provider:
name: aws
runtime: nodejs18.x
lambdaHashingVersion: 20201221
memorySize: 128
timeout: 30
environment:
NODE_OPTIONS: -r ./deploy/openTelemetryProvider.cjs
useDotenv: true
package:
individually: true
functions:
get:
handler: ./src/routes/get/handler.getHandler
events:
- http:
path: /api/audit
method: get
response:
headers:
Content-Type: "'application/json'"
package.json
{
"name": "my-project",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "sls offline --noPrependStageInUrl --reloadHandler",
"dev:cached": "sls offline --allowCache --noPrependStageInUrl",
"dev:debug": "node --inspect ./node_modules/serverless/bin/serverless.js offline",
"sls:invoke": "sls invoke local --function",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:integration": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --testMatch=**/*.integration.test.js --detectOpenHandles",
"test:watch": "jest --watch --verbose",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:debug-watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
"coverage": "jest --coverage",
"format": "npm run lint -- --fix && npm run prettier -- --write",
"prettier": "prettier ./src",
"lint": "eslint ./src",
"openapi:build": "swagger-cli bundle -r --outfile ./docs/openapi.json ./openapi/spec.yaml",
"openapi:serve": "serve -d ./docs",
"package": "rimraf ./dist && sls package --package ./dist",
"release": "standard-version"
},
"standard-version": {},
"engines": {
"node": "16"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@middy/core": "^4.6.0",
"@middy/http-error-handler": "^4.6.0",
"@middy/http-event-normalizer": "^4.6.0",
"@middy/validator": "4.6.0",
"@opentelemetry/instrumentation-mongodb": "^0.21.0",
"@opentelemetry/sdk-node": "0.23.1-alpha.16",
"@types/node": "^20.2.3",
"aws-sdk": "^2.1438.0",
"dotenv": "^16.3.1",
"envalid": "^7.3.1",
"mongodb": "^5.7.0",
"omniact-common-utilities": "^1.0.20"
},
"devDependencies": {
"@apidevtools/swagger-cli": "^4.0.4",
"@shelf/jest-mongodb": "^4.1.7",
"babel-jest": "^29.6.2",
"cross-env": "^7.0.3",
"eslint": "^8.47.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-jsdoc": "^46.4.6",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-unicorn": "^48.0.1",
"jest": "^29.6.2",
"pre-commit": "^1.2.2",
"prettier": "^3.0.2",
"rimraf": "^5.0.1",
"serve": "^14.2.0",
"serverless": "^3.34.0",
"serverless-dotenv-plugin": "^6.0.0",
"serverless-esbuild": "^1.46.0",
"serverless-offline": "^12.0.4"
},
"pre-commit": [
"format",
"test"
],
"lint-staged": {
"*.js": []
}
}
src/routes/get/handler.js
import middy from "@middy/core";
import httpErrorHandler from "@middy/http-error-handler";
import httpEventNormalizer from "@middy/http-event-normalizer";
import validatorMiddleware from "@middy/validator";
import { transpileSchema } from "@middy/validator/transpile";
import { validationErrorJSONFormatter } from "../../middleware/validationErrorJSONFormatter.js";
import { validationSchema } from "./validationSchema.js";
export const getHandler = middy()
.use(httpEventNormalizer()) // parse event json string as object
.use(httpErrorHandler()) // handle common http errors and returns proper responses
.use(validationErrorJSONFormatter()) // format response nicely when there is a validation error
.use(
validatorMiddleware({
eventSchema: transpileSchema(validationSchema, { verbose: true }),
})
)
.handler(async (event, context, { signal }) => {
return {
statusCode: 200,
body: 'hello world!'
};
});
Looking at this a bit closer, the issue seems to be resolved if I remove type: "module"
from the resulting .esbuild/.build/src/routes/get/package.json
. Is there an option to do this in my serverless.yaml
?
I think I had some trouble with this too. My serverless.yml
contains:
custom:
esbuild:
format: esm
outputFileExtension: .mjs
exclude:
- "@aws-sdk/*"
And I end up with a .mjs
file deployed to Lambda.
I also had to add a banner like evanw/esbuild#1921 (comment) , but that may have been to deal with a not-fully-ESM module I was using.
@timkingman how did you implemented banner
? thanks
edit: nvm, found it:
custom:
esbuild:
format: esm
outputFileExtension: .mjs
banner:
js: import { createRequire } from 'module';const require = (await import('node:module')).createRequire(import.meta.url);const __filename = (await import('node:url')).fileURLToPath(import.meta.url);const __dirname = (await import('node:path')).dirname(__filename);