Skip to content
Permalink
Browse files
Add option to shim missing exports (#2118)
  • Loading branch information
kellyselden authored and lukastaegert committed Jun 23, 2018
1 parent 8b582b5 commit 8ba819f23d46a5a25015b2225aef39be67d0c06a
Showing 54 changed files with 426 additions and 114 deletions.
@@ -117,6 +117,7 @@ export default class Chunk {
private renderedModuleSources: MagicString[] = undefined;
private renderedSource: MagicStringBundle = undefined;
private renderedSourceLength: number = undefined;
private needsExportsShim: boolean = false;
private renderedDeclarations: {
dependencies: ChunkDependencies;
exports: ChunkExports;
@@ -277,6 +278,7 @@ export default class Chunk {
const namespaceVariables =
(<NamespaceVariable>traced.variable).originals ||
(<ExternalVariable>traced.variable).module.declarations;

for (const importName of Object.keys(namespaceVariables)) {
const original = namespaceVariables[importName];
if (original.included) {
@@ -349,7 +351,7 @@ export default class Chunk {
for (let i = 0; i < module.exportAllModules.length; i++) {
const exportAllModule = module.exportAllModules[i];
// we have to ensure the right export all module
if (exportAllModule.traceExport(name)) {
if (exportAllModule.traceExport(name, true)) {
return this.traceExport(name, exportAllModule);
}
}
@@ -460,6 +462,7 @@ export default class Chunk {
}

private setIdentifierRenderResolutions(options: OutputOptions) {
this.needsExportsShim = false;
const used = Object.create(null);
const esm = options.format === 'es' || options.format === 'system';

@@ -492,12 +495,14 @@ export default class Chunk {

for (const exportName of Object.keys(this.exportNames)) {
const exportVariable = this.exportNames[exportName];
if (exportVariable === this.graph.exportShimVariable) this.needsExportsShim = true;
if (exportVariable && exportVariable.exportName !== exportName)
exportVariable.exportName = exportName;
}

Array.from(this.imports.entries()).forEach(([variable, module]) => {
let safeName;

if (module instanceof ExternalModule) {
if (variable.name === '*') {
safeName = module.name;
@@ -529,36 +534,44 @@ export default class Chunk {
});

this.orderedModules.forEach(module => {
Object.keys(module.scope.variables).forEach(variableName => {
const variable = module.scope.variables[variableName];
if (isExportDefaultVariable(variable) && variable.referencesOriginal()) {
variable.setSafeName(null);
return;
}
if (!(isExportDefaultVariable(variable) && variable.hasId)) {
let safeName;
if (esm || !variable.isReassigned || variable.isId) {
safeName = getSafeName(variable.name);
this.deconflictExportsOfModule(module, getSafeName, esm);
});

this.graph.scope.deshadow(toDeshadow, this.orderedModules.map(module => module.scope));
}

private deconflictExportsOfModule(
module: Module,
getSafeName: (name: string) => string,
esm: boolean
) {
Object.keys(module.scope.variables).forEach(variableName => {
const variable = module.scope.variables[variableName];
if (isExportDefaultVariable(variable) && variable.referencesOriginal()) {
variable.setSafeName(null);
return;
}
if (!(isExportDefaultVariable(variable) && variable.hasId)) {
let safeName;
if (esm || !variable.isReassigned || variable.isId) {
safeName = getSafeName(variable.name);
} else {
const safeExportName = variable.exportName;
if (safeExportName) {
safeName = `exports.${safeExportName}`;
} else {
const safeExportName = variable.exportName;
if (safeExportName) {
safeName = `exports.${safeExportName}`;
} else {
safeName = getSafeName(variable.name);
}
safeName = getSafeName(variable.name);
}
variable.setSafeName(safeName);
}
});

// deconflict reified namespaces
const namespace = module.getOrCreateNamespace();
if (namespace.needsNamespaceBlock) {
namespace.setSafeName(getSafeName(namespace.name));
variable.setSafeName(safeName);
}
});

this.graph.scope.deshadow(toDeshadow, this.orderedModules.map(module => module.scope));
// deconflict reified namespaces
const namespace = module.getOrCreateNamespace();
if (namespace.needsNamespaceBlock) {
namespace.setSafeName(getSafeName(namespace.name));
}
}

private getChunkDependencyDeclarations(
@@ -757,6 +770,7 @@ export default class Chunk {
this.indentString = options.compact ? '' : getIndentString(this.orderedModules, options);

const n = options.compact ? '' : '\n';
const _ = options.compact ? '' : ' ';

this.prepareDynamicImports();

@@ -829,6 +843,12 @@ export default class Chunk {

if (hoistedSource) magicString.prepend(hoistedSource + n + n);

if (this.needsExportsShim) {
magicString.prepend(
`${n}${this.graph.varOrConst} _missingExportShim${_}=${_}void 0;${n}${n}`
);
}

if (options.compact) {
this.renderedSource = magicString;
} else {
@@ -86,7 +86,7 @@ export default class ExternalModule {
});
}

traceExport(name: string): Variable {
traceExport(name: string, _isExportAllSearch?: boolean): Variable {
if (name !== 'default' && name !== '*') this.exportsNames = true;
if (name === '*') this.exportsNamespace = true;

@@ -4,14 +4,14 @@ import injectImportMeta from 'acorn-import-meta/inject';
import { Program } from 'estree';
import GlobalScope from './ast/scopes/GlobalScope';
import { EntityPathTracker } from './ast/utils/EntityPathTracker';
import GlobalVariable from './ast/variables/GlobalVariable';
import Chunk from './Chunk';
import ExternalModule from './ExternalModule';
import Module, { defaultAcornOptions } from './Module';
import {
InputOptions,
IsExternal,
LoadHook,
MissingExportHook,
ModuleJSON,
OutputBundle,
Plugin,
@@ -26,12 +26,11 @@ import {
Watcher
} from './rollup/types';
import { Asset, createAssetPluginHooks, finaliseAsset } from './utils/assetHooks';
import { handleMissingExport, load, makeOnwarn, resolveId } from './utils/defaults';
import { load, makeOnwarn, resolveId } from './utils/defaults';
import ensureArray from './utils/ensureArray';
import { randomUint8Array, Uint8ArrayToHexString, Uint8ArrayXor } from './utils/entryHashing';
import error from './utils/error';
import first from './utils/first';
import firstSync from './utils/first-sync';
import { isRelative, relative, resolve } from './utils/path';
import relativeId, { getAliasName } from './utils/relativeId';
import { timeEnd, timeStart } from './utils/timers';
@@ -49,7 +48,6 @@ export default class Graph {
isExternal: IsExternal;
isPureExternalModule: (id: string) => boolean;
load: LoadHook;
handleMissingExport: MissingExportHook;
moduleById = new Map<string, Module | ExternalModule>();
assetsById = new Map<string, Asset>();
modules: Module[] = [];
@@ -60,6 +58,8 @@ export default class Graph {
resolveDynamicImport: ResolveDynamicImportHook;
resolveId: (id: string, parent: string) => Promise<string | boolean | void>;
scope: GlobalScope;
shimMissingExports: boolean;
exportShimVariable: GlobalVariable;
treeshakingOptions: TreeshakingOptions;
varOrConst: 'var' | 'const';

@@ -152,34 +152,14 @@ export default class Graph {

this.load = first([...loaders, load]);

this.handleMissingExport = firstSync(
this.plugins
.map(plugin => plugin.missingExport)
.filter(Boolean)
.map(missingExport => {
return (
exportName: string,
importingModule: Module,
importedModule: string,
importerStart?: number
) => {
return missingExport.call(
this.pluginContext,
importingModule.id,
exportName,
importedModule,
importerStart
);
};
})
.concat(handleMissingExport)
);
this.shimMissingExports = options.shimMissingExports;

this.scope = new GlobalScope();
// TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
for (const name of ['module', 'exports', '_interopDefault']) {
this.scope.findVariable(name); // creates global variable as side-effect
}
this.exportShimVariable = this.scope.findVariable('_missingExportShim');

this.context = String(options.context);

@@ -30,11 +30,13 @@ import Chunk from './Chunk';
import ExternalModule from './ExternalModule';
import Graph from './Graph';
import { IdMap, ModuleJSON, RawSourceMap, RollupError, RollupWarning } from './rollup/types';
import { handleMissingExport } from './utils/defaults';
import error from './utils/error';
import getCodeFrame from './utils/getCodeFrame';
import { getOriginalLocation } from './utils/getOriginalLocation';
import { makeLegal } from './utils/identifierHelpers';
import { basename, extname } from './utils/path';
import relativeId from './utils/relativeId';
import { RenderOptions } from './utils/renderHelpers';
import { SOURCEMAPPING_URL_RE } from './utils/sourceMappingURL';
import { timeEnd, timeStart } from './utils/timers';
@@ -56,7 +58,7 @@ export interface ImportDescription {
export interface ExportDescription {
localName: string;
identifier?: string;
node: Node;
node?: Node;
}

export interface ReexportDescription {
@@ -670,13 +672,7 @@ export default class Module {
const declaration = otherModule.traceExport(importDeclaration.name);

if (!declaration) {
this.graph.handleMissingExport.call(
this.graph.pluginContext,
importDeclaration.name,
this,
otherModule.id,
importDeclaration.start
);
handleMissingExport(importDeclaration.name, this, otherModule.id, importDeclaration.start);
}

return declaration;
@@ -691,12 +687,12 @@ export default class Module {
const removedExports: string[] = [];
for (const exportName in this.exports) {
const expt = this.exports[exportName];
(expt.node.included ? renderedExports : removedExports).push(exportName);
(expt.node && expt.node.included ? renderedExports : removedExports).push(exportName);
}
return { renderedExports, removedExports };
}

traceExport(name: string): Variable {
traceExport(name: string, isExportAllSearch?: boolean): Variable {
if (name[0] === '*') {
// namespace
if (name.length === 1) {
@@ -714,8 +710,7 @@ export default class Module {
const declaration = reexportDeclaration.module.traceExport(reexportDeclaration.localName);

if (!declaration) {
this.graph.handleMissingExport.call(
this.graph.pluginContext,
handleMissingExport(
reexportDeclaration.localName,
this,
reexportDeclaration.module.id,
@@ -734,13 +729,19 @@ export default class Module {
return declaration;
}

if (name === 'default') return;
if (name !== 'default') {
for (let i = 0; i < this.exportAllModules.length; i += 1) {
const module = this.exportAllModules[i];
const declaration = module.traceExport(name, true);

for (let i = 0; i < this.exportAllModules.length; i += 1) {
const module = this.exportAllModules[i];
const declaration = module.traceExport(name);
if (declaration) return declaration;
}
}

if (declaration) return declaration;
// we don't want to create shims when we are just
// probing export * modules for exports
if (this.graph.shimMissingExports && !isExportAllSearch) {
return this.shimMissingExport(name);
}
}

@@ -757,4 +758,19 @@ export default class Module {
warning.id = this.id;
this.graph.warn(warning);
}

shimMissingExport(name: string) {
// could have already been generated
if (!this.exports[name])
this.graph.warn({
message: `Missing export "${name}" has been shimmed in module ${relativeId(this.id)}.`,
code: 'SHIMMED_EXPORT',
exportName: name,
exporter: relativeId(this.id)
});
this.exports[name] = {
localName: '_missingExportShim'
};
return this.graph.exportShimVariable;
}
}
@@ -11,10 +11,7 @@ export default class GlobalScope extends Scope {
}

findVariable(name: string) {
if (!this.variables[name]) {
this.variables[name] = new GlobalVariable(name);
}

if (!this.variables[name]) return (this.variables[name] = new GlobalVariable(name));
return this.variables[name] as GlobalVariable;
}

@@ -29,5 +29,3 @@ export default class GlobalVariable extends Variable {
);
}
}

GlobalVariable.prototype.isExternal = true;
@@ -147,12 +147,16 @@ export default function system(
if (hoistedExports.length)
wrapperStart += `${t}${t}${t}` + hoistedExports.join(`${n}${t}${t}${t}`) + n + n;

let wrapperEnd = `${n}${n}${t}${t}}`;
wrapperEnd += `${n}${t}}${options.compact ? '' : ';'}`;
wrapperEnd += `${n}});`;

if (intro) magicString.prepend(intro);

if (outro) magicString.append(outro);

return magicString
.indent(`${t}${t}${t}`)
.append(`${n}${n}${t}${t}}${n}${t}}${options.compact ? '' : ';'}${n}});`)
.append(wrapperEnd)
.prepend(wrapperStart);
}

0 comments on commit 8ba819f

Please sign in to comment.