Skip to content

Commit

Permalink
Deconflict build-in names, resolves #2680
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Feb 14, 2019
1 parent 467975d commit acc18e2
Show file tree
Hide file tree
Showing 48 changed files with 590 additions and 57 deletions.
12 changes: 12 additions & 0 deletions src/Chunk.ts
Expand Up @@ -31,6 +31,7 @@ import { basename, dirname, isAbsolute, normalize, relative, resolve } from './u
import renderChunk from './utils/renderChunk';
import { RenderOptions } from './utils/renderHelpers';
import { makeUnique, renderNamePattern } from './utils/renderNamePattern';
import { ADDITIONAL_NAMES_BY_FORMAT } from './utils/safeName';
import { sanitizeFileName } from './utils/sanitizeFileName';
import { timeEnd, timeStart } from './utils/timers';
import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames';
Expand Down Expand Up @@ -430,10 +431,21 @@ export default class Chunk {
}
}

const additionalNames = ADDITIONAL_NAMES_BY_FORMAT[options.format];
const usedNames = Object.assign(Object.create(null), additionalNames.globals);
const forbiddenNames = Object.assign(Object.create(null), additionalNames.forbidden);
Object.assign(usedNames, forbiddenNames);

if (this.needsExportsShim) {
usedNames[MISSING_EXPORT_SHIM_VARIABLE] = true;
}

