floydspace / serverless-esbuild

💨 A Serverless framework plugin to bundle JavaScript and TypeScript with extremely fast esbuild
MIT License
442 stars 133 forks source link

Unable to import native nodejs modules using ESM modules #498

Open keyvhinng opened 9 months ago

keyvhinng commented 9 months ago

Describe the bug I want to use ES modules and exclude @aws-sdk libraries from the bundle since those libraries are built-in by default on AWS Lambda

To Reproduce Steps to reproduce the behavior. If you have an example repository that would be even better:

files:

|- index.js
|- package.json
|- serverless.yml
|- ebuild.config.js

package.json :

...
"type": "module",
...

serverless.yml :

org: keyvhinng
app: aws-node-project
service: aws-node-project
frameworkVersion: "3"

provider:
  name: aws
  runtime: nodejs18.x

custom:
  esbuild:
    config: ./esbuild.config.js

functions:
  function1:
    handler: index.handler

plugins:
  - serverless-esbuild

esbuild.config.js :

module.exports = () => {
  return {
    entryPoints: ["index.js"],
    format: "esm",
    platform: "node",
    outputFileExtension: ".mjs",
    banner: {
      js: `
// BANNER START
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);
// BANNER END
`,
    },
    external: [
      "@aws-sdk/client-s3",
      "@aws-sdk/s3-request-presigner",
      "@aws-sdk/lib-storage",
    ],
  };
};

But I got the following error:

Error [ERR_REQUIRE_ESM]: require() of ES Module /.../aws-node-project/esbuild.config.js from /.../aws-node-project/node_modules/serverless-esbuild/dist/index.js not supported.
esbuild.config.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename esbuild.config.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /Users/keyvhinng/Developer/playground/aws/lambda/aws-node-project/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).

Expected behavior Import native nodejs modules correctly

james-yeoman commented 4 months ago

The problem here is that serverless-esbuild is assuming a CJS environment. However, I'm in the process of migrating to ESLint Flat Config, and so I've upgraded from v0.3.3 of esbuild-plugin-eslint to v0.3.8 as a result of https://github.com/robinloeffel/esbuild-plugin-eslint/issues/11 (supporting v9 of eslint). As such, I've encountered the problem of importing ESM in CJS, thanks to the work from v0.3.4 (migration to ESM).

The ideal solution to this, would be to support ESBuild config files in ESM format (esbuild.config.mjs, or "type": "module" in our package.json). This can be done thanks to the async import function, but would require quite a bit of refactoring in order to use, since it introduces async into the mix...

In order to reduce the immediate burden, it might be worth using something like https://www.npmjs.com/package/promise-synchronizer to prevent the need to refactor and test the plugin in order to support async.

In the meantime, I'm going to continue to use .cjs for my esbuild config, and use promise-synchronizer in order to use the dynamic import to use esbuild-plugin-eslint. This isn't ideal (the ideal solution would be the plugin supporting ESM), but it gets me back up and running.