[Feature Request]: Support transforming CJS `require` calls into ESM imports
AdrianGonz97 opened this issue · 5 comments
What problem does this feature solve?
Rolldown's current internal implementation of @rollup/plugin-commonjs
is seemingly missing the feature of transforming CJS require
calls into ESM imports when outputting to an esm
format.
At the moment, the require
calls remain in place, making it impossible to run the outputted code in node when the package.json
type is set to type: "module"
:
➜ node dist-rolldown
file:///home/koala/Reproductions/rolldown-missing-transformation/dist-rolldown/index.js:28
const path = require("node:path");
^
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/home/koala/Reproductions/rolldown-missing-transformation/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///home/koala/Reproductions/rolldown-missing-transformation/dist-rolldown/index.js:28:15
at file:///home/koala/Reproductions/rolldown-missing-transformation/dist-rolldown/index.js:9:48
at file:///home/koala/Reproductions/rolldown-missing-transformation/dist-rolldown/index.js:35:31
at ModuleJob.run (node:internal/modules/esm/module_job:234:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5)
Node.js v20.17.0
What does the proposed API look like?
Reproduction: https://github.com/AdrianGonz97/rolldown-missing-cjs-transformation.git
Input:
// entry.ts
import { foo } from "./commonjs.cjs";
console.log(foo);
// commonjs.cjs
const path = require("path");
const foo = path.resolve(".");
module.exports = { foo };
Output:
Rolldown (fails to execute in type: "module"
mode):
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = {exports: {}}).exports, mod), mod.exports);
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
//#region commonjs.cjs
var require_commonjs = __commonJSMin((exports, module) => {
const path = require("node:path");
const foo$1 = path.resolve(".");
module.exports = {foo: foo$1};
});
//#endregion
//#region entry.js
var import_commonjs = __toESM(require_commonjs());
console.log(import_commonjs.foo);
//#endregion
Rollup's @rollup/plugin-commonjs
plugin (works as expected):
import require$$0 from 'path';
const path = require$$0;
const foo = path.resolve(".");
var commonjs = { foo };
console.log(commonjs.foo);
The vite extenal
tests also has the problem, see here. It need to convert require
to import
and using importmap
load it.
Since we are porting esbuild, esbuild also have this behavior and explain the reason in
We met this also in rolldown's self-bootstrapping and used workaround like this
rolldown/packages/rolldown/rolldown.config.mjs
Lines 40 to 43 in 8acc362
Transforming require('ext')
to import 'ext'
will certainly breaks the semantic:
- imported modules has side-effects
- module graph's execution order
Yeah, I've contributed a fair amount to Vite and unfortunately this case is hit in almost all realworld projects. Only about 20% of libraries distribute ESM code, so as soon as you add a dependency to your project, there's a good chance you're hitting this and in a real project it's almost unavoidable
Some cases:
import 'ext'
andrequire('ext')
will fall into different resolve logic of node
Since we have a platform: node
option, I guess it's ok rolldown support injecting automatically
rolldown/packages/rolldown/rolldown.config.mjs
Lines 40 to 43 in 8acc362
if rolldown detects require
while targeting esm
output.
But transforming require
into import
is another matter.