Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/commonjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,18 @@ For these situations, you can change Rollup's behaviour either globally or per m
console.log(dep);
```

- `"auto"`: This is complementary to how [`output.exports`](https://rollupjs.org/guide/en/#outputexports): `"auto"` works in Rollup: If a module has a default export and no named exports, requiring that module returns the default export. In all other cases, the namespace is returned. For external dependencies when using `esmExternals: true`, a corresponding interop helper is added:
- `"auto"`: This is complementary to how [`output.exports`](https://rollupjs.org/guide/en/#outputexports): `"auto"` works in Rollup: If a module has a default export and no named exports, requiring that module returns the default export. In all other cases, the namespace is returned. One exception are namespaces of CommonJS modules imported on Node >= 23: They expose the raw `module.exports` value under a `'module.exports'` key ([nodejs/node#53848](https://github.com/nodejs/node/pull/53848)), and requiring such a module returns that value, just like a real `require()` call would. For external dependencies when using `esmExternals: true`, a corresponding interop helper is added:

```js
// output
import * as dep$1 from 'dep';

function getDefaultExportFromNamespaceIfNotNamed(n) {
if (n && Object.prototype.hasOwnProperty.call(n, 'module.exports')) {
var d = n['default'];
var m = n['module.exports'];
if (d === m || (d !== d && m !== m)) return m;
}
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1
? n['default']
: n;
Expand Down
10 changes: 10 additions & 0 deletions packages/commonjs/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export const IS_WRAPPED_COMMONJS = 'withRequireFunction';
// Minifiers like uglify will usually transpile it back if compatibility with ES3 is not enabled.
// This could be improved by inspecting Rollup's "generatedCode" option

// Since Node 23, the namespace of an imported CommonJS module exposes the raw `module.exports`
// value under a 'module.exports' key whose value is identical to the `default` (https://github.com/nodejs/node/pull/53848).
// `getDefaultExportFromNamespaceIfNotNamed` returns it to match what a real `require()` call would return.
// The same-value check (NaN-safe, hence the self-comparisons) keeps namespaces of ES modulesthat just export a binding named 'module.exports' intact.

const HELPERS = `
export var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

Expand All @@ -38,6 +43,11 @@ export function getDefaultExportFromNamespaceIfPresent (n) {
}

export function getDefaultExportFromNamespaceIfNotNamed (n) {
if (n && Object.prototype.hasOwnProperty.call(n, 'module.exports')) {
var d = n['default'];
var m = n['module.exports'];
if (d === m || (d !== d && m !== m)) return m;
}
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Since Node 23,the namespace of an imported CommonJS module carries a 'module.exports' key,
// so it never has `default` as its only key and the "auto" unwrapping failed.
// https://github.com/nodejs/node/pull/53848
module.exports = {
description:
'returns `module.exports` when requiring an external CommonJS module through a Node >= 23 namespace and requireReturnsDefault is "auto"',
options: {
external: [
'external-cjs-node23-constructor',
'external-cjs-node23-named',
'external-cjs-node23-nan',
'external-esm-module-exports-key'
]
},
pluginOptions: {
requireReturnsDefault: 'auto',
esmExternals: true
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const ExternalCtor = require('external-cjs-node23-constructor');
const namedExports = require('external-cjs-node23-named');
const esmNamespace = require('external-esm-module-exports-key');
const nanExport = require('external-cjs-node23-nan');

t.is(typeof ExternalCtor, 'function', 'unwraps `module.exports` instead of returning the namespace');
t.is(new ExternalCtor('foo').value, 'foo', 'the required value is constructable');
t.deepEqual(
namedExports,
{ foo: 'foo' },
'returns `module.exports` even when named exports were detected'
);
t.deepEqual(
esmNamespace,
{ default: 'bar', foo: 'foo', 'module.exports': 'not-the-default' },
'keeps the namespace of an ES module that exports a binding named "module.exports"'
);
t.is(nanExport, NaN, 'unwraps `module.exports` when the exported value is NaN');

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 99 additions & 0 deletions packages/commonjs/test/snapshots/function.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5010,6 +5010,95 @@ module.exports = main;
}
`;

exports[`import-cjs-node23-require-returns-default-auto 1`] = `
{
"main.js": "'use strict';

var externalCjsNode23Constructor = require('external-cjs-node23-constructor');
var externalCjsNode23Named = require('external-cjs-node23-named');
var externalEsmModuleExportsKey = require('external-esm-module-exports-key');
var externalCjsNode23Nan = require('external-cjs-node23-nan');

function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}

var externalCjsNode23Constructor__namespace = /*#__PURE__*/_interopNamespaceCompat(externalCjsNode23Constructor);
var externalCjsNode23Named__namespace = /*#__PURE__*/_interopNamespaceCompat(externalCjsNode23Named);
var externalEsmModuleExportsKey__namespace = /*#__PURE__*/_interopNamespaceCompat(externalEsmModuleExportsKey);
var externalCjsNode23Nan__namespace = /*#__PURE__*/_interopNamespaceCompat(externalCjsNode23Nan);

function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

function getDefaultExportFromNamespaceIfNotNamed (n) {
if (n && Object.prototype.hasOwnProperty.call(n, 'module.exports')) {
var d = n['default'];
var m = n['module.exports'];
if (d === m || (d !== d && m !== m)) return m;
}
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}

var main$1 = {};

var require$$0 = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(externalCjsNode23Constructor__namespace);

var require$$1 = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(externalCjsNode23Named__namespace);

var require$$2 = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(externalEsmModuleExportsKey__namespace);

var require$$3 = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(externalCjsNode23Nan__namespace);

var hasRequiredMain;

function requireMain () {
if (hasRequiredMain) return main$1;
hasRequiredMain = 1;
const ExternalCtor = require$$0;
const namedExports = require$$1;
const esmNamespace = require$$2;
const nanExport = require$$3;

t.is(typeof ExternalCtor, 'function', 'unwraps \`module.exports\` instead of returning the namespace');
t.is(new ExternalCtor('foo').value, 'foo', 'the required value is constructable');
t.deepEqual(
namedExports,
{ foo: 'foo' },
'returns \`module.exports\` even when named exports were detected'
);
t.deepEqual(
esmNamespace,
{ default: 'bar', foo: 'foo', 'module.exports': 'not-the-default' },
'keeps the namespace of an ES module that exports a binding named "module.exports"'
);
t.is(nanExport, NaN, 'unwraps \`module.exports\` when the exported value is NaN');
return main$1;
}

var mainExports = requireMain();
var main = /*@__PURE__*/getDefaultExportFromCjs(mainExports);

module.exports = main;
",
}
`;

exports[`import-esm-require-returns-default-auto 1`] = `
{
"main.js": "'use strict';
Expand Down Expand Up @@ -5045,6 +5134,11 @@ function getDefaultExportFromCjs (x) {
}

function getDefaultExportFromNamespaceIfNotNamed (n) {
if (n && Object.prototype.hasOwnProperty.call(n, 'module.exports')) {
var d = n['default'];
var m = n['module.exports'];
if (d === m || (d !== d && m !== m)) return m;
}
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}

Expand Down Expand Up @@ -5503,6 +5597,11 @@ function getDefaultExportFromCjs (x) {
}

function getDefaultExportFromNamespaceIfNotNamed (n) {
if (n && Object.prototype.hasOwnProperty.call(n, 'module.exports')) {
var d = n['default'];
var m = n['module.exports'];
if (d === m || (d !== d && m !== m)) return m;
}
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}

Expand Down