Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[commonjs] Add defaultIsModuleExports option to match Node.js behavior #838

Merged
merged 4 commits into from Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/commonjs/README.md
Expand Up @@ -174,6 +174,49 @@ If you set `esmExternals` to `true`, this plugins assumes that all external depe

You can also supply an array of ids to be treated as ES modules, or a function that will be passed each external id to determine if it is an ES module.

### `defaultIsModuleExports`

Type: `boolean | "auto"`<br>
Default: `"auto"`

Controls what is the default export when importing a CommonJS file from an ES module.

- `true`: The value of the default export is `module.exports`. This currently matches the behavior of Node.js when importing a CommonJS file.
```js
// mod.cjs
exports.default = 3;
```
```js
import foo from './mod.cjs';
console.log(foo); // { default: 3 }
```
- `false`: The value of the default export is `exports.default`.
```js
// mod.cjs
exports.default = 3;
```
```js
import foo from './mod.cjs';
console.log(foo); // 3
```
- `"auto"`: The value of the default export is `exports.default` if the CommonJS file has an `exports.__esModule === true` property; otherwise it's `module.exports`. This makes it possible to import
the default export of ES modules compiled to CommonJS as if they were not compiled.
```js
// mod.cjs
exports.default = 3;
```
```js
// mod-compiled.cjs
exports.__esModule = true;
exports.default = 3;
```
```js
import foo from './mod.cjs';
import bar from './mod-compiled.cjs';
console.log(foo); // { default: 3 }
console.log(bar); // 3
```

### `requireReturnsDefault`

Type: `boolean | "namespace" | "auto" | "preferred" | ((id: string) => boolean | "auto" | "preferred")`<br>
Expand Down
37 changes: 24 additions & 13 deletions packages/commonjs/src/generate-exports.js
Expand Up @@ -20,7 +20,8 @@ export function rewriteExportsAndGetExportsBlock(
isRestorableCompiledEsm,
code,
uses,
HELPERS_NAME
HELPERS_NAME,
defaultIsModuleExports
) {
const namedExportDeclarations = [`export { ${moduleName} as __moduleExports };`];
const moduleExportsPropertyAssignments = [];
Expand Down Expand Up @@ -75,19 +76,29 @@ export function rewriteExportsAndGetExportsBlock(

// Generate default export
const defaultExport = [];
if (isRestorableCompiledEsm) {
defaultExport.push(`export default ${deconflictedDefaultExportName || moduleName};`);
} else if (
(wrapped || deconflictedDefaultExportName) &&
(defineCompiledEsmExpressions.length > 0 || code.indexOf('__esModule') >= 0)
) {
// eslint-disable-next-line no-param-reassign
uses.commonjsHelpers = true;
defaultExport.push(
`export default /*@__PURE__*/${HELPERS_NAME}.getDefaultExportFromCjs(${moduleName});`
);
} else {
if (defaultIsModuleExports === 'auto') {
if (isRestorableCompiledEsm) {
defaultExport.push(`export default ${deconflictedDefaultExportName || moduleName};`);
} else if (
(wrapped || deconflictedDefaultExportName) &&
(defineCompiledEsmExpressions.length > 0 || code.includes('__esModule'))
) {
// eslint-disable-next-line no-param-reassign
uses.commonjsHelpers = true;
defaultExport.push(
`export default /*@__PURE__*/${HELPERS_NAME}.getDefaultExportFromCjs(${moduleName});`
);
} else {
defaultExport.push(`export default ${moduleName};`);
}
} else if (defaultIsModuleExports === true) {
defaultExport.push(`export default ${moduleName};`);
} else if (defaultIsModuleExports === false) {
if (deconflictedDefaultExportName) {
defaultExport.push(`export default ${deconflictedDefaultExportName};`);
} else {
defaultExport.push(`export default ${moduleName}.default;`);
}
}

return `\n\n${defaultExport
Expand Down
5 changes: 4 additions & 1 deletion packages/commonjs/src/index.js
Expand Up @@ -59,6 +59,8 @@ export default function commonjs(options = {}) {
: Array.isArray(esmExternals)
? ((esmExternalIds = new Set(esmExternals)), (id) => esmExternalIds.has(id))
: () => esmExternals;
const defaultIsModuleExports =
typeof options.defaultIsModuleExports === 'boolean' ? options.defaultIsModuleExports : 'auto';

const { dynamicRequireModuleSet, dynamicRequireModuleDirPaths } = getDynamicRequirePaths(
options.dynamicRequireTargets
Expand Down Expand Up @@ -145,7 +147,8 @@ export default function commonjs(options = {}) {
dynamicRequireModuleSet,
disableWrap,
commonDir,
ast
ast,
defaultIsModuleExports
);
}

Expand Down
6 changes: 4 additions & 2 deletions packages/commonjs/src/transform-commonjs.js
Expand Up @@ -53,7 +53,8 @@ export default function transformCommonjs(
dynamicRequireModuleSet,
disableWrap,
commonDir,
astCache
astCache,
defaultIsModuleExports
) {
const ast = astCache || tryParse(parse, code, id);
const magicString = new MagicString(code);
Expand Down Expand Up @@ -470,7 +471,8 @@ export default function transformCommonjs(
isRestorableCompiledEsm,
code,
uses,
HELPERS_NAME
HELPERS_NAME,
defaultIsModuleExports
);

const importBlock = rewriteRequireExpressionsAndGetImportBlock(
Expand Down
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: 'auto'
}
};
@@ -0,0 +1,3 @@
exports.__esModule = true;
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = /*#__PURE__*/Object.defineProperty({
default: _default,
named: named
}, '__esModule', {value: true});

export default _default;
export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: 'auto'
}
};
@@ -0,0 +1,2 @@
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = {
default: _default,
named: named
};

export default input;
export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: false
}
};
@@ -0,0 +1,3 @@
exports.__esModule = true;
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = /*#__PURE__*/Object.defineProperty({
default: _default,
named: named
}, '__esModule', {value: true});

export default _default;
export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: false
}
};
@@ -0,0 +1,2 @@
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = {
default: _default,
named: named
};

export default _default;
export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: false
}
};
@@ -0,0 +1 @@
exports.named = 3;
@@ -0,0 +1,9 @@
var named = 3;

var input = {
named: named
};

export default input.default;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this one. Should we avoid injecting the default export when it's not present? But then, how do we handle cases where input.default exists but it's not easily statically analyzable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I would lean towards adding it always for now, though maybe as undefined, as this is similar to the current behaviour.

export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: true
}
};
@@ -0,0 +1,3 @@
exports.__esModule = true;
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = /*#__PURE__*/Object.defineProperty({
default: _default,
named: named
}, '__esModule', {value: true});

export default input;
export { input as __moduleExports };
export { named };
@@ -0,0 +1,5 @@
module.exports = {
options: {
defaultIsModuleExports: true
}
};
@@ -0,0 +1,2 @@
exports.default = 2;
exports.named = 3;
@@ -0,0 +1,11 @@
var _default = 2;
var named = 3;

var input = {
default: _default,
named: named
};

export default input;
export { input as __moduleExports };
export { named };