Skip to content

Commit

Permalink
[v3.0] Keep dynamic imports in CommonJS output (#4647)
Browse files Browse the repository at this point in the history
* Support dynamic imports in cjs output and make default

* Add documentation

* Also use import for unresolvable dynamic imports

* Extend CLI docs
  • Loading branch information
lukastaegert committed Oct 11, 2022
1 parent 516c65a commit 02ec439
Show file tree
Hide file tree
Showing 47 changed files with 148 additions and 251 deletions.
1 change: 1 addition & 0 deletions cli/help.md
Expand Up @@ -29,6 +29,7 @@ Basic options:
--chunkFileNames <pattern> Name pattern for emitted secondary chunks
--compact Minify wrapper code
--context <variable> Specify top-level `this` value
--no-dynamicImportInCjs Write external dynamic CommonJS imports as require
--entryFileNames <pattern> Name pattern for emitted entry chunks
--environment <values> Settings passed to config file (see example)
--no-esModule Do not add __esModule property
Expand Down
1 change: 1 addition & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -360,6 +360,7 @@ Many options have command line equivalents. In those cases, any arguments passed
--chunkFileNames <pattern> Name pattern for emitted secondary chunks
--compact Minify wrapper code
--context <variable> Specify top-level `this` value
--no-dynamicImportInCjs Write external dynamic CommonJS imports as require
--entryFileNames <pattern> Name pattern for emitted entry chunks
--environment <values> Settings passed to config file (see example)
--no-esModule Do not add __esModule property
Expand Down
46 changes: 46 additions & 0 deletions docs/999-big-list-of-options.md
Expand Up @@ -466,6 +466,52 @@ Type: `boolean`<br> CLI: `--compact`/`--no-compact`<br> Default: `false`
This will minify the wrapper code generated by rollup. Note that this does not affect code written by the user. This option is useful when bundling pre-minified code.
#### output.dynamicImportInCjs
Type: `boolean`<br> CLI: `--dynamicImportInCjs`/`--no-dynamicImportInCjs`<br> Default: `true`
While CommonJS output originally supported only `require(…)` to import dependencies, recent Node versions also started to support `import(…)`, which is the only way to import ES modules from CommonJS files. If this option is `true`, which is the default, Rollup will keep external dynamic imports as `import(…)` expressions in CommonJS output. Set this to `false` to rewrite dynamic imports using `require(…)` syntax.
```js
// input
import('external').then(console.log);

// cjs output with dynamicImportInCjs: true or not set
import('external').then(console.log);

// cjs output with dynamicImportInCjs: false
function _interopNamespaceDefault(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);
}

Promise.resolve()
.then(function () {
return /*#__PURE__*/ _interopNamespaceDefault(require('external'));
})
.then(console.log);
```
#### output.entryFileNames
Type: `string | ((chunkInfo: ChunkInfo) => string)`<br> CLI: `--entryFileNames <pattern>`<br> Default: `"[name].js"`
Expand Down
9 changes: 8 additions & 1 deletion src/ast/nodes/ImportExpression.ts
@@ -1,6 +1,6 @@
import type MagicString from 'magic-string';
import ExternalModule from '../../ExternalModule';
import type Module from '../../Module';
import Module from '../../Module';
import type { GetInterop, NormalizedOutputOptions } from '../../rollup/types';
import type { PluginDriver } from '../../utils/PluginDriver';
import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets';
Expand Down Expand Up @@ -134,6 +134,7 @@ export default class ImportExpression extends NodeBase {
{
compact,
dynamicImportFunction,
dynamicImportInCjs,
format,
generatedCode: { arrowFunctions },
interop
Expand All @@ -156,6 +157,12 @@ export default class ImportExpression extends NodeBase {
const hasDynamicTarget = !this.resolution || typeof this.resolution === 'string';
switch (format) {
case 'cjs': {
if (
dynamicImportInCjs &&
(!resolution || typeof resolution === 'string' || resolution instanceof ExternalModule)
) {
return { helper: null, mechanism: null };
}
const helper = getInteropHelper(resolution, exportMode, interop);
let left = `require(`;
let right = `)`;
Expand Down
2 changes: 2 additions & 0 deletions src/rollup/types.d.ts
Expand Up @@ -613,6 +613,7 @@ export interface OutputOptions {
dir?: string;
/** @deprecated Use the "renderDynamicImport" plugin hook instead. */
dynamicImportFunction?: string;
dynamicImportInCjs?: boolean;
entryFileNames?: string | ((chunkInfo: PreRenderedChunk) => string);
esModule?: boolean | 'if-default-prop';
exports?: 'default' | 'named' | 'none' | 'auto';
Expand Down Expand Up @@ -663,6 +664,7 @@ export interface NormalizedOutputOptions {
dir: string | undefined;
/** @deprecated Use the "renderDynamicImport" plugin hook instead. */
dynamicImportFunction: string | undefined;
dynamicImportInCjs: boolean;
entryFileNames: string | ((chunkInfo: PreRenderedChunk) => string);
esModule: boolean | 'if-default-prop';
exports: 'default' | 'named' | 'none' | 'auto';
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/mergeOptions.ts
Expand Up @@ -231,6 +231,7 @@ function mergeOutputOptions(
compact: getOption('compact'),
dir: getOption('dir'),
dynamicImportFunction: getOption('dynamicImportFunction'),
dynamicImportInCjs: getOption('dynamicImportInCjs'),
entryFileNames: getOption('entryFileNames'),
esModule: getOption('esModule'),
exports: getOption('exports'),
Expand Down
1 change: 1 addition & 0 deletions src/utils/options/normalizeOutputOptions.ts
Expand Up @@ -43,6 +43,7 @@ export function normalizeOutputOptions(
compact,
dir: getDir(config, file),
dynamicImportFunction: getDynamicImportFunction(config, inputOptions, format),
dynamicImportInCjs: config.dynamicImportInCjs ?? true,
entryFileNames: getEntryFileNames(config, unsetOptions),
esModule: config.esModule ?? 'if-default-prop',
exports: getExports(config, unsetOptions),
Expand Down
@@ -1,22 +1,5 @@
'use strict';

function _interopNamespaceDefault(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);
}

Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require(
import /* () should not break */ (
/* webpackChunkName: "chunk-name" */
'./foo.js'/*suffix*/)); });
'./foo.js'/*suffix*/);
@@ -1,20 +1,3 @@
'use strict';

function _interopNamespaceDefault(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);
}

Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./foo.js')); }).then(result => console.log(result));
import('./foo.js').then(result => console.log(result));
@@ -1,22 +1,5 @@
'use strict';

function _interopNamespaceDefault(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 dep = 'dep';

(function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require(t)); }); })(dep);
import(dep);
@@ -1,20 +1,3 @@
'use strict';

function _interopNamespaceDefault(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);
}

Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./foo.js')); });
import('./foo.js');
Expand Up @@ -2,23 +2,6 @@

var dep = require('./generated-dep.js');

function _interopNamespaceDefault(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);
}

Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./ext\'ernal')); });
import('./ext\'ernal');

