When using esbuild to bundle code with --platform=node that depends on npm packages with a mixture of cjs and esm entry points, use the following rule of thumb:
If you're wondering about the difference between cjs and esm, take a look at Node.js: A brief history of cjs, bundlers, and esm.
When executing esbuild bundled code with --platform=node you may have come across one of the following runtime errors:
Error: Dynamic require of "<module_name>" is not supported
Error [ERR_REQUIRE_ESM]: require() of ES Module (...) from (...) not supported. Instead change the require of (...) in (...) to a dynamic import() which is available in all CommonJS modules.
This is because of one of the following limitations:
esbuild has limited transformation capabilities between esm and cjs. Additionally, some scenarios while supported by esbuild are not supported by Node.js itself. As of esbuild@0.24.0, the following table summarizes what's supported:
Format | Scenario | Supported? |
---|---|---|
cjs | static import | Yes |
cjs | dynamic import() | Yes |
cjs | top-level await | No |
cjs | --packages=external of esm entry point | No* |
esm | require() of user modules** | Yes*** |
esm | require() of node:* modules | No**** |
esm | --packages=external of cjs entry point | Yes |
* Supported by esbuild but not by Node.js
** Refers to npm packages or relative path files.
*** User modules are supported with some caveats: __dirname and __filename are not supported without a polyfill.
**** node:* modules can be supported with the same polyfill.
What follows is a detailed description of these scenarios without the use of any polyfills:
We'll use the following example npm packages:
esm module with a static import:
Error: Dynamic require of "<module_name>" is not supported
esm module with a dynamic import() within an async function:
Error [ERR_REQUIRE_ESM]: require() of ES Module (...) from (...) not supported. Instead change the require of (...) in (...) to a dynamic import() which is available in all CommonJS modules.
esm module with a dynamic import() and a top-level await:
import { version } from "node:process"; export function getVersion() { return version; }
cjs module with a require() invocation:
export async function getVersion() { const { version } = await import("node:process"); return version; }
We'll run esbuild with the following arguments:
const { version } = await import("node:process"); export function getVersion() { return version; }
and the following code:
const { version } = require("node:process"); exports.getVersion = function() { return version; }
Produces the following which runs just fine:
esbuild --bundle --format=cjs --platform=node --outfile=bundle.cjs src/main.js
Produces the following which runs just fine:
import { getVersion } from "{npm-package}"; (async () => { // version can be `string` or `Promise<string>` const version = await getVersion(); console.log(version); })();
Notice how the dynamic import() is not transformed to a require() because it's also allowed in cjs modules.
esbuild fails with the following error:
// node_modules/static-import/index.js var import_node_process = require("node:process"); function getVersion() { return import_node_process.version; } // src/main.js (async () => { const version2 = await getVersion(); console.log(version2); })();
Using --packages=external succeeds with all npm packages:
// (...esbuild auto-generated helpers...) // node_modules/dynamic-import/index.js async function getVersion() { const { version } = await import("node:process"); return version; } // src/main.js (async () => { const version = await getVersion(); console.log(version); })();
produces:
[ERROR] Top-level await is currently not supported with the "cjs" output format node_modules/top-level-await/index.js:1:20: 1 │ const { version } = await import("node:process"); ╵ ~~~~~
However, they all fail to run because Nodes.js doesn't allow cjs modules to import esm modules:
esbuild --packages=external --format=cjs --platform=node --outfile=bundle.cjs src/main.js
We'll now run esbuild with the following arguments:
var npm_package_import = require("{npm-package}"); (async () => { const version = await (0, npm_package_import.getVersion)(); console.log(version); })();
src/main.js
/(...)/bundle.cjs:1 var import_static_import = require("static-import"); ^ Error [ERR_REQUIRE_ESM]: require() of ES Module /(...)/node_modules/static-import/index.js from /(...)/bundle.cjs not supported. Instead change the require of index.js in /(...)/bundle.cjs to a dynamic import() which is available in all CommonJS modules.
produces the following which runs just fine:
esbuild --bundle --format=esm --platform=node --outfile=bundle.mjs src/main.js
src/main.js
const { getVersion } = require("static-import"); console.log(getVersion());
produces the following:
// (...esbuild auto-generated helpers...) // node_modules/static-import/index.js var static_import_exports = {}; __export(static_import_exports, { getVersion: () => getVersion }); import { version } from "node:process"; function getVersion() { return version; } var init_static_import = __esm({ "node_modules/static-import/index.js"() { } }); // src/main.js var { getVersion: getVersion2 } = (init_static_import(), __toCommonJS(static_import_exports)); console.log(getVersion2());
However, it fails to run:
import { getVersion } from "require"; console.log(getVersion());
Using --packages=external succeeds with all npm packages, including those with cjs entry points. For example:
// (...esbuild auto-generated helpers...) var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // (...esbuild auto-generated helpers...) // node_modules/require/index.js var require_require = __commonJS({ "node_modules/require/index.js"(exports) { var { version } = __require("node:process"); exports.getVersion = function() { return version; }; } }); // src/main.js var import_require = __toESM(require_require()); console.log((0, import_require.getVersion)());
with:
src/index.js
Error: Dynamic require of "node:process" is not supported
produces a nearly-verbatim output which runs just fine because esm modules can import npm packages with cjs entry points:
esbuild --packages=external --format=esm --platform=node --outfile=bundle.mjs src/main.js
I hope you find this post useful to troubleshoot esbuild outputs now and in the future. Let me know your thoughts below!
The above is the detailed content of Node.js and esbuild: beware of mixing cjs and esm. For more information, please follow other related articles on the PHP Chinese website!