Transpiling ESM files inside node_modules
This is a gigantic hack but it seems to work, until ECMAScript modules (ESM) are more widely supported. Many npm modules are already published as ECMAScript modules but not all apps could import them. Iʼm using it on one of my Gatsby sites.
The idea is to use esbuild to compile ESM files to CommonJS directly inside the node_modules
folder on npm install. Weʼll detect packages that need to be compiled by checking that the type
filed in the package.json
has the module
value.
- Add dependencies:
npm install -D tiny-glob esbuild
- Add a new script,
scripts/esmtocjs.js
const { dirname } = require('path');
const { readFile, writeFile } = require('fs/promises');
const glob = require('tiny-glob');
const { build } = require('esbuild');
/**
* Read and parse a JSON file
*/
const readJson = async (filepath) => {
const file = (await readFile(filepath)).toString();
try {
return JSON.parse(file);
} catch (err) {
console.error(`Cannot parse JSON file: ${filepath}`);
return {};
}
};
async function transpileNodeModules() {
// Get all package.json file inside node_modules, including in subfolders
const allPackages = await glob(`node_modules/**/package.json`);
await allPackages.forEach(async (packageJson) => {
const json = await readJson(packageJson);
// Skip unless the type of the package is ESM
if (!json.name || json.type !== 'module') {
return;
}
console.log(`🦀 Transpiling ${json.name}...`);
const dir = dirname(packageJson);
// Get all .js files unless they are in a nested node_modules folder
const entryPoints = (await glob(`${dir}/**/*.js`)).filter(
(d) => !d.startsWith(`${dir}/node_modules/`)
);
if (entryPoints.length === 0) {
return;
}
// Compile all ESM files to CommonJS
await build({
entryPoints,
outdir: dir,
allowOverwrite: true,
bundle: false,
minify: false,
sourcemap: false,
logLevel: 'info',
platform: 'node',
format: 'cjs',
target: 'node12',
});
// Overwrite the package.json with the type of CommonJS
await writeFile(
packageJson,
JSON.stringify({ ...json, type: 'commonjs' }, null, 2)
);
});
}
transpileNodeModules();
- Add a
postinstall
script to thepackage.json
:
{
"scripts": {
"postinstall": "node scripts/esmtocjs.js"
}
}
Now, every time we run npm install
, all ESM files inside node_modules
will be compiled to CommonJS, so they could be imported by Gatsy or any other tool that doesn't yet support ESM.