Skip to content

Commit

Permalink
Add option to prevent code for external live bindings (#3010)
Browse files Browse the repository at this point in the history
* Add option to not generate code for external live bindings

* Update documentation

* Increase timeouts
  • Loading branch information
lukastaegert committed Aug 1, 2019
1 parent def3ae2 commit 4a0fa56
Show file tree
Hide file tree
Showing 32 changed files with 341 additions and 68 deletions.
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 };

0 comments on commit 4a0fa56

Please sign in to comment.