console.log('main', dep.value);
19 changes: 1 addition & 18 deletions test/chunking-form/samples/nested-chunks/_expected/cjs/main1.js
Expand Up @@ -2,24 +2,7 @@

var dep = require('./generated-dep.js');

function _interopNamespaceDefault(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);
}

console.log('main1', dep.value);

Promise.resolve().then(function () { return require('./generated-dynamic.js'); }).then(result => console.log(result));
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./external.js')); }).then(result => console.log(result));
import('./external.js').then(result => console.log(result));
Expand Up @@ -2,24 +2,7 @@

var dep = require('../generated-dep.js');

function _interopNamespaceDefault(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);
}

console.log('main2', dep.value);

Promise.resolve().then(function () { return require('../generated-dynamic.js'); }).then(result => console.log(result));
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('../external.js')); }).then(result => console.log(result));
import('../external.js').then(result => console.log(result));
Expand Up @@ -5,29 +5,12 @@ require('to-indirect-relative-external');
require('direct-absolute-external');
require('to-indirect-absolute-external');

function _interopNamespaceDefault(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);
}

// nested
Promise.resolve().then(function () { return existing; });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./direct-relative-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('to-indirect-relative-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('direct-absolute-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('to-indirect-absolute-external')); });
import('./direct-relative-external');
import('to-indirect-relative-external');
import('direct-absolute-external');
import('to-indirect-absolute-external');

const value = 'existing';
console.log('existing');
Expand All @@ -39,12 +22,12 @@ var existing = /*#__PURE__*/Object.freeze({

//main
Promise.resolve().then(function () { return existing; });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('./direct-relative-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('to-indirect-relative-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('direct-absolute-external')); });
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('to-indirect-absolute-external')); });
import('./direct-relative-external');
import('to-indirect-relative-external');
import('direct-absolute-external');
import('to-indirect-absolute-external');

(function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require(t)); }); })('dynamic-direct-external' + unknown);
Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require('to-dynamic-indirect-external')); });
import('dynamic-direct-external' + unknown);
import('to-dynamic-indirect-external');
Promise.resolve().then(function () { return existing; });
(function (t) { return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefault(require(t)); }); })('my' + 'replacement');
import('my' + 'replacement');
Expand Up @@ -5,7 +5,8 @@ module.exports = {
output: {
globals: { external: 'external' },
name: 'bundle',
interop: 'auto'
interop: 'auto',
dynamicImportInCjs: false
}
}
};
3 changes: 2 additions & 1 deletion test/form/samples/dynamic-import-this-arrow/_config.js
Expand Up @@ -6,7 +6,8 @@ module.exports = {
external: ['input', 'output'],
output: {
generatedCode: { arrowFunctions: true },
name: 'bundle'
name: 'bundle',
dynamicImportInCjs: false
}
}
};
3 changes: 2 additions & 1 deletion test/form/samples/dynamic-import-this-function/_config.js
Expand Up @@ -6,7 +6,8 @@ module.exports = {
external: ['input', 'output'],
output: {
generatedCode: { arrowFunctions: false },
name: 'bundle'
name: 'bundle',
dynamicImportInCjs: false
}
}
};

0 comments on commit 02ec439

Please sign in to comment.