Skip to content

Commit

Permalink
Add dynamicImportFunction option (#2723)
Browse files Browse the repository at this point in the history
* feat: add dynamicImportFunction option

This option allows users of `es` format to rename the `import()`
function, which right now has mixed support in browsers. By allowing
this function to be renamed we can have polyfills (which cannot use the
reserved `import` keyword but could go by a different name like
`importModule`).

* docs: add dynamicImportFunction docs

* feat: warn when using dynamicImportFunction in non-esm format

* Add functional tests and align warning messages

* Fix merge

* Add dynamicImportFunction to two more places and fix sorting
  • Loading branch information
keithamus authored and lukastaegert committed Mar 1, 2019
1 parent 7c141d2 commit 91ce981
Show file tree
Hide file tree
Showing 40 changed files with 206 additions and 46 deletions.
1 change: 1 addition & 0 deletions bin/src/help.md
Expand Up @@ -25,6 +25,7 @@ Basic options:
--chunkFileNames <pattern> Name pattern for emitted secondary chunks
--compact Minify wrapper code
--context <variable> Specify top-level `this` value
--dynamicImportFunction <name> Rename the dynamic `import()` function
--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 @@ -85,6 +85,7 @@ export default { // can be an array (for multiple inputs)

// danger zone
amd,
dynamicImportFunction,
esModule,
exports,
freeze,
Expand Down
1 change: 1 addition & 0 deletions docs/02-javascript-api.md
Expand Up @@ -134,6 +134,7 @@ const outputOptions = {

// danger zone
amd,
dynamicImportFunction,
esModule,
exports,
freeze,
Expand Down
7 changes: 7 additions & 0 deletions docs/999-big-list-of-options.md
Expand Up @@ -667,6 +667,13 @@ Default: `true`

Whether to include the 'use strict' pragma at the top of generated non-ESM bundles. Strictly-speaking, ES modules are *always* in strict mode, so you shouldn't disable this without good reason.
#### output.dynamicImportFunction
Type: `string`<br>
CLI: `--dynamicImportFunction <name>`<br>
Default: `import`
This will rename the dynamic import function to the chosen name when outputting ESM bundles. This is useful for generating code that uses a dynamic import polyfill such as [this one](https://github.com/uupaa/dynamic-import-polyfill).
#### preserveSymlinks
Type: `boolean`<br>
CLI: `--preserveSymlinks`<br>
Expand Down
9 changes: 8 additions & 1 deletion src/Chunk.ts
Expand Up @@ -435,6 +435,7 @@ export default class Chunk {

const renderOptions: RenderOptions = {
compact: options.compact,
dynamicImportFunction: options.dynamicImportFunction,
format: options.format,
freeze: options.freeze !== false,
indent: this.indentString,
Expand Down Expand Up @@ -545,7 +546,13 @@ export default class Chunk {
code: 'INVALID_OPTION',
message: `Invalid format: ${options.format} - valid options are ${Object.keys(
finalisers
).join(', ')}`
).join(', ')}.`
});
}
if (options.dynamicImportFunction && options.format !== 'es') {
this.graph.warn({
code: 'INVALID_OPTION',
message: '"output.dynamicImportFunction" is ignored for formats other than "esm".'
});
}

Expand Down
19 changes: 12 additions & 7 deletions src/ast/nodes/Import.ts
Expand Up @@ -11,10 +11,10 @@ interface DynamicImportMechanism {
right: string;
}

const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImportMechanism => {
switch (format) {
const getDynamicImportMechanism = (options: RenderOptions): DynamicImportMechanism => {
switch (options.format) {
case 'cjs': {
const _ = compact ? '' : ' ';
const _ = options.compact ? '' : ' ';
return {
interopLeft: `Promise.resolve({${_}default:${_}require(`,
interopRight: `)${_}})`,
Expand All @@ -23,9 +23,9 @@ const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImp
};
}
case 'amd': {
const _ = compact ? '' : ' ';
const resolve = compact ? 'c' : 'resolve';
const reject = compact ? 'e' : 'reject';
const _ = options.compact ? '' : ' ';
const resolve = options.compact ? 'c' : 'resolve';
const reject = options.compact ? 'e' : 'reject';
return {
interopLeft: `new Promise(function${_}(${resolve},${_}${reject})${_}{${_}require([`,
interopRight: `],${_}function${_}(m)${_}{${_}${resolve}({${_}default:${_}m${_}})${_}},${_}${reject})${_}})`,
Expand All @@ -38,6 +38,11 @@ const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImp
left: 'module.import(',
right: ')'
};
case 'es':
return {
left: `${options.dynamicImportFunction || 'import'}(`,
right: ')'
};
}
};

Expand Down Expand Up @@ -72,7 +77,7 @@ export default class Import extends NodeBase {
return;
}

const importMechanism = getDynamicImportMechanism(options.format, options.compact);
const importMechanism = getDynamicImportMechanism(options);
if (importMechanism) {
const leftMechanism =
(this.resolutionInterop && importMechanism.interopLeft) || importMechanism.left;
Expand Down
2 changes: 1 addition & 1 deletion src/finalisers/iife.ts
Expand Up @@ -50,7 +50,7 @@ export default function iife(
if (hasExports && !name) {
error({
code: 'INVALID_OPTION',
message: `You must supply output.name for IIFE bundles`
message: `You must supply "output.name" for IIFE bundles.`
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/finalisers/umd.ts
Expand Up @@ -45,7 +45,7 @@ export default function umd(
if (hasExports && !options.name) {
error({
code: 'INVALID_OPTION',
message: 'You must supply output.name for UMD bundles'
message: 'You must supply "output.name" for UMD bundles.'
});
}

Expand Down
33 changes: 17 additions & 16 deletions src/rollup/index.ts
Expand Up @@ -28,14 +28,14 @@ import {
function checkOutputOptions(options: OutputOptions) {
if (<string>options.format === 'es6') {
error({
message: 'The `es6` output format is deprecated – use `esm` instead',
message: 'The "es6" output format is deprecated – use "esm" instead',
url: `https://rollupjs.org/guide/en#output-format`
});
}

if (!options.format) {
error({
message: `You must specify output.format, which can be one of 'amd', 'cjs', 'system', 'esm', 'iife' or 'umd'`,
message: `You must specify "output.format", which can be one of "amd", "cjs", "system", "esm", "iife" or "umd".`,
url: `https://rollupjs.org/guide/en#output-format`
});
}
Expand Down Expand Up @@ -88,37 +88,37 @@ function getInputOptions(rawInputOptions: GenericConfigObject): any {
if (inputOptions.preserveModules)
error({
code: 'INVALID_OPTION',
message: `preserveModules does not support the inlineDynamicImports option.`
message: `"preserveModules" does not support the "inlineDynamicImports" option.`
});
if (inputOptions.manualChunks)
error({
code: 'INVALID_OPTION',
message: '"manualChunks" option is not supported for inlineDynamicImports.'
message: '"manualChunks" option is not supported for "inlineDynamicImports".'
});

if (inputOptions.experimentalOptimizeChunks)
error({
code: 'INVALID_OPTION',
message: '"experimentalOptimizeChunks" option is not supported for inlineDynamicImports.'
message: '"experimentalOptimizeChunks" option is not supported for "inlineDynamicImports".'
});
if (
(inputOptions.input instanceof Array && inputOptions.input.length > 1) ||
(typeof inputOptions.input === 'object' && Object.keys(inputOptions.input).length > 1)
)
error({
code: 'INVALID_OPTION',
message: 'Multiple inputs are not supported for inlineDynamicImports.'
message: 'Multiple inputs are not supported for "inlineDynamicImports".'
});
} else if (inputOptions.preserveModules) {
if (inputOptions.manualChunks)
error({
code: 'INVALID_OPTION',
message: 'preserveModules does not support the manualChunks option.'
message: '"preserveModules" does not support the "manualChunks" option.'
});
if (inputOptions.experimentalOptimizeChunks)
error({
code: 'INVALID_OPTION',
message: 'preserveModules does not support the experimentalOptimizeChunks option.'
message: '"preserveModules" does not support the "experimentalOptimizeChunks" option.'
});
}

Expand Down Expand Up @@ -290,7 +290,7 @@ export default function rollup(rawInputOptions: GenericConfigObject): Promise<Ro
if (!outputOptions || (!outputOptions.dir && !outputOptions.file)) {
error({
code: 'MISSING_OPTION',
message: 'You must specify output.file or output.dir for the build.'
message: 'You must specify "output.file" or "output.dir" for the build.'
});
}
return generate(outputOptions, true).then(bundle => {
Expand All @@ -305,17 +305,17 @@ export default function rollup(rawInputOptions: GenericConfigObject): Promise<Ro
if (outputOptions.sourcemapFile)
error({
code: 'INVALID_OPTION',
message: '"sourcemapFile" is only supported for single-file builds.'
message: '"output.sourcemapFile" is only supported for single-file builds.'
});
if (typeof outputOptions.file === 'string')
error({
code: 'INVALID_OPTION',
message:
'When building multiple chunks, the output.dir option must be used, not output.file.' +
'When building multiple chunks, the "output.dir" option must be used, not "output.file".' +
(typeof inputOptions.input !== 'string' ||
inputOptions.inlineDynamicImports === true
? ''
: ' To inline dynamic imports set the inlineDynamicImports: true option.')
: ' To inline dynamic imports, set the "inlineDynamicImports" option.')
});
}
return Promise.all(
Expand Down Expand Up @@ -436,19 +436,19 @@ function normalizeOutputOptions(
error({
code: 'INVALID_OPTION',
message:
'You must set either output.file for a single-file build or output.dir when generating multiple chunks.'
'You must set either "output.file" for a single-file build or "output.dir" when generating multiple chunks.'
});
if (inputOptions.preserveModules) {
error({
code: 'INVALID_OPTION',
message:
'You must set output.dir instead of output.file when using the preserveModules option.'
'You must set "output.dir" instead of "output.file" when using the "preserveModules" option.'
});
}
if (typeof inputOptions.input === 'object' && !Array.isArray(inputOptions.input))
error({
code: 'INVALID_OPTION',
message: 'You must set output.dir instead of output.file when providing named inputs.'
message: 'You must set "output.dir" instead of "output.file" when providing named inputs.'
});
}

Expand All @@ -461,7 +461,8 @@ function normalizeOutputOptions(
if (typeof outputOptions.file === 'string')
error({
code: 'INVALID_OPTION',
message: 'You must set output.dir instead of output.file when generating multiple chunks.'
message:
'You must set "output.dir" instead of "output.file" when generating multiple chunks.'
});
}

Expand Down
1 change: 1 addition & 0 deletions src/rollup/types.d.ts
Expand Up @@ -294,6 +294,7 @@ export interface OutputOptions {
compact?: boolean;
// only required for bundle.write
dir?: string;
dynamicImportFunction?: string;
entryFileNames?: string;
esModule?: boolean;
exports?: 'default' | 'named' | 'none' | 'auto';
Expand Down
1 change: 1 addition & 0 deletions src/utils/mergeOptions.ts
Expand Up @@ -238,6 +238,7 @@ function getOutputOptions(
chunkFileNames: getOption('chunkFileNames'),
compact: getOption('compact', false),
dir: getOption('dir'),
dynamicImportFunction: getOption('dynamicImportFunction'),
entryFileNames: getOption('entryFileNames'),
esModule: getOption('esModule', true),
exports: getOption('exports'),
Expand Down
1 change: 1 addition & 0 deletions src/utils/renderHelpers.ts
Expand Up @@ -4,6 +4,7 @@ import { treeshakeNode } from './treeshakeNode';

export interface RenderOptions {
compact: boolean;
dynamicImportFunction: string;
format: string;
freeze: boolean;
indent: string;
Expand Down
15 changes: 15 additions & 0 deletions test/chunking-form/samples/dynamic-import-name/_config.js
@@ -0,0 +1,15 @@
module.exports = {
description: 'allows specifying a custom importer function',
options: {
input: 'main.js',
onwarn() {},
plugins: {
resolveDynamicImport() {
return false;
}
},
output: {
dynamicImportFunction: 'foobar'
}
}
};
@@ -0,0 +1,5 @@
define(['require'], function (require) { 'use strict';

new Promise(function (resolve, reject) { require(['./foo.js'], resolve, reject) }).then(result => console.log(result));

});
@@ -0,0 +1,3 @@
'use strict';

Promise.resolve(require('./foo.js')).then(result => console.log(result));
@@ -0,0 +1 @@
foobar('./foo.js').then(result => console.log(result));
@@ -0,0 +1,10 @@
System.register([], function (exports, module) {
'use strict';
return {
execute: function () {

module.import('./foo.js').then(result => console.log(result));

}
};
});
1 change: 1 addition & 0 deletions test/chunking-form/samples/dynamic-import-name/main.js
@@ -0,0 +1 @@
import('./foo.js').then(result => console.log(result));
32 changes: 32 additions & 0 deletions test/function/samples/dynamic-import-name-warn/_config.js
@@ -0,0 +1,32 @@
const assert = require('assert');

module.exports = {
description: 'warns when specifying a custom importer function for formats other than "esm"',
context: {
require(path) {
assert.equal(path, './foo.js');
return 42;
}
},
options: {
input: 'main.js',
plugins: {
resolveDynamicImport() {
return false;
}
},
output: {
dynamicImportFunction: 'myImporter',
format: 'cjs'
}
},
exports(exports) {
return exports.fromFoo.then(value => assert.strictEqual(value, 42));
},
warnings: [
{
code: 'INVALID_OPTION',
message: '"output.dynamicImportFunction" is ignored for formats other than "esm".'
}
]
};
@@ -0,0 +1,5 @@
define(['require'], function (require) { 'use strict';

new Promise(function (resolve, reject) { require(['./foo.js'], resolve, reject) });

});
@@ -0,0 +1,3 @@
'use strict';

Promise.resolve(require('./foo.js'));
@@ -0,0 +1 @@
foobar('./foo.js');
@@ -0,0 +1,10 @@
System.register([], function (exports, module) {
'use strict';
return {
execute: function () {

module.import('./foo.js');

}
};
});
1 change: 1 addition & 0 deletions test/function/samples/dynamic-import-name-warn/main.js
@@ -0,0 +1 @@
export const fromFoo = import('./foo.js');

0 comments on commit 91ce981

Please sign in to comment.