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

Deconflict global variables used inside format-specific code #2880

Merged
merged 6 commits into from May 31, 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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
79 changes: 43 additions & 36 deletions src/Chunk.ts
Expand Up @@ -36,7 +36,7 @@ import { makeUnique, renderNamePattern } from './utils/renderNamePattern';
import { RESERVED_NAMES } from './utils/reservedNames';
import { sanitizeFileName } from './utils/sanitizeFileName';
import { timeEnd, timeStart } from './utils/timers';
import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames';
import { INTEROP_DEFAULT_VARIABLE, MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames';

export interface ModuleDeclarations {
dependencies: ModuleDeclarationDependency[];
Expand Down Expand Up @@ -130,7 +130,6 @@ export default class Chunk {
exportMode = 'named';
facadeModule: Module | null = null;
graph: Graph;
hasDynamicImport = false;
id: string = undefined as any;
indentString: string = undefined as any;
isEmpty: boolean;
Expand Down Expand Up @@ -596,16 +595,17 @@ export default class Chunk {
if (!this.renderedSource)
throw new Error('Internal error: Chunk render called before preRender');

const finalise = finalisers[options.format as string];
const format = options.format as string;
const finalise = finalisers[format];
if (!finalise) {
error({
code: 'INVALID_OPTION',
message: `Invalid format: ${options.format} - valid options are ${Object.keys(
finalisers
).join(', ')}.`
message: `Invalid format: ${format} - valid options are ${Object.keys(finalisers).join(
', '
)}.`
});
}
if (options.dynamicImportFunction && options.format !== 'es') {
if (options.dynamicImportFunction && format !== 'es') {
this.graph.warn({
code: 'INVALID_OPTION',
message: '"output.dynamicImportFunction" is ignored for formats other than "esm".'
Expand All @@ -628,37 +628,49 @@ export default class Chunk {
renderedDependency.id = relPath;
}

this.finaliseDynamicImports(options.format as string);
const needsAmdModule = this.finaliseImportMetas(options);
this.finaliseDynamicImports(format);
this.finaliseImportMetas(format);

const hasExports =
this.renderedDeclarations.exports.length !== 0 ||
this.renderedDeclarations.dependencies.some(
dep => (dep.reexports && dep.reexports.length !== 0) as boolean
);

const usesTopLevelAwait = this.orderedModules.some(module => module.usesTopLevelAwait);
if (usesTopLevelAwait && options.format !== 'es' && options.format !== 'system') {
let usesTopLevelAwait = false;
const accessedGlobals = new Set<string>();
for (const module of this.orderedModules) {
if (module.usesTopLevelAwait) {
usesTopLevelAwait = true;
}
const accessedGlobalVariablesByFormat = module.scope.accessedGlobalVariablesByFormat;
const accessedGlobalVariables =
accessedGlobalVariablesByFormat && accessedGlobalVariablesByFormat.get(format);
if (accessedGlobalVariables) {
for (const name of accessedGlobalVariables) {
accessedGlobals.add(name);
}
}
}

if (usesTopLevelAwait && format !== 'es' && format !== 'system') {
error({
code: 'INVALID_TLA_FORMAT',
message: `Module format ${
options.format
} does not support top-level await. Use the "es" or "system" output formats rather.`
message: `Module format ${format} does not support top-level await. Use the "es" or "system" output formats rather.`
});
}

const magicString = finalise(
this.renderedSource,
{
accessedGlobals,
dependencies: this.renderedDeclarations.dependencies,
dynamicImport: this.hasDynamicImport,
exports: this.renderedDeclarations.exports,
hasExports,
indentString: this.indentString,
intro: addons.intro as string,
isEntryModuleFacade: this.facadeModule !== null && this.facadeModule.isEntryPoint,
namedExportsMode: this.exportMode !== 'default',
needsAmdModule,
outro: addons.outro as string,
usesTopLevelAwait,
varOrConst: options.preferConst ? 'const' : 'var',
Expand Down Expand Up @@ -828,25 +840,14 @@ export default class Chunk {
}
}

private finaliseImportMetas(options: OutputOptions): boolean {
let needsAmdModule = false;
private finaliseImportMetas(format: string): void {
for (let i = 0; i < this.orderedModules.length; i++) {
const module = this.orderedModules[i];
const code = this.renderedModuleSources[i];
for (const importMeta of module.importMetas) {
if (
importMeta.renderFinalMechanism(
code,
this.id,
options.format as string,
this.graph.pluginDriver
)
) {
needsAmdModule = true;
}
importMeta.renderFinalMechanism(code, this.id, format, this.graph.pluginDriver);
}
}
return needsAmdModule;
}

private getChunkDependencyDeclarations(options: OutputOptions): ChunkDependencies {
Expand Down Expand Up @@ -1050,9 +1051,18 @@ export default class Chunk {
}
}

const usedNames = Object.create(null);
const usedNames = new Set<string>();
if (this.needsExportsShim) {
usedNames[MISSING_EXPORT_SHIM_VARIABLE] = true;
usedNames.add(MISSING_EXPORT_SHIM_VARIABLE);
}
if (options.format !== 'es') {
usedNames.add('exports');
if (options.format === 'cjs') {
usedNames.add(INTEROP_DEFAULT_VARIABLE);
if (this.exportMode === 'default') {
usedNames.add('module');
}
}
}

deconflictChunk(
Expand Down Expand Up @@ -1098,11 +1108,8 @@ export default class Chunk {
}
}
for (const { node, resolution } of module.dynamicImports) {
if (node.included) {
this.hasDynamicImport = true;
if (resolution instanceof Module && resolution.chunk === this)
resolution.getOrCreateNamespace().include();
}
if (node.included && resolution instanceof Module && resolution.chunk === this)
resolution.getOrCreateNamespace().include();
}
}
}
5 changes: 3 additions & 2 deletions src/Module.ts
Expand Up @@ -618,8 +618,9 @@ export default class Module {
}

traceVariable(name: string): Variable | null {
if (name in this.scope.variables) {
return this.scope.variables[name];
const localVariable = this.scope.variables.get(name);
if (localVariable) {
return localVariable;
}

if (name in this.importDescriptions) {
Expand Down
13 changes: 11 additions & 2 deletions src/ast/nodes/Import.ts
Expand Up @@ -47,6 +47,12 @@ const getDynamicImportMechanism = (options: RenderOptions): DynamicImportMechani
return undefined as any;
};

const accessedImportGlobals = {
amd: ['require'],
cjs: ['require'],
system: ['module']
};

export default class Import extends NodeBase {
parent: CallExpression;
type: NodeType.tImport;
Expand All @@ -55,8 +61,11 @@ export default class Import extends NodeBase {
private resolutionNamespace: string;

include() {
this.included = true;
this.context.includeDynamicImport(this);
if (!this.included) {
this.included = true;
this.context.includeDynamicImport(this);
this.scope.addAccessedGlobalsByFormat(accessedImportGlobals);
}
}

initialise() {
Expand Down
34 changes: 25 additions & 9 deletions src/ast/nodes/MetaProperty.ts
@@ -1,4 +1,5 @@
import MagicString from 'magic-string';
import { accessedFileUrlGlobals, accessedMetaUrlGlobals } from '../../utils/defaultPlugin';
import { dirname, normalize, relative } from '../../utils/path';
import { PluginDriver } from '../../utils/pluginDriver';
import { ObjectPathKey } from '../values';
Expand All @@ -15,10 +16,30 @@ export default class MetaProperty extends NodeBase {
property: Identifier;
type: NodeType.tMetaProperty;

private metaProperty?: string | null;

hasEffectsWhenAccessedAtPath(path: ObjectPathKey[]): boolean {
return path.length > 1;
}

include() {
if (!this.included) {
this.included = true;
const parent = this.parent;
const metaProperty = (this.metaProperty =
parent instanceof MemberExpression && typeof parent.propertyKey === 'string'
? parent.propertyKey
: null);
if (metaProperty) {
if (metaProperty === 'url') {
this.scope.addAccessedGlobalsByFormat(accessedMetaUrlGlobals);
} else if (metaProperty.startsWith(ASSET_PREFIX) || metaProperty.startsWith(CHUNK_PREFIX)) {
this.scope.addAccessedGlobalsByFormat(accessedFileUrlGlobals);
}
}
}
}

initialise() {
if (this.meta.name === 'import') {
this.context.addImportMeta(this);
Expand All @@ -31,13 +52,10 @@ export default class MetaProperty extends NodeBase {
chunkId: string,
format: string,
pluginDriver: PluginDriver
): boolean {
if (!this.included) return false;
): void {
if (!this.included) return;
const parent = this.parent;
const importMetaProperty =
parent instanceof MemberExpression && typeof parent.propertyKey === 'string'
? parent.propertyKey
: null;
const importMetaProperty = this.metaProperty as string | null;

if (
importMetaProperty &&
Expand Down Expand Up @@ -86,7 +104,7 @@ export default class MetaProperty extends NodeBase {
(parent as MemberExpression).end,
replacement
);
return true;
return;
}

const replacement = pluginDriver.hookFirstSync('resolveImportMeta', [
Expand All @@ -103,8 +121,6 @@ export default class MetaProperty extends NodeBase {
} else {
code.overwrite(this.start, this.end, replacement);
}
return true;
}
return false;
}
}