esbuild
esbuild copied to clipboard
should use esm/cjs's native way to import externals (no wrappers needed)
Code Example
Say the we have an input file foobar.js
:
import foo from 'foo';
const bar = require('bar');
module.exports = foo + bar;
What I expect when running esbuild foobar.js --bundle --format=esm --external:foo --external:bar --platform=browser
is:
import foo from "foo";
import bar from "bar";
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// foobar.js
var require_foobar = __commonJS({
"foobar.js"(exports, module) {
module.exports = foo + bar;
}
});
export default require_foobar();
However the actual is:
var __getOwnPropNames = Object.getOwnPropertyNames;
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 new Error('Dynamic require of "' + x + '" is not supported');
});
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// foobar.js
import foo from "foo";
var require_foobar = __commonJS({
"foobar.js"(exports, module) {
var bar = __require("bar");
module.exports = foo + bar;
}
});
export default require_foobar();
Reason
Generally we have two ways to consume the output:
- Consume the output directly (e.g. running the esm output in
- Consume the output via a bundler (e.g. esbuild, webpack, rollup...).
Consume the output directly
When we consume the output directly, the externals should ready available as well. In browsers, we should already specified what the module foo
and the module bar
are using import-maps. Since foo
and bar
must be esm, we don't need a wrapper when improting them. In Node.js, it means foo
and bar
are already installed or them are just nodejs built-in modules. Thought foo
and bar
could be esm or cjs, but we dont' need an import wrapper because Node.js will automatically does it for us.
However in current implementation, esbuild will produce something like var bar = __require("bar");
, which will cause a rumtime error.
Consume the output via a bundler
When consume the output via a bundler, the bundler will actually add those wrappers. So no wrappers needed for esbuild.
This is actually some sort of @rollup/plugin-commonjs does. But that's not trivial, changing the import method will result in choosing different files according to the exports
field in package.json.
While I'd like to see esbuild can do this tiny transform (just get rid of __require
s).
In simple cases, someone has written a script to do that transform: https://github.com/evanw/esbuild/issues/1921#issuecomment-1010490128.
@hyrious
hanging the import method will result in choosing different files according to the exports field in package.json.
That only happens when running the output in a Node.js environment, not for browsers. (most bundlers implenment the Node.js resovle algorithm, but not all of them)
whats the consensus on this ? will there be a built in way to deal with this in the future ?
See my comment here: https://github.com/evanw/esbuild/issues/1921#issuecomment-1771806854