Skip to content

Commit

Permalink
Support exporting live-bindings from other chunks or external depende…
Browse files Browse the repository at this point in the history
…ncies (#2765)

* Implement basic live binding support across chunks and external imports

* Refine cjs finalizer
  • Loading branch information
lukastaegert committed Mar 25, 2019
1 parent a764107 commit e53c734
Show file tree
Hide file tree
Showing 98 changed files with 696 additions and 140 deletions.
6 changes: 5 additions & 1 deletion src/Chunk.ts
Expand Up @@ -68,6 +68,7 @@ export type ChunkExports = {

export interface ReexportSpecifier {
imported: string;
needsLiveBinding: boolean;
reexported: string;
}

Expand Down Expand Up @@ -815,6 +816,7 @@ export default class Chunk {
for (let exportName of this.getExportNames()) {
let exportModule: Chunk | ExternalModule;
let importName: string;
let needsLiveBinding = false;
if (exportName[0] === '*') {
exportModule = <ExternalModule>this.graph.moduleById.get(exportName.substr(1));
importName = exportName = '*';
Expand All @@ -826,14 +828,16 @@ export default class Chunk {
if (module instanceof Module) {
exportModule = module.chunk;
importName = module.chunk.getVariableExportName(variable);
needsLiveBinding = variable.isReassigned;
} else {
exportModule = module;
importName = variable.name;
needsLiveBinding = true;
}
}
let exportDeclaration = reexportDeclarations.get(exportModule);
if (!exportDeclaration) reexportDeclarations.set(exportModule, (exportDeclaration = []));
exportDeclaration.push({ imported: importName, reexported: exportName });
exportDeclaration.push({ imported: importName, reexported: exportName, needsLiveBinding });
}

const importsAsArray = Array.from(this.imports);
Expand Down
7 changes: 4 additions & 3 deletions src/finalisers/amd.ts
Expand Up @@ -13,7 +13,7 @@ export default function amd(
dynamicImport,
exports,
hasExports,
indentString,
indentString: t,
intro,
isEntryModuleFacade,
namedExportsMode,
Expand Down Expand Up @@ -69,15 +69,16 @@ export default function amd(
dependencies,
namedExportsMode,
options.interop,
options.compact
options.compact,
t
);
if (exportBlock) magicString.append(n + n + exportBlock);
if (namedExportsMode && hasExports && isEntryModuleFacade && options.esModule)
magicString.append(`${n}${n}${options.compact ? compactEsModuleExport : esModuleExport}`);
if (outro) magicString.append(outro);

return magicString
.indent(indentString)
.indent(t)
.append(n + n + '});')
.prepend(wrapperStart);
}
109 changes: 41 additions & 68 deletions src/finalisers/cjs.ts
Expand Up @@ -11,6 +11,7 @@ export default function cjs(
dependencies,
exports,
hasExports,
indentString: t,
intro,
isEntryModuleFacade,
namedExportsMode,
Expand All @@ -32,78 +33,49 @@ export default function cjs(
const interop = options.interop !== false;
let importBlock: string;

if (options.compact) {
let definingVariable = false;
importBlock = '';

dependencies.forEach(
({
id,
namedExportsMode,
isChunk,
name,
reexports,
imports,
exportsNames,
exportsDefault
}) => {
if (!reexports && !imports) {
importBlock += definingVariable ? ';' : ',';
definingVariable = false;
importBlock += `require('${id}')`;
} else {
importBlock += definingVariable ? ',' : `${importBlock ? ';' : ''}${varOrConst} `;
definingVariable = true;

if (!interop || isChunk || !exportsDefault || !namedExportsMode) {
importBlock += `${name}=require('${id}')`;
} else {
needsInterop = true;
if (exportsNames)
importBlock += `${name}=require('${id}'),${name}__default=${INTEROP_DEFAULT_VARIABLE}(${name})`;
else importBlock += `${name}=${INTEROP_DEFAULT_VARIABLE}(require('${id}'))`;
}
}
let definingVariable = false;
importBlock = '';
for (const {
id,
namedExportsMode,
isChunk,
name,
reexports,
imports,
exportsNames,
exportsDefault
} of dependencies) {
if (!reexports && !imports) {
if (importBlock) {
importBlock += !options.compact || definingVariable ? `;${n}` : ',';
}
);
if (importBlock.length) importBlock += ';';
} else {
importBlock = dependencies
.map(
({
id,
namedExportsMode,
isChunk,
name,
reexports,
imports,
exportsNames,
exportsDefault
}) => {
if (!reexports && !imports) return `require('${id}');`;

if (!interop || isChunk || !exportsDefault || !namedExportsMode)
return `${varOrConst} ${name} = require('${id}');`;

needsInterop = true;

if (exportsNames)
return (
`${varOrConst} ${name} = require('${id}');` +
`\n${varOrConst} ${name}__default = ${INTEROP_DEFAULT_VARIABLE}(${name});`
);

return `${varOrConst} ${name} = ${INTEROP_DEFAULT_VARIABLE}(require('${id}'));`;
}
)
.join('\n');
definingVariable = false;
importBlock += `require('${id}')`;
} else {
importBlock +=
options.compact && definingVariable ? ',' : `${importBlock ? `;${n}` : ''}${varOrConst} `;
definingVariable = true;

if (!interop || isChunk || !exportsDefault || !namedExportsMode) {
importBlock += `${name}${_}=${_}require('${id}')`;
} else {
needsInterop = true;
if (exportsNames)
importBlock += `${name}${_}=${_}require('${id}')${
options.compact ? ',' : `;\n${varOrConst} `
}${name}__default${_}=${_}${INTEROP_DEFAULT_VARIABLE}(${name})`;
else importBlock += `${name}${_}=${_}${INTEROP_DEFAULT_VARIABLE}(require('${id}'))`;
}
}
}
if (importBlock) importBlock += ';';

if (needsInterop) {
if (options.compact)
intro += `function ${INTEROP_DEFAULT_VARIABLE}(e){return(e&&(typeof e==='object')&&'default'in e)?e['default']:e}`;
else
intro += `function ${INTEROP_DEFAULT_VARIABLE} (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }\n\n`;
const ex = options.compact ? 'e' : 'ex';
intro +=
`function ${INTEROP_DEFAULT_VARIABLE}${_}(${ex})${_}{${_}return${_}` +
`(${ex}${_}&&${_}(typeof ${ex}${_}===${_}'object')${_}&&${_}'default'${_}in ${ex})${_}` +
`?${_}${ex}['default']${_}:${_}${ex}${options.compact ? '' : '; '}}${n}${n}`;
}

if (importBlock) intro += importBlock + n + n;
Expand All @@ -114,6 +86,7 @@ export default function cjs(
namedExportsMode,
options.interop,
options.compact,
t,
`module.exports${_}=${_}`
);

Expand Down
3 changes: 2 additions & 1 deletion src/finalisers/iife.ts
Expand Up @@ -95,7 +95,8 @@ export default function iife(
dependencies,
namedExportsMode,
options.interop,
options.compact
options.compact,
t
);
if (exportBlock) magicString.append(n + n + exportBlock);
if (outro) magicString.append(outro);
Expand Down
22 changes: 16 additions & 6 deletions src/finalisers/shared/getExportBlock.ts
Expand Up @@ -6,9 +6,11 @@ export default function getExportBlock(
namedExportsMode: boolean,
interop: boolean,
compact: boolean,
t: string,
mechanism = 'return '
) {
const _ = compact ? '' : ' ';
const n = compact ? '' : '\n';

if (!namedExportsMode) {
let local;
Expand Down Expand Up @@ -43,7 +45,13 @@ export default function getExportBlock(
reexports.forEach(specifier => {
if (specifier.reexported === '*') {
if (!compact && exportBlock) exportBlock += '\n';
exportBlock += `Object.keys(${name}).forEach(function${_}(key)${_}{${_}exports[key]${_}=${_}${name}[key];${_}});`;
exportBlock +=
`Object.keys(${name}).forEach(function${_}(key)${_}{${n}` +
`${t}Object.defineProperty(exports,${_}key,${_}{${n}` +
`${t}${t}enumerable:${_}true,${n}` +
`${t}${t}get:${_}function${_}()${_}{${n}` +
`${t}${t}${t}return ${name}[key];${n}` +
`${t}${t}}${n}${t}});${n}});`;
}
});
}
Expand All @@ -55,10 +63,7 @@ export default function getExportBlock(
reexports.forEach(specifier => {
if (specifier.imported === 'default' && !isChunk) {
const exportsNamesOrNamespace =
(imports &&
imports.some(
specifier => specifier.imported === '*' || specifier.imported !== 'default'
)) ||
(imports && imports.some(specifier => specifier.imported !== 'default')) ||
(reexports &&
reexports.some(
specifier => specifier.imported !== 'default' && specifier.imported !== '*'
Expand All @@ -82,7 +87,12 @@ export default function getExportBlock(
specifier.imported === 'default' && !depNamedExportsMode
? name
: `${name}.${specifier.imported}`;
exportBlock += `exports.${specifier.reexported}${_}=${_}${importName};`;
exportBlock += specifier.needsLiveBinding
? `Object.defineProperty(exports,${_}'${specifier.reexported}',${_}{${n}` +
`${t}enumerable:${_}true,${n}` +
`${t}get:${_}function${_}()${_}{${n}` +
`${t}${t}return ${importName};${n}${t}}${n}});`
: `exports.${specifier.reexported}${_}=${_}${importName};`;
} else if (specifier.reexported !== '*') {
if (exportBlock && !compact) exportBlock += '\n';
exportBlock += `exports.${specifier.reexported}${_}=${_}${name};`;
Expand Down
3 changes: 2 additions & 1 deletion src/finalisers/umd.ts
Expand Up @@ -157,7 +157,8 @@ export default function umd(
dependencies,
namedExportsMode,
options.interop,
options.compact
options.compact,
t
);
if (exportBlock) magicString.append(n + n + exportBlock);
if (namedExportsMode && hasExports && options.esModule)
Expand Down
3 changes: 2 additions & 1 deletion test/chunking-form/samples/chunking-reexport/_config.js
@@ -1,6 +1,7 @@
module.exports = {
description: 'chunking star external',
options: {
input: ['main1.js', 'main2.js']
input: ['main1.js', 'main2.js'],
external: ['external']
}
};
Expand Up @@ -2,7 +2,12 @@ define(['exports', 'external', './generated-chunk.js'], function (exports, exter



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

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

Expand Down
Expand Up @@ -2,7 +2,12 @@ define(['exports', 'external', './generated-chunk.js'], function (exports, exter



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

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

Expand Down
Expand Up @@ -7,4 +7,9 @@ require('./generated-chunk.js');



exports.dep = external.asdf;
Object.defineProperty(exports, 'dep', {
enumerable: true,
get: function () {
return external.asdf;
}
});
Expand Up @@ -7,4 +7,9 @@ require('./generated-chunk.js');



exports.dep = external.asdf;
Object.defineProperty(exports, 'dep', {
enumerable: true,
get: function () {
return external.asdf;
}
});
2 changes: 1 addition & 1 deletion test/chunking-form/samples/chunking-source-maps/_config.js
@@ -1,5 +1,5 @@
module.exports = {
description: 'simple chunking',
description: 'source maps',
options: {
input: ['main1.js', 'main2.js'],
output: {
Expand Down
3 changes: 2 additions & 1 deletion test/chunking-form/samples/chunking-star-external/_config.js
@@ -1,6 +1,7 @@
module.exports = {
description: 'chunking star external',
options: {
input: ['main1.js', 'main2.js']
input: ['main1.js', 'main2.js'],
external: ['external1', 'external2', 'starexternal1', 'starexternal2']
}
};
Expand Up @@ -2,8 +2,20 @@ define(['exports', 'starexternal1', 'external1', 'starexternal2', 'external2', '

var main = '1';

Object.keys(starexternal1).forEach(function (key) { exports[key] = starexternal1[key]; });
exports.e = external1.e;
Object.keys(starexternal1).forEach(function (key) {
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return starexternal1[key];
}
});
});
Object.defineProperty(exports, 'e', {
enumerable: true,
get: function () {
return external1.e;
}
});
exports.dep = __chunk_1.dep;
exports.main = main;

Expand Down
Expand Up @@ -2,8 +2,20 @@ define(['exports', 'starexternal2', 'external2', './generated-chunk.js'], functi

var main = '2';

Object.keys(starexternal2).forEach(function (key) { exports[key] = starexternal2[key]; });
exports.e = external2.e;
Object.keys(starexternal2).forEach(function (key) {
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return starexternal2[key];
}
});
});
Object.defineProperty(exports, 'e', {
enumerable: true,
get: function () {
return external2.e;
}
});
exports.dep = __chunk_1.dep;
exports.main = main;

Expand Down

0 comments on commit e53c734

Please sign in to comment.