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

Add option to prevent code for external live bindings #3010

Merged
merged 3 commits into from Aug 1, 2019
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
2 changes: 2 additions & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -89,6 +89,7 @@ export default { // can be an array (for multiple inputs)
dynamicImportFunction,
esModule,
exports,
externalLiveBindings,
freeze,
indent,
namespaceToStringTag,
Expand Down Expand Up @@ -214,6 +215,7 @@ Many options have command line equivalents. In those cases, any arguments passed
--no-esModule Do not add __esModule property
--exports <mode> Specify export mode (auto, default, named, none)
--extend Extend global variable defined by --name
--no-externalLiveBindings Do not generate code to support live bindings
--footer <text> Code to insert at end of bundle (outside wrapper)
--no-freeze Do not freeze namespace objects
--no-indent Don't indent result
Expand Down
71 changes: 36 additions & 35 deletions docs/02-javascript-api.md
Expand Up @@ -109,41 +109,42 @@ The `outputOptions` object can contain the following properties (see the [big li

```js
const outputOptions = {
// core output options
dir,
file,
format, // required
globals,
name,

// advanced output options
assetFileNames,
banner,
chunkFileNames,
compact,
entryFileNames,
extend,
footer,
interop,
intro,
outro,
paths,
sourcemap,
sourcemapExcludeSources,
sourcemapFile,
sourcemapPathTransform,

// danger zone
amd,
dynamicImportFunction,
esModule,
exports,
freeze,
indent,
namespaceToStringTag,
noConflict,
preferConst,
strict
// core output options
dir,
file,
format, // required
globals,
name,

// advanced output options
assetFileNames,
banner,
chunkFileNames,
compact,
entryFileNames,
extend,
externalLiveBindings,
footer,
interop,
intro,
outro,
paths,
sourcemap,
sourcemapExcludeSources,
sourcemapFile,
sourcemapPathTransform,

// danger zone
amd,
dynamicImportFunction,
esModule,
exports,
freeze,
indent,
namespaceToStringTag,
noConflict,
preferConst,
strict
};
```

Expand Down
39 changes: 39 additions & 0 deletions docs/999-big-list-of-options.md
Expand Up @@ -661,6 +661,45 @@ const yourMethod = require( 'your-lib' ).yourMethod;
const yourLib = require( 'your-lib' ).default;
```

#### output.externalLiveBindings
Type: `boolean`<br>
CLI: `--externalLiveBindings`/`--no-externalLiveBindings`<br>
Default: `true`

When set to `false`, Rollup will not generate code to support live bindings for external imports but instead assume that exports do not change over time. This will enable Rollup to generate more optimized code. Note that this can cause issues when there are circular dependencies involving an external dependency.

This will avoid most cases where Rollup generates getters in the code and can therefore be used to make code IE8 compatible in many cases.

Example:

```js
// input
export {x} from 'external';

// CJS output with externalLiveBindings: true
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var external = require('external');

Object.defineProperty(exports, 'x', {
enumerable: true,
get: function () {
return external.x;
}
});

// CJS output with externalLiveBindings: false
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var external = require('external');

exports.x = external.x;
```

#### output.freeze
Type: `boolean`<br>
CLI: `--freeze`/`--no-freeze`<br>
Expand Down
3 changes: 2 additions & 1 deletion src/Chunk.ts
Expand Up @@ -851,6 +851,7 @@ export default class Chunk {
let importName: string;
let needsLiveBinding = false;
if (exportName[0] === '*') {
needsLiveBinding = options.externalLiveBindings !== false;
exportChunk = this.graph.moduleById.get(exportName.substr(1)) as ExternalModule;
importName = exportName = '*';
} else {
Expand All @@ -865,7 +866,7 @@ export default class Chunk {
} else {
exportChunk = module;
importName = variable.name;
needsLiveBinding = true;
needsLiveBinding = options.externalLiveBindings !== false;
}
}
let reexportDeclaration = reexportDeclarations.get(exportChunk);
Expand Down
2 changes: 1 addition & 1 deletion src/finalisers/amd.ts
Expand Up @@ -75,7 +75,7 @@ export default function amd(
magicString.prepend(interopBlock + n + n);
}
if (accessedGlobals.has(INTEROP_NAMESPACE_VARIABLE)) {
magicString.prepend(getInteropNamespace(_, n, t));
magicString.prepend(getInteropNamespace(_, n, t, options.externalLiveBindings !== false));
}

if (intro) magicString.prepend(intro);
Expand Down
2 changes: 1 addition & 1 deletion src/finalisers/cjs.ts
Expand Up @@ -80,7 +80,7 @@ export default function cjs(
`?${_}${ex}['default']${_}:${_}${ex}${options.compact ? '' : '; '}}${n}${n}`;
}
if (accessedGlobals.has(INTEROP_NAMESPACE_VARIABLE)) {
intro += getInteropNamespace(_, n, t);
intro += getInteropNamespace(_, n, t, options.externalLiveBindings !== false);
}

if (importBlock) intro += importBlock + n + n;
Expand Down
20 changes: 13 additions & 7 deletions src/finalisers/shared/getExportBlock.ts
Expand Up @@ -45,13 +45,19 @@ export default function getExportBlock(
reexports.forEach(specifier => {
if (specifier.reexported === '*') {
if (!compact && exportBlock) exportBlock += '\n';
exportBlock +=
`Object.keys(${name}).forEach(function${_}(k)${_}{${n}` +
`${t}if${_}(k${_}!==${_}'default')${_}Object.defineProperty(exports,${_}k,${_}{${n}` +
`${t}${t}enumerable:${_}true,${n}` +
`${t}${t}get:${_}function${_}()${_}{${n}` +
`${t}${t}${t}return ${name}[k];${n}` +
`${t}${t}}${n}${t}});${n}});`;
if (specifier.needsLiveBinding) {
exportBlock +=
`Object.keys(${name}).forEach(function${_}(k)${_}{${n}` +
`${t}if${_}(k${_}!==${_}'default')${_}Object.defineProperty(exports,${_}k,${_}{${n}` +
`${t}${t}enumerable:${_}true,${n}` +
`${t}${t}get:${_}function${_}()${_}{${n}` +
`${t}${t}${t}return ${name}[k];${n}` +
`${t}${t}}${n}${t}});${n}});`;
} else {
exportBlock +=
`Object.keys(${name}).forEach(function${_}(k)${_}{${n}` +
`${t}if${_}(k${_}!==${_}'default')${_}exports[k]${_}=${_}${name}[k];${n}});`;
}
}
});
}
Expand Down
50 changes: 31 additions & 19 deletions src/finalisers/shared/getInteropNamespace.ts
@@ -1,22 +1,34 @@
import { INTEROP_NAMESPACE_VARIABLE } from '../../utils/variableNames';

export function getInteropNamespace(_: string, n: string, t: string) {
return `function ${INTEROP_NAMESPACE_VARIABLE}(e)${_}{${n}` +
`${t}if${_}(e${_}&&${_}e.__esModule)${_}{${_}return e;${_}}${_}else${_}{${n}` +
`${t}${t}var n${_}=${_}{};${n}` +
`${t}${t}if${_}(e)${_}{${n}` +
`${t}${t}${t}Object.keys(e).forEach(function${_}(k)${_}{${n}` +
`${t}${t}${t}${t}var d${_}=${_}Object.getOwnPropertyDescriptor(e,${_}k);${n}` +
`${t}${t}${t}${t}Object.defineProperty(n,${_}k,${_}d.get${_}?${_}d${_}:${_}{${n}` +
`${t}${t}${t}${t}${t}enumerable:${_}true,${n}` +
`${t}${t}${t}${t}${t}get:${_}function${_}()${_}{${n}` +
`${t}${t}${t}${t}${t}${t}return e[k];${n}` +
`${t}${t}${t}${t}${t}}${n}` +
`${t}${t}${t}${t}});${n}` +
`${t}${t}${t}});${n}` +
`${t}${t}}${n}` +
`${t}${t}n['default']${_}=${_}e;${n}` +
`${t}${t}return n;${n}` +
`${t}}${n}` +
`}${n}${n}`;
function copyPropertyLiveBinding(_: string, n: string, t: string, i: string) {
return (
`${i}var d${_}=${_}Object.getOwnPropertyDescriptor(e,${_}k);${n}` +
`${i}Object.defineProperty(n,${_}k,${_}d.get${_}?${_}d${_}:${_}{${n}` +
`${i}${t}enumerable:${_}true,${n}` +
`${i}${t}get:${_}function${_}()${_}{${n}` +
`${i}${t}${t}return e[k];${n}` +
`${i}${t}}${n}` +
`${i}});${n}`
);
}

function copyPropertyStatic(_: string, n: string, _t: string, i: string) {
return `${i}n[k]${_}=e${_}[k];${n}`;
}

export function getInteropNamespace(_: string, n: string, t: string, liveBindings: boolean) {
return (
`function ${INTEROP_NAMESPACE_VARIABLE}(e)${_}{${n}` +
`${t}if${_}(e${_}&&${_}e.__esModule)${_}{${_}return e;${_}}${_}else${_}{${n}` +
`${t}${t}var n${_}=${_}{};${n}` +
`${t}${t}if${_}(e)${_}{${n}` +
`${t}${t}${t}Object.keys(e).forEach(function${_}(k)${_}{${n}` +
(liveBindings ? copyPropertyLiveBinding : copyPropertyStatic)(_, n, t, t + t + t + t) +
`${t}${t}${t}});${n}` +
`${t}${t}}${n}` +
`${t}${t}n['default']${_}=${_}e;${n}` +
`${t}${t}return n;${n}` +
`${t}}${n}` +
`}${n}${n}`
);
}
1 change: 1 addition & 0 deletions src/rollup/types.d.ts
Expand Up @@ -431,6 +431,7 @@ export interface OutputOptions {
esModule?: boolean;
exports?: 'default' | 'named' | 'none' | 'auto';
extend?: boolean;
externalLiveBindings?: boolean;
// only required for bundle.write
file?: string;
footer?: string | (() => string | Promise<string>);
Expand Down
1 change: 1 addition & 0 deletions src/utils/mergeOptions.ts
Expand Up @@ -269,6 +269,7 @@ function getOutputOptions(
esModule: getOption('esModule', true),
exports: getOption('exports'),
extend: getOption('extend'),
externalLiveBindings: getOption('externalLiveBindings', true),
file: getOption('file'),
footer: getOption('footer'),
format: format === 'esm' ? 'es' : format,
Expand Down
4 changes: 2 additions & 2 deletions test/cli/index.js
Expand Up @@ -25,7 +25,7 @@ runTestSuiteWithSamples(

const command = 'node ' + path.resolve(__dirname, '../../bin') + path.sep + config.command;

const childProcess = exec(command, { timeout: 25000 }, (err, code, stderr) => {
const childProcess = exec(command, { timeout: 40000 }, (err, code, stderr) => {
if (err && !err.killed) {
if (config.error) {
const shouldContinue = config.error(err);
Expand Down Expand Up @@ -118,7 +118,7 @@ runTestSuiteWithSamples(
}
});
}
).timeout(30000);
).timeout(50000);
},
() => process.chdir(cwd)
);
11 changes: 11 additions & 0 deletions test/form/samples/no-external-live-bindings-compact/_config.js
@@ -0,0 +1,11 @@
module.exports = {
description: 'Allows omitting the code that handles external live bindings in compact mode',
options: {
external: () => true,
output: {
compact: true,
externalLiveBindings: false,
name: 'bundle'
}
}
};

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.

@@ -0,0 +1 @@
export{external1}from'external1';export*from'external2';const dynamic = import('external3');export{dynamic};

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.

3 changes: 3 additions & 0 deletions test/form/samples/no-external-live-bindings-compact/main.js
@@ -0,0 +1,3 @@
export { external1 } from 'external1';
export * from 'external2';
export const dynamic = import('external3');
10 changes: 10 additions & 0 deletions test/form/samples/no-external-live-bindings/_config.js
@@ -0,0 +1,10 @@
module.exports = {
description: 'Allows omitting the code that handles external live bindings',
options: {
external: () => true,
output: {
externalLiveBindings: false,
name: 'bundle'
}
}
};
26 changes: 26 additions & 0 deletions test/form/samples/no-external-live-bindings/_expected/amd.js
@@ -0,0 +1,26 @@
define(['require', 'exports', 'external1', 'external2'], function (require, exports, external1, external2) { 'use strict';

function _interopNamespace(e) {
if (e && e.__esModule) { return e; } else {
var n = {};
if (e) {
Object.keys(e).forEach(function (k) {
n[k] =e [k];
});
}
n['default'] = e;
return n;
}
}

const dynamic = new Promise(function (resolve, reject) { require(['external3'], function (m) { resolve(_interopNamespace(m)); }, reject) });

Object.keys(external2).forEach(function (k) {
if (k !== 'default') exports[k] = external2[k];
});
exports.external1 = external1.external1;
exports.dynamic = dynamic;

Object.defineProperty(exports, '__esModule', { value: true });

});
27 changes: 27 additions & 0 deletions test/form/samples/no-external-live-bindings/_expected/cjs.js
@@ -0,0 +1,27 @@
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopNamespace(e) {
if (e && e.__esModule) { return e; } else {
var n = {};
if (e) {
Object.keys(e).forEach(function (k) {
n[k] =e [k];
});
}
n['default'] = e;
return n;
}
}

var external1 = require('external1');
var external2 = require('external2');

const dynamic = new Promise(function (resolve) { resolve(_interopNamespace(require('external3'))); });

Object.keys(external2).forEach(function (k) {
if (k !== 'default') exports[k] = external2[k];
});
exports.external1 = external1.external1;
exports.dynamic = dynamic;
6 changes: 6 additions & 0 deletions test/form/samples/no-external-live-bindings/_expected/es.js
@@ -0,0 +1,6 @@
export { external1 } from 'external1';
export * from 'external2';

const dynamic = import('external3');

export { dynamic };