-
Align more closely with node's
default
import behavior for CommonJS (#532)Note: This could be considered a breaking change or a bug fix depending on your point of view.
Importing a CommonJS file into an ESM file does not behave the same everywhere. Historically people compiled their ESM code into CommonJS using Babel before ESM was supported natively. More recently, node has made it possible to use ESM syntax natively but to still import CommonJS files into ESM. These behave differently in many ways but one of the most unfortunate differences is how the
default
export is handled.When you import a normal CommonJS file, both Babel and node agree that the value of
module.exports
should be stored in the ESM import nameddefault
. However, if the CommonJS file used to be an ESM file but was compiled into a CommonJS file, Babel will set the ESM import nameddefault
to the value of the original ESM export nameddefault
while node will continue to set the ESM import nameddefault
to the value ofmodule.exports
. Babel detects if a CommonJS file used to be an ESM file by the presence of theexports.__esModule = true
marker.This is unfortunate because it means there is no general way to make code work with both ecosystems. With Babel the code
import * as someFile from './some-file'
can access the originaldefault
export withsomeFile.default
but with node you need to usesomeFile.default.default
instead. Previously esbuild followed Babel's approach but starting with this release, esbuild will now try to use a blend between the Babel and node approaches.This is the new behavior: importing a CommonJS file will set the
default
import tomodule.exports
in all cases except whenmodule.exports.__esModule && "default" in module.exports
, in which case it will fall through tomodule.exports.default
. In other words: in cases where the default import was previouslyundefined
for CommonJS files whenexports.__esModule === true
, the default import will now bemodule.exports
. This should hopefully keep Babel cross-compiled ESM code mostly working but at the same time now enable some node-oriented code to start working.If you are authoring a library using ESM but shipping it as CommonJS, the best way to avoid this mess is to just never use
default
exports in ESM. Only use named exports with names other thandefault
. -
Fix bug when ESM file has empty exports and is converted to CommonJS (#910)
A file containing the contents
export {}
is still considered to be an ESM file even though it has no exports. However, if a file containing this edge case is converted to CommonJS internally during bundling (e.g. when it is the target ofrequire()
), esbuild failed to mark theexports
symbol from the CommonJS wrapping closure as used even though it is actually needed. This resulted in an output file that crashed when run. Theexports
symbol is now considered used in this case, so the bug has been fixed. -
Avoid introducing
this
for imported function callsIt is possible to import a function exported by a CommonJS file into an ESM file like this:
import {fn} from './cjs-file.js' console.log(fn())
When you do this, esbuild currently transforms your code into something like this:
var cjs_file = __toModule(require("./cjs-file.js")); console.log(cjs_file.fn());
However, doing that changes the value of
this
observed by the exportfn
. The property accesscjs_file.fn
is in the syntactic "call target" position so the value ofthis
becomes the value ofcjs_file
. With this release, esbuild will now use a different syntax in this case to avoid passingcjs_file
asthis
:var cjs_file = __toModule(require("./cjs-file.js")); console.log((0, cjs_file.fn)());
This change in esbuild mirrors a similar recent TypeScript compiler change, and also makes esbuild more consistent with Babel which already does this transformation.