⛳
esbuild に CJS として import してほしいのに ESM として import されちゃうときのプラグイン
import 文を書くと esbuild は該当パッケージを ESM として読みに行く。
どういうことかというと、import 先のパッケージが Conditional Exports でこんな風に書いてる場合、./foo.mjs
をバンドルする。
package.json
{
"exports": {
"import": "./foo.mjs",
"require": "./bar.js"
}
}
基本的にこれは望ましい挙動なんだけど、場合によってはうまくバンドルできなかったりする。
たとえば、foo.mjs
がこんな感じのとき。実装の実態は bar.js
にあるんだけど createRequire
を使って foo.mjs
が ESM の口になってるような感じ。
foo.mjs
imprt module from "node:module";
const require = module.createRequire(import.meta.url);
const bar = require("./bar.js");
export { bar };
こういうのを esbuild でバンドルすると imoport.meta.url
は空オブジェクトになるし、それを解決したとしても createRequire
で作った require
で読み込んだモジュールはそもそもバンドルされない。
なので指定したモジュールを ESM ではなく CJS として読みに行くようにするプラグインがあると便利なのかもしれない。
require
の方のモジュール解決のアルゴリズムに乗っかるので CJS の方を読みに行く。
import path from 'node:path';
import module from 'node:module';
const require = module.createRequire(import.meta.url);
/**
* @param {Array<string>} specifiers
* @returns {import('esbuild').Plugin}
*/
function resolveCjsPlugin(specifiers) {
const filter = new RegExp(specifiers.join('|'));
return {
name: 'resolveCjs',
setup(build) {
build.onResolve({ filter }, (args) => {
return {
path: path.join(require.resolve(args.path)),
};
});
},
};
}
export default resolveCjsPlugin;
本当はこんなことしたくない...
Discussion