deconflictChunk(
this.orderedModules,
this.dependencies,
this.imports,
usedNames,
forbiddenNames,
esmOrSystem,
options.interop !== false,
this.graph.preserveModules
Expand Down
3 changes: 2 additions & 1 deletion src/ast/nodes/ObjectExpression.ts
@@ -1,6 +1,7 @@
import MagicString from 'magic-string';
import { BLANK } from '../../utils/blank';
import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers';
import { NameCollection } from '../../utils/safeName';
import CallOptions from '../CallOptions';
import { DeoptimizableEntity } from '../DeoptimizableEntity';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
Expand Down Expand Up @@ -45,7 +46,7 @@ export default class ObjectExpression extends NodeBase {
private propertyMap: PropertyMap | null;
private unmatchablePropertiesRead: (Property | SpreadElement)[] | null;
private unmatchablePropertiesWrite: Property[] | null;
private deoptimizedPaths: { [key: string]: true };
private deoptimizedPaths: NameCollection;
private hasUnknownDeoptimizedProperty: boolean;
private expressionsToBeDeoptimized: { [key: string]: DeoptimizableEntity[] };

Expand Down
4 changes: 3 additions & 1 deletion src/ast/nodes/shared/pureFunctions.ts
@@ -1,4 +1,6 @@
const pureFunctions: { [name: string]: boolean } = {};
import { NameCollection } from '../../../utils/safeName';

const pureFunctions: NameCollection = {};

const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split(
' '
Expand Down
8 changes: 4 additions & 4 deletions src/ast/scopes/ChildScope.ts
@@ -1,4 +1,4 @@
import { getSafeName } from '../../utils/safeName';
import { getSafeName, NameCollection } from '../../utils/safeName';
import { ExpressionEntity } from '../nodes/shared/Expression';
import Variable from '../variables/Variable';
import Scope from './Scope';
Expand Down Expand Up @@ -28,8 +28,8 @@ export default class ChildScope extends Scope {
return name in this.variables || this.parent.contains(name);
}

deconflict() {
const usedNames: { [name: string]: true } = Object.create(null);
deconflict(forbiddenNames: NameCollection) {
const usedNames: NameCollection = Object.assign(Object.create(null), forbiddenNames);
for (const name of Object.keys(this.accessedOutsideVariables)) {
const variable = this.accessedOutsideVariables[name];
if (variable.included) {
Expand All @@ -43,7 +43,7 @@ export default class ChildScope extends Scope {
}
}
for (const scope of this.children) {
scope.deconflict();
scope.deconflict(forbiddenNames);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/ast/scopes/ModuleScope.ts
@@ -1,4 +1,5 @@
import { AstContext } from '../../Module';
import { NameCollection } from '../../utils/safeName';
import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration';
import { UNDEFINED_EXPRESSION } from '../values';
import ExportDefaultVariable from '../variables/ExportDefaultVariable';
Expand Down Expand Up @@ -36,9 +37,9 @@ export default class ModuleScope extends ChildScope {
}
}

deconflict() {
deconflict(forbiddenNames: NameCollection) {
// all module level variables are already deconflicted in the chunk
for (const scope of this.children) scope.deconflict();
for (const scope of this.children) scope.deconflict(forbiddenNames);
}

findLexicalBoundary() {
Expand Down
8 changes: 5 additions & 3 deletions src/utils/deconflictChunk.ts
Expand Up @@ -3,22 +3,24 @@ import Variable from '../ast/variables/Variable';
import Chunk from '../Chunk';
import ExternalModule from '../ExternalModule';
import Module from '../Module';
import { getSafeName } from './safeName';
import { getSafeName, NameCollection } from './safeName';

export function deconflictChunk(
modules: Module[],
dependencies: (ExternalModule | Chunk)[],
imports: Set<Variable>,
usedNames: NameCollection,
forbiddenNames: NameCollection,
esmOrSystem: boolean,
interop: boolean,
preserveModules: boolean
) {
// register globals
const accessedGlobals: { [name: string]: Variable } = Object.assign(
{},
...modules.map(module => module.scope.accessedOutsideVariables)
);

const usedNames: { [name: string]: true } = Object.create(null);
for (const name of Object.keys(accessedGlobals)) {
const variable = accessedGlobals[name];
if (variable.included) {
Expand Down Expand Up @@ -91,6 +93,6 @@ export function deconflictChunk(
}

for (const module of modules) {
module.scope.deconflict();
module.scope.deconflict(forbiddenNames);
}
}
3 changes: 2 additions & 1 deletion src/utils/pluginDriver.ts
Expand Up @@ -15,6 +15,7 @@ import {
import { createAssetPluginHooks, EmitAsset } from './assetHooks';
import { getRollupDefaultPlugin } from './defaultPlugin';
import { error } from './error';
import { NameCollection } from './safeName';

export interface PluginDriver {
emitAsset: EmitAsset;
Expand Down Expand Up @@ -57,7 +58,7 @@ export function createPluginDriver(
): PluginDriver {
const plugins = [...(options.plugins || []), getRollupDefaultPlugin(options)];
const { emitAsset, getAssetFileName, setAssetSource } = createAssetPluginHooks(graph.assetsById);
const existingPluginKeys: { [key: string]: true } = {};
const existingPluginKeys: NameCollection = {};

let hasLoadersOrTransforms = false;

Expand Down
78 changes: 70 additions & 8 deletions src/utils/safeName.ts
@@ -1,17 +1,79 @@
import { toBase64 } from './base64';
import { INTEROP_DEFAULT_VARIABLE, MISSING_EXPORT_SHIM_VARIABLE } from './variableNames';
import { INTEROP_DEFAULT_VARIABLE } from './variableNames';

const RESERVED_NAMES: { [name: string]: true } = {
[MISSING_EXPORT_SHIM_VARIABLE]: true,
[INTEROP_DEFAULT_VARIABLE]: true,
exports: true,
module: true
export interface NameCollection {
[name: string]: true;
}

const RESERVED_NAMES: NameCollection = Object.assign(Object.create(null), {
await: true,
break: true,
case: true,
catch: true,
class: true,
const: true,
continue: true,
debugger: true,
default: true,
delete: true,
do: true,
else: true,
enum: true,
eval: true,
export: true,
extends: true,
finally: true,
for: true,
function: true,
if: true,
implements: true,
import: true,
in: true,
instanceof: true,
interface: true,
let: true,
new: true,
null: true,
package: true,
private: true,
protected: true,
public: true,
return: true,
static: true,
super: true,
switch: true,
throw: true,
try: true,
typeof: true,
undefined: true,
var: true,
void: true,
while: true,
with: true,
yield: true
});

const NONE = Object.create(null);
const EXPORTS: NameCollection = { exports: true };

export const ADDITIONAL_NAMES_BY_FORMAT: {
[format: string]: { globals: NameCollection; forbidden: NameCollection };
} = {
cjs: {
globals: { exports: true, module: true, [INTEROP_DEFAULT_VARIABLE]: true },
forbidden: RESERVED_NAMES
},
iife: { globals: EXPORTS, forbidden: RESERVED_NAMES },
amd: { globals: EXPORTS, forbidden: RESERVED_NAMES },
umd: { globals: EXPORTS, forbidden: RESERVED_NAMES },
system: { globals: NONE, forbidden: Object.assign(Object.create(null), RESERVED_NAMES, EXPORTS) },
es: { globals: NONE, forbidden: RESERVED_NAMES }
};

export function getSafeName(baseName: string, usedNames: { [name: string]: true }): string {
export function getSafeName(baseName: string, usedNames: NameCollection): string {
let safeName = baseName;
let count = 1;
while (usedNames[safeName] || RESERVED_NAMES[safeName]) {
while (usedNames[safeName]) {
safeName = `${baseName}$${toBase64(count++)}`;
}
usedNames[safeName] = true;
Expand Down
3 changes: 2 additions & 1 deletion src/utils/traverseStaticDependencies.ts
@@ -1,12 +1,13 @@
import ExternalModule from '../ExternalModule';
import Module from '../Module';
import { NameCollection } from './safeName';

export function visitStaticModuleDependencies(
baseModule: Module | ExternalModule,
areDependenciesSkipped: (module: Module | ExternalModule) => boolean
) {
const modules = [baseModule];
const visitedModules: { [id: string]: true } = {};
const visitedModules: NameCollection = {};
for (const module of modules) {
if (areDependenciesSkipped(module) || module instanceof ExternalModule) continue;
for (const dependency of module.dependencies) {
Expand Down
@@ -1,2 +1,2 @@
import {missingFn as _missingExportShim$1,x}from'./dep.js';_missingExportShim$1();
x(_missingExportShim$1);
import {missingFn as _missingExportShim,x}from'./dep.js';_missingExportShim();
x(_missingExportShim);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -1,4 +1,4 @@
import { missing1 as _missingExportShim$1 } from './dep1.js';
import { missing2 as _missingExportShim$2, previousShimmedExport as _missingExportShim$3 } from './dep2.js';
import { missing1 as _missingExportShim } from './dep1.js';
import { missing2 as _missingExportShim$1, previousShimmedExport as _missingExportShim$2 } from './dep2.js';

console.log(_missingExportShim$1, _missingExportShim$2, _missingExportShim$3);
console.log(_missingExportShim, _missingExportShim$1, _missingExportShim$2);
@@ -1,16 +1,16 @@
System.register(['./dep1.js', './dep2.js'], function (exports, module) {
'use strict';
var _missingExportShim$1, _missingExportShim$2, _missingExportShim$3;
var _missingExportShim, _missingExportShim$1, _missingExportShim$2;
return {
setters: [function (module) {
_missingExportShim$1 = module.missing1;
_missingExportShim = module.missing1;
}, function (module) {
_missingExportShim$2 = module.missing2;
_missingExportShim$3 = module.previousShimmedExport;
_missingExportShim$1 = module.missing2;
_missingExportShim$2 = module.previousShimmedExport;
}],
execute: function () {

console.log(_missingExportShim$1, _missingExportShim$2, _missingExportShim$3);
console.log(_missingExportShim, _missingExportShim$1, _missingExportShim$2);

}
};
Expand Down
@@ -1,4 +1,4 @@
import { missingFn as _missingExportShim$1, x } from './dep.js';
import { missingFn as _missingExportShim, x } from './dep.js';

_missingExportShim$1();
x(_missingExportShim$1, _missingExportShim$1);
_missingExportShim();
x(_missingExportShim, _missingExportShim);
@@ -1,15 +1,15 @@
System.register(['./dep.js'], function (exports, module) {
'use strict';
var _missingExportShim$1, x;
var _missingExportShim, x;
return {
setters: [function (module) {
_missingExportShim$1 = module.missingFn;
_missingExportShim = module.missingFn;
x = module.x;
}],
execute: function () {

_missingExportShim$1();
x(_missingExportShim$1, _missingExportShim$1);
_missingExportShim();
x(_missingExportShim, _missingExportShim);

}
};
Expand Down
@@ -0,0 +1,4 @@
module.exports = {
description: 'only deconflict "exports" for formats where it is necessary',
options: { output: { name: 'bundle' } }
};
@@ -0,0 +1,29 @@
define(['exports'], function (exports) { 'use strict';

const exports$1 = {
x: 42
};
console.log(exports$1);

function nestedConflict() {
const exports$1 = {
x: 42
};
console.log(exports$1);
exports.x++;
}

function nestedNoConflict() {
const exports = {
x: 42
};
console.log(exports);
}

exports.x = 43;
nestedConflict();
nestedNoConflict();

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

});
@@ -0,0 +1,27 @@
'use strict';

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

const exports$1 = {
x: 42
};
console.log(exports$1);

function nestedConflict() {
const exports$1 = {
x: 42
};
console.log(exports$1);
exports.x++;
}

function nestedNoConflict() {
const exports = {
x: 42
};
console.log(exports);
}

exports.x = 43;
nestedConflict();
nestedNoConflict();

0 comments on commit acc18e2

Please sign in to comment.