Open brettwillis opened 1 month ago
The __export(is_equal_exports, {
and __esm()
helpers are generated for any CommonJS-style imports
. For example import('./is-equal.mjs')
require('./is-equal')
. This is because they captures the namespace object and esbuild doesn't know if outside code used or not used the object.
Example of namespace capturing in ESM context. // However, esbuild does know the namespace object is PURE, so if you never used the object it will be tree-shaked.
Example of using import()
. // Not only it generates a namespace object, but also it has to generate a promise to preserve the semantic of import()
. The promise obviously is a side-effect, which cannot be tree-shaked.
I see, so it depends on the style of import where the module is imported, not so much the module itself.
Here is a full representation of my scenario, btw.
When you say "namespace object", I think you are referring to the "module object" in the case of ESM and the value of module.exports
in the case of CJS, right?
I still don't quite understand, though, because init_is_equal
is not the module/namespace object, it is a function generated by ESBuild to initialise the module, and esbuild inserts it where the module is require()
'd and only every calls the function. init_is_equal
is not passed around to application code as a reference to the module.
Furthermore, it is a complete no-op. It was generated by esbuild as an empty function so surely esbuild can know it is a no-op and omit it?
When you say "namespace object", I think you are referring to the "module object" in the case of ESM and the value of module.exports in the case of CJS, right?
Yes, my wording might not be very correct.
esbuild bundle works by scanning the code in 2 phases. The first phase is gathering and transforming input files in parallel (that's why it is fast). The second phase is linking these individual modules, performing scope hoisting and generating esm-cjs interop helpers.
I guess when esbuild see your wrapper.cts, it knows:
export = ...
.import { ...
.Therefore, it can prepare such template for wrapper.cts:
// prepare a commonjs module object
var require_wrapper = __commonJS {
// evaluating dependencies to make sure
// any possible side effect evaluates before this module
init_is_equal();
... // rest of the module's content
};
During the second phase, the linker just has to make sure these variables are bound. There's no further side-effect tracking algorithm performed on them.
It might be diffcult to adding such optimization during the linking phase without hurting the speed.
Ok while there is opportunity to optimise the output I understand your explanation, thanks!
ESBuild seems to generate empty "module init" functions even for side-effect free ESM modules.
Given a module
src/utils/is-equal.mts
:What ends up in the generated bundle is (in the root scope):
And then
init_is_equal
is called everywhere thatisEqual
is imported. What is causing this? I would not expectinit_is_equal
to be present. It does this for some modules but not others.Config
platform: 'node', format: 'esm', target: 'node20'
"sideEffects: false"
inpackage.json
) but some dependencies are CJS