ESM builds output CommonJS format despite being in dist/esm
Opened this issue · 0 comments
Problem
The ESM builds are outputting CommonJS code. You can see an example in decap-cms-core's ESM build which uses CommonJS-style exports despite being in the ESM directory.
Root Cause
By default, @babel/preset-env
transforms ES modules to CommonJS for compatibility. This transformation needs to be explicitly disabled for ESM builds.
The issue likely went unnoticed because most users consume the main bundles through decap-cms
or decap-cms-app
rather than using the ESM versions of individual packages directly.
Additional Problems
While fixing the issue, I noticed that the CommonJS build fails when the ESM build was not run before and the Nx cache is empty as it tries to build decap-cms
and decap-cms-app
in parallel, alltough decap-cms-app
depends on decap-cms
.
This can be fixed by telling Nx to build the dependencies first in nx.json
:
{
"targetDefaults": {
"build:esm": {
"cache": true,
"dependsOn": ["^build:esm"]
},
"build": {
"cache": true,
"dependsOn": ["^build"]
}
}
}
It looks as if someone tried to fix this before by importing from the ESM build directly:
import { DecapCmsApp as CMS } from 'decap-cms-app/dist/esm';
This is problematic as it bypasses the proper package entry points and makes the code fragile.
Impact
- ESM builds aren't truly ESM, making it impossible to use modern ES module features
- Packages advertise ESM support through the 'dist/esm' directory but don't actually provide it
- Clean builds fail unless run through the npm script that enforces sequential execution
- Code contains fragile imports that bypass proper package entry points
Solution
- Configure Babel to preserve ES modules for ESM builds by setting 'modules: false', which tells Babel to keep the original import/export syntax instead of transforming it to require/exports:
function presets() {
return [
['@babel/preset-env', isESM ? { modules: false } : {}],
...
];
}
- Add proper build dependencies in
nx.json
to ensure correct build order regardless of cache state:
{
"targetDefaults": {
"build:esm": {
"cache": true,
"dependsOn": ["^build:esm"]
},
"build": {
"cache": true,
"dependsOn": ["^build"]
}
}
}
This ensures:
- Proper ESM output in dist/esm
- Correct build ordering through explicit dependencies
- Builds work correctly with empty cache
- No need for direct 'dist/esm' imports
I will open a PR with these changes.