Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does serverless-esbuild support ESM/ES Modules? #483

Open
capndave opened this issue Aug 21, 2023 · 3 comments
Open

Does serverless-esbuild support ESM/ES Modules? #483

capndave opened this issue Aug 21, 2023 · 3 comments

Comments

@capndave
Copy link

capndave commented Aug 21, 2023

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!'
		};
	});
@capndave
Copy link
Author

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?

@timkingman
Copy link

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.

@efstathiosntonas
Copy link

efstathiosntonas commented Sep 14, 2023

@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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants