Skip to content

Commit

Permalink
Properly deduplicate reexported default exports (#2866)
Browse files Browse the repository at this point in the history
* Resolve export default original variables over several stages

* Streamline chunk export assembly

* Replace manual type guards with instanceof

* Start using the "unknown" type for options
  • Loading branch information
lukastaegert committed May 19, 2019
1 parent 0655489 commit 9f84980
Show file tree
Hide file tree
Showing 44 changed files with 279 additions and 182 deletions.
30 changes: 15 additions & 15 deletions src/Chunk.ts
@@ -1,10 +1,9 @@
import sha256 from 'hash.js/lib/hash/sha/256';
import MagicString, { Bundle as MagicStringBundle, SourceMap } from 'magic-string';
import * as NodeType from './ast/nodes/NodeType';
import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration';
import FunctionDeclaration from './ast/nodes/FunctionDeclaration';
import { UNDEFINED_EXPRESSION } from './ast/values';
import ExportDefaultVariable, {
isExportDefaultVariable
} from './ast/variables/ExportDefaultVariable';
import ExportDefaultVariable from './ast/variables/ExportDefaultVariable';
import ExportShimVariable from './ast/variables/ExportShimVariable';
import GlobalVariable from './ast/variables/GlobalVariable';
import LocalVariable from './ast/variables/LocalVariable';
Expand Down Expand Up @@ -706,7 +705,7 @@ export default class Chunk {
this.facadeModule = facadedModule;
facadedModule.facadeChunk = this;
for (const exportName of facadedModule.getAllExports()) {
const tracedVariable = facadedModule.getVariableForExportName(exportName) as Variable;
const tracedVariable = facadedModule.getVariableForExportName(exportName);
this.exports.add(tracedVariable);
this.exportNames[exportName] = tracedVariable;
}
Expand Down Expand Up @@ -878,9 +877,7 @@ export default class Chunk {
const imports: ImportSpecifier[] = [];
for (const variable of this.imports) {
const renderedVariable =
variable instanceof ExportDefaultVariable && variable.referencesOriginal()
? (variable.getOriginalVariable() as Variable)
: variable;
variable instanceof ExportDefaultVariable ? variable.getOriginalVariable() : variable;
if (
(variable.module instanceof Module
? variable.module.chunk === dep
Expand Down Expand Up @@ -955,13 +952,16 @@ export default class Chunk {
if (variable.init === UNDEFINED_EXPRESSION) {
uninitialized = true;
}
variable.declarations.forEach(decl => {
if (decl.type === NodeType.ExportDefaultDeclaration) {
if (decl.declaration.type === NodeType.FunctionDeclaration) hoisted = true;
} else if (decl.parent.type === NodeType.FunctionDeclaration) {
for (const declaration of variable.declarations) {
if (
declaration.parent instanceof FunctionDeclaration ||
(declaration instanceof ExportDefaultDeclaration &&
declaration.declaration instanceof FunctionDeclaration)
) {
hoisted = true;
break;
}
});
}
} else if (variable instanceof GlobalVariable) {
hoisted = true;
}
Expand Down Expand Up @@ -1052,7 +1052,7 @@ export default class Chunk {
options.format !== 'system' &&
exportVariable.isReassigned &&
!exportVariable.isId &&
(!isExportDefaultVariable(exportVariable) || !exportVariable.hasId)
!(exportVariable instanceof ExportDefaultVariable && exportVariable.hasId)
) {
exportVariable.setRenderNames('exports', exportName);
} else {
Expand Down Expand Up @@ -1089,7 +1089,7 @@ export default class Chunk {
if (module.getOrCreateNamespace().included) {
for (const reexportName of Object.keys(module.reexports)) {
const reexport = module.reexports[reexportName];
const variable = reexport.module.getVariableForExportName(reexport.localName) as Variable;
const variable = reexport.module.getVariableForExportName(reexport.localName);
if ((variable.module as Module).chunk !== this) {
this.imports.add(variable);
if (variable.module instanceof Module) {
Expand Down
2 changes: 1 addition & 1 deletion src/Graph.ts
Expand Up @@ -164,7 +164,7 @@ export default class Graph {
this.getModuleContext = () => this.context;
}

this.onwarn = options.onwarn || makeOnwarn();
this.onwarn = (options.onwarn as WarningHandler) || makeOnwarn();
this.acornOptions = options.acorn || {};
const acornPluginsToInject = [];

Expand Down
27 changes: 13 additions & 14 deletions src/Module.ts
Expand Up @@ -5,22 +5,20 @@ import MagicString from 'magic-string';
import extractAssignedNames from 'rollup-pluginutils/src/extractAssignedNames';
import ClassDeclaration from './ast/nodes/ClassDeclaration';
import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration';
import ExportDefaultDeclaration, {
isExportDefaultDeclaration
} from './ast/nodes/ExportDefaultDeclaration';
import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration';
import ExportNamedDeclaration from './ast/nodes/ExportNamedDeclaration';
import FunctionDeclaration from './ast/nodes/FunctionDeclaration';
import Identifier from './ast/nodes/Identifier';
import Import from './ast/nodes/Import';
import ImportDeclaration from './ast/nodes/ImportDeclaration';
import ImportSpecifier from './ast/nodes/ImportSpecifier';
import { nodeConstructors } from './ast/nodes/index';
import { isLiteral } from './ast/nodes/Literal';
import Literal from './ast/nodes/Literal';
import MetaProperty from './ast/nodes/MetaProperty';
import * as NodeType from './ast/nodes/NodeType';
import Program from './ast/nodes/Program';
import { Node, NodeBase } from './ast/nodes/shared/Node';
import { isTemplateLiteral } from './ast/nodes/TemplateLiteral';
import TemplateLiteral from './ast/nodes/TemplateLiteral';
import VariableDeclaration from './ast/nodes/VariableDeclaration';
import ModuleScope from './ast/scopes/ModuleScope';
import { EntityPathTracker } from './ast/utils/EntityPathTracker';
Expand Down Expand Up @@ -69,7 +67,7 @@ export interface ImportDescription {
}

export interface ExportDescription {
identifier?: string;
identifier: string | null;
localName: string;
}

Expand Down Expand Up @@ -163,6 +161,7 @@ function handleMissingExport(
}

const MISSING_EXPORT_SHIM_DESCRIPTION: ExportDescription = {
identifier: null,
localName: MISSING_EXPORT_SHIM_VARIABLE
};

Expand Down Expand Up @@ -292,11 +291,11 @@ export default class Module {
getDynamicImportExpressions(): (string | Node)[] {
return this.dynamicImports.map(({ node }) => {
const importArgument = node.parent.arguments[0];
if (isTemplateLiteral(importArgument)) {
if (importArgument instanceof TemplateLiteral) {
if (importArgument.expressions.length === 0 && importArgument.quasis.length === 1) {
return importArgument.quasis[0].value.cooked;
}
} else if (isLiteral(importArgument)) {
} else if (importArgument instanceof Literal) {
if (typeof importArgument.value === 'string') {
return importArgument.value;
}
Expand Down Expand Up @@ -354,7 +353,7 @@ export default class Module {
getTransitiveDependencies() {
return this.dependencies.concat(
this.getReexports().map(
exportName => (this.getVariableForExportName(exportName)).module as Module
exportName => this.getVariableForExportName(exportName).module as Module
)
);
}
Expand Down Expand Up @@ -671,7 +670,7 @@ export default class Module {
};
}
}
} else if (isExportDefaultDeclaration(node)) {
} else if (node instanceof ExportDefaultDeclaration) {
// export default function foo () {}
// export default foo;
// export default 42;
Expand All @@ -686,7 +685,7 @@ export default class Module {
}

this.exports.default = {
identifier: node.variable.getOriginalVariableName() as string | undefined,
identifier: node.variable.getAssignedVariableName(),
localName: 'default'
};
} else if ((node as ExportNamedDeclaration).declaration) {
Expand All @@ -702,13 +701,13 @@ export default class Module {
if (declaration.type === NodeType.VariableDeclaration) {
for (const decl of declaration.declarations) {
for (const localName of extractAssignedNames(decl.id)) {
this.exports[localName] = { localName };
this.exports[localName] = { identifier: null, localName };
}
}
} else {
// export function foo () {}
const localName = (declaration.id as Identifier).name;
this.exports[localName] = { localName };
this.exports[localName] = { identifier: null, localName };
}
} else {
// export { foo, bar, baz }
Expand All @@ -726,7 +725,7 @@ export default class Module {
);
}

this.exports[exportedName] = { localName };
this.exports[exportedName] = { identifier: null, localName };
}
}
}
Expand Down
4 changes: 0 additions & 4 deletions src/ast/nodes/BlockStatement.ts
Expand Up @@ -8,10 +8,6 @@ import { UNKNOWN_EXPRESSION } from '../values';
import * as NodeType from './NodeType';
import { Node, StatementBase, StatementNode } from './shared/Node';

export function isBlockStatement(node: Node): node is BlockStatement {
return node.type === NodeType.BlockStatement;
}

export default class BlockStatement extends StatementBase {
body: StatementNode[];
type: NodeType.tBlockStatement;
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/BreakStatement.ts
Expand Up @@ -11,7 +11,7 @@ export default class BreakStatement extends StatementBase {
return (
super.hasEffects(options) ||
!options.ignoreBreakStatements() ||
((this.label && !options.ignoreLabel(this.label.name)) as boolean)
(this.label !== null && !options.ignoreLabel(this.label.name))
);
}
}
12 changes: 4 additions & 8 deletions src/ast/nodes/ClassDeclaration.ts
@@ -1,17 +1,13 @@
import MagicString from 'magic-string';
import { RenderOptions } from '../../utils/renderHelpers';
import ChildScope from '../scopes/ChildScope';
import Identifier from './Identifier';
import { IdentifierWithVariable } from './Identifier';
import * as NodeType from './NodeType';
import ClassNode from './shared/ClassNode';
import { GenericEsTreeNode, Node } from './shared/Node';

export function isClassDeclaration(node: Node): node is ClassDeclaration {
return node.type === NodeType.ClassDeclaration;
}
import { GenericEsTreeNode } from './shared/Node';

export default class ClassDeclaration extends ClassNode {
id: Identifier;
id: IdentifierWithVariable | null;
type: NodeType.tClassDeclaration;

initialise() {
Expand All @@ -24,7 +20,7 @@ export default class ClassDeclaration extends ClassNode {
parseNode(esTreeNode: GenericEsTreeNode) {
if (esTreeNode.id !== null) {
this.id = new this.context.nodeConstructors.Identifier(esTreeNode.id, this, this.scope
.parent as ChildScope) as Identifier;
.parent as ChildScope) as IdentifierWithVariable;
}
super.parseNode(esTreeNode);
}
Expand Down
18 changes: 7 additions & 11 deletions src/ast/nodes/ExportDefaultDeclaration.ts
Expand Up @@ -8,11 +8,11 @@ import {
import { treeshakeNode } from '../../utils/treeshakeNode';
import ModuleScope from '../scopes/ModuleScope';
import ExportDefaultVariable from '../variables/ExportDefaultVariable';
import ClassDeclaration, { isClassDeclaration } from './ClassDeclaration';
import FunctionDeclaration, { isFunctionDeclaration } from './FunctionDeclaration';
import ClassDeclaration from './ClassDeclaration';
import FunctionDeclaration from './FunctionDeclaration';
import Identifier from './Identifier';
import * as NodeType from './NodeType';
import { ExpressionNode, Node, NodeBase } from './shared/Node';
import { ExpressionNode, NodeBase } from './shared/Node';

const WHITESPACE = /\s/;

Expand All @@ -34,10 +34,6 @@ function getIdInsertPosition(code: string, declarationKeyword: string, start = 0
return declarationEnd + generatorStarPos + 1;
}

export function isExportDefaultDeclaration(node: Node): node is ExportDefaultDeclaration {
return node.type === NodeType.ExportDefaultDeclaration;
}

export default class ExportDefaultDeclaration extends NodeBase {
declaration: FunctionDeclaration | ClassDeclaration | ExpressionNode;
needsBoundaries: true;
Expand Down Expand Up @@ -70,23 +66,23 @@ export default class ExportDefaultDeclaration extends NodeBase {
render(code: MagicString, options: RenderOptions, { start, end }: NodeRenderOptions = BLANK) {
const declarationStart = getDeclarationStart(code.original, this.start);

if (isFunctionDeclaration(this.declaration)) {
if (this.declaration instanceof FunctionDeclaration) {
this.renderNamedDeclaration(
code,
declarationStart,
'function',
this.declaration.id === null,
options
);
} else if (isClassDeclaration(this.declaration)) {
} else if (this.declaration instanceof ClassDeclaration) {
this.renderNamedDeclaration(
code,
declarationStart,
'class',
this.declaration.id === null,
options
);
} else if (this.variable.referencesOriginal()) {
} else if (this.variable.getOriginalVariable() !== this.variable) {
// Remove altogether to prevent re-declaring the same variable
if (options.format === 'system' && this.variable.exportName) {
code.overwrite(
Expand Down Expand Up @@ -133,7 +129,7 @@ export default class ExportDefaultDeclaration extends NodeBase {
}
if (
options.format === 'system' &&
isClassDeclaration(this.declaration) &&
this.declaration instanceof ClassDeclaration &&
this.variable.exportName
) {
code.appendLeft(this.end, ` exports('${this.variable.exportName}', ${name});`);
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/ExportNamedDeclaration.ts
Expand Up @@ -23,7 +23,7 @@ export default class ExportNamedDeclaration extends NodeBase {
}

hasEffects(options: ExecutionPathOptions) {
return (this.declaration && this.declaration.hasEffects(options)) as boolean;
return this.declaration !== null && this.declaration.hasEffects(options);
}

initialise() {
Expand Down
6 changes: 1 addition & 5 deletions src/ast/nodes/ForInStatement.ts
Expand Up @@ -5,14 +5,10 @@ import BlockScope from '../scopes/BlockScope';
import Scope from '../scopes/Scope';
import { EMPTY_PATH } from '../values';
import * as NodeType from './NodeType';
import { ExpressionNode, Node, StatementBase, StatementNode } from './shared/Node';
import { ExpressionNode, StatementBase, StatementNode } from './shared/Node';
import { PatternNode } from './shared/Pattern';
import VariableDeclaration from './VariableDeclaration';

export function isForInStatement(node: Node): node is ForInStatement {
return node.type === NodeType.ForInStatement;
}

export default class ForInStatement extends StatementBase {
body: StatementNode;
left: VariableDeclaration | PatternNode;
Expand Down
6 changes: 1 addition & 5 deletions src/ast/nodes/ForOfStatement.ts
Expand Up @@ -5,14 +5,10 @@ import BlockScope from '../scopes/BlockScope';
import Scope from '../scopes/Scope';
import { EMPTY_PATH } from '../values';
import * as NodeType from './NodeType';
import { ExpressionNode, Node, StatementBase, StatementNode } from './shared/Node';
import { ExpressionNode, StatementBase, StatementNode } from './shared/Node';
import { PatternNode } from './shared/Pattern';
import VariableDeclaration from './VariableDeclaration';

export function isForOfStatement(node: Node): node is ForOfStatement {
return node.type === NodeType.ForOfStatement;
}

export default class ForOfStatement extends StatementBase {
await: boolean;
body: StatementNode;
Expand Down
6 changes: 1 addition & 5 deletions src/ast/nodes/ForStatement.ts
Expand Up @@ -4,13 +4,9 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions';
import BlockScope from '../scopes/BlockScope';
import Scope from '../scopes/Scope';
import * as NodeType from './NodeType';
import { ExpressionNode, Node, StatementBase, StatementNode } from './shared/Node';
import { ExpressionNode, StatementBase, StatementNode } from './shared/Node';
import VariableDeclaration from './VariableDeclaration';

export function isForStatement(node: Node): node is ForStatement {
return node.type === NodeType.ForStatement;
}

export default class ForStatement extends StatementBase {
body: StatementNode;
init: VariableDeclaration | ExpressionNode | null;
Expand Down
10 changes: 3 additions & 7 deletions src/ast/nodes/FunctionDeclaration.ts
@@ -1,12 +1,8 @@
import ChildScope from '../scopes/ChildScope';
import Identifier from './Identifier';
import { IdentifierWithVariable } from './Identifier';
import * as NodeType from './NodeType';
import FunctionNode from './shared/FunctionNode';
import { GenericEsTreeNode, Node } from './shared/Node';

export function isFunctionDeclaration(node: Node): node is FunctionDeclaration {
return node.type === NodeType.FunctionDeclaration;
}
import { GenericEsTreeNode } from './shared/Node';

export default class FunctionDeclaration extends FunctionNode {
type: NodeType.tFunctionDeclaration;
Expand All @@ -21,7 +17,7 @@ export default class FunctionDeclaration extends FunctionNode {
parseNode(esTreeNode: GenericEsTreeNode) {
if (esTreeNode.id !== null) {
this.id = new this.context.nodeConstructors.Identifier(esTreeNode.id, this, this.scope
.parent as ChildScope) as Identifier;
.parent as ChildScope) as IdentifierWithVariable;
}
super.parseNode(esTreeNode);
}
Expand Down

0 comments on commit 9f84980

Please sign in to comment.