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

fix(commonjs): fix interop when importing CJS that is a transpiled ES module from an actual ES module #501

Merged
merged 3 commits into from
Jul 21, 2020
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
module.exports = {
solo: true,
description: 'correctly renames helpers'
};
30 changes: 13 additions & 17 deletions packages/commonjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,33 +46,29 @@
"require"
],
"peerDependencies": {
"rollup": "^2.3.4"
"rollup": "^2.22.0"
},
"dependencies": {
"@rollup/pluginutils": "^3.0.8",
"@rollup/pluginutils": "^3.1.0",
"commondir": "^1.0.1",
"estree-walker": "^1.0.1",
"glob": "^7.1.2",
"is-reference": "^1.1.2",
"magic-string": "^0.25.2",
"resolve": "^1.11.0"
"estree-walker": "^2.0.1",
"glob": "^7.1.6",
"is-reference": "^1.2.1",
"magic-string": "^0.25.7",
"resolve": "^1.17.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/register": "^7.9.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^7.0.0",
"acorn": "^7.1.1",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^8.4.0",
"acorn": "^7.3.1",
"locate-character": "^2.0.5",
"prettier": "^1.19.1",
"require-relative": "^0.8.7",
"rollup": "^2.3.4",
"rollup-plugin-babel": "^4.3.3",
"rollup": "^2.22.0",
"shx": "^0.3.2",
"source-map": "^0.6.1",
"source-map-support": "^0.5.16",
"typescript": "^3.7.4"
"source-map-support": "^0.5.19",
"typescript": "^3.9.7"
},
"ava": {
"files": [
Expand Down
18 changes: 2 additions & 16 deletions packages/commonjs/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import babel from 'rollup-plugin-babel';
import json from '@rollup/plugin-json';

import pkg from './package.json';

export default {
input: 'src/index.js',
plugins: [
json(),
babel({
presets: [
[
'@babel/preset-env',
{
targets: {
node: 6
}
}
]
]
})
],
plugins: [json()],
external: Object.keys(pkg.dependencies).concat(['fs', 'path']),
output: [
{
Expand All @@ -30,6 +15,7 @@ export default {
{
file: pkg.main,
format: 'cjs',
exports: 'auto',
sourcemap: true
}
]
Expand Down
12 changes: 12 additions & 0 deletions packages/commonjs/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ export const HELPERS_ID = '\0commonjsHelpers.js';

// `x['default']` is used instead of `x.default` for backward compatibility with ES3 browsers.
// Minifiers like uglify will usually transpile it back if compatibility with ES3 is not enabled.
// This will no longer be necessary once Rollup switches to ES6 output, likely
// in Rollup 3

// The "hasOwnProperty" call in "getDefaultExportFromCjs" is technically not
// needed, but for consumers that use Rollup's old interop pattern, it will fix
// rollup/rollup-plugin-commonjs#224
// We should remove it once Rollup core and this plugin are updated to not use
// this pattern any more
export const HELPERS = `
export var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

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

export function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
Expand Down
15 changes: 8 additions & 7 deletions packages/commonjs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,10 @@ export default function commonjs(options = {}) {
return null;
}

const moduleInfo = this.getModuleInfo(id);

const transformed = transformCommonjs(
this.parse,
code,
id,
moduleInfo.isEntry,
moduleInfo.importers && moduleInfo.importers.length > 0,
isEsModule,
ignoreGlobal || isEsModule,
ignoreRequire,
Expand Down Expand Up @@ -141,6 +137,11 @@ export default function commonjs(options = {}) {
return code;
}

// commonjsHelpers?commonjsRegister
if (id.startsWith(HELPERS_ID)) {
return `export {${id.split('?')[1]} as default} from '${HELPERS_ID}';`;
}

// generate proxy modules
if (id.endsWith(EXTERNAL_SUFFIX)) {
const actualId = getIdFromExternalProxyId(id);
Expand All @@ -154,7 +155,7 @@ export default function commonjs(options = {}) {
}

if (id === DYNAMIC_PACKAGES_ID) {
let code = `const { commonjsRegister } = require('${HELPERS_ID}');`;
let code = `const commonjsRegister = require('${HELPERS_ID}?commonjsRegister');`;
for (const dir of dynamicRequireModuleDirPaths) {
let entryPoint = 'index.js';

Expand Down Expand Up @@ -187,7 +188,7 @@ export default function commonjs(options = {}) {
const normalizedPath = normalizePathSlashes(actualId);

if (isDynamicJson) {
return `require('${HELPERS_ID}').commonjsRegister(${JSON.stringify(
return `const commonjsRegister = require('${HELPERS_ID}?commonjsRegister');\ncommonjsRegister(${JSON.stringify(
getVirtualPathForDynamicRequirePath(normalizedPath, commonDir)
)}, function (module, exports) {
module.exports = require(${JSON.stringify(normalizedPath)});
Expand All @@ -198,7 +199,7 @@ export default function commonjs(options = {}) {
// Try our best to still export the module fully.
// The commonjs polyfill should take care of circular references.

return `require('${HELPERS_ID}').commonjsRegister(${JSON.stringify(
return `const commonjsRegister = require('${HELPERS_ID}?commonjsRegister');\ncommonjsRegister(${JSON.stringify(
getVirtualPathForDynamicRequirePath(normalizedPath, commonDir)
)}, function (module, exports) {
${readFileSync(normalizedPath, { encoding: 'utf8' })}
Expand Down
2 changes: 1 addition & 1 deletion packages/commonjs/src/resolve-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function getResolveId(extensions) {
importee = getIdFromProxyId(importee);
} else if (importee.startsWith('\0')) {
if (
importee === HELPERS_ID ||
importee.startsWith(HELPERS_ID) ||
importee === DYNAMIC_PACKAGES_ID ||
importee.startsWith(DYNAMIC_JSON_PREFIX)
) {
Expand Down
39 changes: 18 additions & 21 deletions packages/commonjs/src/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ export function transformCommonjs(
parse,
code,
id,
isEntry,
hasImporters,
isEsModule,
ignoreGlobal,
ignoreRequire,
Expand Down Expand Up @@ -577,7 +575,7 @@ export function transformCommonjs(
let wrapperEnd = '';

const moduleName = deconflict(scope, globals, getName(id));
if ((!isEntry || hasImporters) && !isEsModule) {
if (!isEsModule) {
const exportModuleExports = {
str: `export { ${moduleName} as __moduleExports };`,
name: '__moduleExports'
Expand Down Expand Up @@ -647,38 +645,37 @@ export function transformCommonjs(
}
}

if (!hasDefaultExport && (names.length || ((!isEntry || hasImporters) && !isEsModule))) {
if (!(isEsModule || hasDefaultExport)) {
wrapperEnd = `\n\nvar ${moduleName} = {\n${names
.map(({ name, deconflicted }) => `\t${name}: ${deconflicted}`)
.join(',\n')}\n};`;
}
}

const defaultExport = `export default ${moduleName};`;

const named = namedExportDeclarations
.filter((x) => x.name !== 'default' || !hasDefaultExport)
.map((x) => x.str);

const exportBlock = `\n\n${(isEsModule ? [] : [defaultExport])
.concat(named)
.concat(hasDefaultExport ? defaultExportPropertyAssignments : [])
.join('\n')}`;

magicString
.trim()
.prepend(importBlock + wrapperStart)
.trim()
.append(wrapperEnd);

const injectExportBlock =
hasDefaultExport || named.length > 0 || shouldWrap || !isEntry || hasImporters;
if (injectExportBlock) {
magicString.append(exportBlock);
}
const defaultExport =
code.indexOf('__esModule') >= 0
? `export default /*@__PURE__*/${HELPERS_NAME}.getDefaultExportFromCjs(${moduleName});`
: `export default ${moduleName};`;

const named = namedExportDeclarations
.filter((x) => x.name !== 'default' || !hasDefaultExport)
.map((x) => x.str);

magicString.append(
`\n\n${(isEsModule ? [] : [defaultExport])
.concat(named)
.concat(hasDefaultExport ? defaultExportPropertyAssignments : [])
.join('\n')}`
);

code = magicString.toString();
const map = sourceMap ? magicString.generateMap() : null;

return { code, map, syntheticNamedExports: injectExportBlock };
return { code, map, syntheticNamedExports: '__moduleExports' };
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ var foo = function () {
};

var input = 42;

var input_1 = {

};

export default input_1;
export { input_1 as __moduleExports };

This file was deleted.

4 changes: 0 additions & 4 deletions packages/commonjs/test/fixtures/function/__esModule/main.js

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const nodeResolve = require('@rollup/plugin-node-resolve');
const { nodeResolve } = require('@rollup/plugin-node-resolve');

module.exports = {
description: 'returns the same module instance if required directly or via package.json/index.js',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const path = require('path');

const json = require('@rollup/plugin-json');
const nodeResolve = require('@rollup/plugin-node-resolve');
const { nodeResolve } = require('@rollup/plugin-node-resolve');

module.exports = {
input: 'sub/entry.js',
description: 'resolves imports of node_modules from subdirectories',
options: {
input: path.join(__dirname, 'sub/entry.js'),
plugins: [nodeResolve(), json()]
},
pluginOptions: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const json = require('@rollup/plugin-json');
const nodeResolve = require('@rollup/plugin-node-resolve');
const { nodeResolve } = require('@rollup/plugin-node-resolve');

module.exports = {
description: 'resolves imports of directories via package.json files',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const nodeResolve = require('@rollup/plugin-node-resolve');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
Copy link
Contributor

Choose a reason for hiding this comment

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

Why have you switched to importing the named export in favor of the default export?
Is it just to be explicit or didn't it work anymore with the default export?
I know that it exports both, but we need to be sure that nothing has been fatally broken :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

This is not an import, this is a CJS require. Since a recent version, this plugin switched to named export mode. We could also have used the default export at .default but then if we need to use a property anyway, I prefer the named export.


module.exports = {
description: 'resolves imports of directories via index.js',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const nodeResolve = require('@rollup/plugin-node-resolve');
const { nodeResolve } = require('@rollup/plugin-node-resolve');

module.exports = {
options: {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require('path');
const fs = require('fs');

const ID_MAIN = path.join(__dirname, 'main.js');
const ID_OTHER = path.join(__dirname, 'other.js');

module.exports = {
options: {
input: [ID_MAIN, ID_OTHER],
plugins: [
{
load(id) {
if (id === ID_MAIN) {
return new Promise((resolve) =>
setTimeout(() => resolve(fs.readFileSync(ID_MAIN, 'utf8')), 100)
);
}
return null;
}
}
]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo = require('./other.js');

t.is(foo, 'foo');
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'foo';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 42;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const dep = require('./dep.js');

t.is(dep, 42);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Object.defineProperty(exports, '__esModule', { value: true });
exports.default = 'default';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import dep from './dep';

t.is(dep, 'default');
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const path = require('path');

module.exports = {
description:
'Creates correct exports if an ES module that was transpiled to CJS is used as entry point',
options: {
input: [path.join(__dirname, 'main.js'), path.join(__dirname, 'entry.js')]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Object.defineProperty(exports, '__esModule', { value: true });
exports.default = 'default';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as entry from './entry.js';

t.deepEqual(entry, { default: 'default' });
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const path = require('path');

module.exports = {
description:
'Creates correct exports if an ES module that was transpiled to CJS is used as entry point',
options: {
input: [path.join(__dirname, 'main.js'), path.join(__dirname, 'entry.js')]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Object.defineProperty(exports, '__esModule', { value: true });
exports.default = 'default';
exports.named = 'named';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as entry from './entry.js';

t.deepEqual(entry, { default: 'default', named: 'named' });
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const path = require('path');

module.exports = {
description:
'Creates correct exports if an ES module that was transpiled to CJS is used as entry point',
options: {
input: [path.join(__dirname, 'main.js'), path.join(__dirname, 'entry.js')]
}
};