Skip to content

Commit

Permalink
Try to treeshake
Browse files Browse the repository at this point in the history
  • Loading branch information
TrickyPi committed Mar 17, 2024
1 parent 080b10f commit c6c2366
Show file tree
Hide file tree
Showing 77 changed files with 518 additions and 65 deletions.
1 change: 1 addition & 0 deletions HOW.md
@@ -0,0 +1 @@
# recursive the Identifier like this `this.variable.init.includePath`
52 changes: 33 additions & 19 deletions src/Graph.ts
Expand Up @@ -166,30 +166,44 @@ export default class Graph {
}
if (this.options.treeshake) {
let treeshakingPass = 1;
do {
timeStart(`treeshaking pass ${treeshakingPass}`, 3);
this.needsTreeshakingPass = false;
for (const module of this.modules) {
if (module.isExecuted) {
if (module.info.moduleSideEffects === 'no-treeshake') {
module.includeAllInBundle();
} else {
module.include();
const loopTreeShaking = () => {
do {
timeStart(`treeshaking pass ${treeshakingPass}`, 3);
this.needsTreeshakingPass = false;
for (const module of this.modules) {
if (module.isExecuted) {
if (module.info.moduleSideEffects === 'no-treeshake') {
module.includeAllInBundle();
} else {
module.include();
}
}
}
}
if (treeshakingPass === 1) {
// We only include exports after the first pass to avoid issues with
// the TDZ detection logic
for (const module of entryModules) {
if (module.preserveSignature !== false) {
module.includeAllExports(false);
this.needsTreeshakingPass = true;
if (treeshakingPass === 1) {
// We only include exports after the first pass to avoid issues with
// the TDZ detection logic
for (const module of entryModules) {
if (module.preserveSignature !== false) {
module.includeAllExports(false);
this.needsTreeshakingPass = true;
}
}
}
timeEnd(`treeshaking pass ${treeshakingPass++}`, 3);
} while (this.needsTreeshakingPass);
};
loopTreeShaking();
for (const module of this.modules) {
for (const dynamicDependency of module.dynamicDependenciesIncludeAllExports) {
dynamicDependency.includeAllExports(true);
}
timeEnd(`treeshaking pass ${treeshakingPass++}`, 3);
} while (this.needsTreeshakingPass);
}
if (this.needsTreeshakingPass) {
loopTreeShaking();
}
if (this.needsTreeshakingPass) {
throw new Error('What What What!!!');
}
} else {
for (const module of this.modules) module.includeAllInBundle();
}
Expand Down
15 changes: 8 additions & 7 deletions src/Module.ts
Expand Up @@ -217,6 +217,7 @@ export default class Module {
readonly dynamicDependencies = new Set<Module | ExternalModule>();
readonly dynamicImporters: string[] = [];
readonly dynamicImports: DynamicImport[] = [];
dynamicDependenciesIncludeAllExports = new Set<Module>();
excludeFromSourcemap: boolean;
execIndex = Infinity;
readonly implicitlyLoadedAfter = new Set<Module>();
Expand Down Expand Up @@ -1308,23 +1309,23 @@ export default class Module {
}

private includeDynamicImport(node: ImportExpression): void {
const resolution = (
this.dynamicImports.find(dynamicImport => dynamicImport.node === node) as {
resolution: string | Module | ExternalModule | undefined;
}
).resolution;
const resolution = this.dynamicImports.find(
dynamicImport => dynamicImport.node === node
)!.resolution;

if (resolution instanceof Module) {
resolution.includedDynamicImporters.push(this);
!resolution.includedDynamicImporters.includes(this) &&
resolution.includedDynamicImporters.push(this);

const importedNames = this.options.treeshake
? node.getDeterministicImportedNames()
: undefined;

if (importedNames) {
this.dynamicDependenciesIncludeAllExports.delete(resolution);
resolution.includeExportsByNames(importedNames);
} else {
resolution.includeAllExports(true);
this.dynamicDependenciesIncludeAllExports.add(resolution);
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -195,9 +195,14 @@ export default class Identifier extends NodeBase implements PatternNode {
}
}

includePath(): void {
includePath(path?: ObjectPath): void {
if (!this.deoptimized) this.applyDeoptimizations();
if (!this.included) {
if (this.included) {
if (path?.length && !this.includedPaths.has(path[0])) {
this.includedPaths.add(path[0]);
this.variable?.includePath(path);
}
} else {
this.included = true;
if (this.variable !== null) {
this.scope.context.includeVariableInModule(this.variable);
Expand Down
14 changes: 12 additions & 2 deletions src/ast/nodes/ImportExpression.ts
Expand Up @@ -12,7 +12,7 @@ import {
import { findFirstOccurrenceOutsideComment, type RenderOptions } from '../../utils/renderHelpers';
import type { InclusionContext } from '../ExecutionContext';
import type ChildScope from '../scopes/ChildScope';
import type { ObjectPath } from '../utils/PathTracker';
import { type ObjectPath, UnknownKey } from '../utils/PathTracker';
import type NamespaceVariable from '../variables/NamespaceVariable';
import ArrowFunctionExpression from './ArrowFunctionExpression';
import AwaitExpression from './AwaitExpression';
Expand Down Expand Up @@ -43,6 +43,7 @@ export default class ImportExpression extends NodeBase {
declare type: NodeType.tImportExpression;
declare sourceAstNode: AstNode;

private hasUnknownAccessedKey = false;
private accessedPropKey = new Set<string>();
private attributes: string | null | true = null;
private mechanism: DynamicImportMechanism | null = null;
Expand Down Expand Up @@ -85,6 +86,7 @@ export default class ImportExpression extends NodeBase {
if (parent2 instanceof VariableDeclarator) {
const declaration = parent2.id;
if (declaration instanceof Identifier) {
if (this.hasUnknownAccessedKey) return undefined;
return this.accessedPropKey.size > 0 ? [...this.accessedPropKey] : undefined;
}
if (declaration instanceof ObjectPattern) {
Expand Down Expand Up @@ -170,8 +172,16 @@ export default class ImportExpression extends NodeBase {
this.included = true;
this.scope.context.includeDynamicImport(this);
this.scope.addAccessedDynamicImport(this);
this.source.includePath(path, context, includeChildrenRecursively);
}
if (this.hasUnknownAccessedKey) return;
if (path[0] === UnknownKey) {
this.hasUnknownAccessedKey = true;
this.scope.context.includeDynamicImport(this);
} else if (typeof path[0] === 'string') {
this.accessedPropKey.add(path[0]);
this.scope.context.includeDynamicImport(this);
}
this.source.includePath(path, context, includeChildrenRecursively);
}

initialise(): void {
Expand Down
21 changes: 2 additions & 19 deletions src/ast/nodes/MemberExpression.ts
Expand Up @@ -27,12 +27,9 @@ import {
} from '../utils/PathTracker';
import { UNDEFINED_EXPRESSION } from '../values';
import ExternalVariable from '../variables/ExternalVariable';
import LocalVariable from '../variables/LocalVariable';
import type NamespaceVariable from '../variables/NamespaceVariable';
import type Variable from '../variables/Variable';
import AwaitExpression from './AwaitExpression';
import Identifier from './Identifier';
import type ImportExpression from './ImportExpression';
import Literal from './Literal';
import type * as NodeType from './NodeType';
import type PrivateIdentifier from './PrivateIdentifier';
Expand All @@ -49,10 +46,6 @@ import {
import type { ChainElement, ExpressionNode, IncludeChildren } from './shared/Node';
import { NodeBase } from './shared/Node';

function isImportExpression(node: ExpressionNode): node is ImportExpression {
return node.type === 'ImportExpression';
}

// To avoid infinite recursions
const MAX_PATH_DEPTH = 7;

Expand Down Expand Up @@ -168,16 +161,6 @@ export default class MemberExpression
} else {
super.bind();
}
if (baseVariable instanceof LocalVariable) {
const init = baseVariable.init;
if (
init instanceof AwaitExpression &&
isImportExpression(init.argument) &&
typeof this.propertyKey === 'string'
) {
init.argument.addAccessedPropKey(this.propertyKey);
}
}
}

deoptimizeArgumentsOnInteractionAtPath(
Expand Down Expand Up @@ -316,12 +299,12 @@ export default class MemberExpression
}

includePath(
path: ObjectPath,
_: ObjectPath,
context: InclusionContext,
includeChildrenRecursively: IncludeChildren
): void {
if (!this.deoptimized) this.applyDeoptimizations();
this.includeProperties(path, context, includeChildrenRecursively);
this.includeProperties([this.propertyKey || UnknownKey], context, includeChildrenRecursively);
}

includeAsAssignmentTarget(
Expand Down
31 changes: 26 additions & 5 deletions src/ast/nodes/ObjectPattern.ts
@@ -1,12 +1,14 @@
import type { HasEffectsContext } from '../ExecutionContext';
import type { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import type { NodeInteractionAssigned } from '../NodeInteractions';
import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker';
import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker';
import type LocalVariable from '../variables/LocalVariable';
import type Variable from '../variables/Variable';
import * as NodeType from './NodeType';
import type Property from './Property';
import type RestElement from './RestElement';
import type { ExpressionEntity } from './shared/Expression';
import Property from './Property';
import RestElement from './RestElement';
import VariableDeclarator from './VariableDeclarator';
import type { ExpressionEntity, InclusionOptions } from './shared/Expression';
import type { IncludeChildren } from './shared/Node';
import { NodeBase } from './shared/Node';
import type { PatternNode } from './shared/Pattern';
import type { VariableKind } from './shared/VariableKinds';
Expand Down Expand Up @@ -47,6 +49,25 @@ export default class ObjectPattern extends NodeBase implements PatternNode {
}
}

includePath(
path: ObjectPath,
context: InclusionContext,
includeChildrenRecursively: IncludeChildren,
options?: InclusionOptions | undefined
): void {
super.includePath(path, context, includeChildrenRecursively, options);
if (this.parent instanceof VariableDeclarator) {
for (const p of this.properties) {
if (p instanceof Property) {
this.parent.init?.includePath([p.key.name], context, includeChildrenRecursively, options);
}
if (p instanceof RestElement) {
this.parent.init?.includePath(UNKNOWN_PATH, context, includeChildrenRecursively, options);
}
}
}
}

hasEffectsOnInteractionAtPath(
// At the moment, this is only triggered for assignment left-hand sides,
// where the path is empty
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/Property.ts
Expand Up @@ -4,16 +4,16 @@ import type { RenderOptions } from '../../utils/renderHelpers';
import type { HasEffectsContext } from '../ExecutionContext';
import { UnknownKey } from '../utils/PathTracker';
import type LocalVariable from '../variables/LocalVariable';
import type Identifier from './Identifier';
import type * as NodeType from './NodeType';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './shared/Expression';
import MethodBase from './shared/MethodBase';
import type { ExpressionNode } from './shared/Node';
import type { PatternNode } from './shared/Pattern';
import type { VariableKind } from './shared/VariableKinds';

export default class Property extends MethodBase implements PatternNode {
declare key: ExpressionNode;
declare key: Identifier;
declare kind: 'init' | 'get' | 'set';
declare type: NodeType.tProperty;
private declarationInit: ExpressionEntity | null = null;
Expand Down
9 changes: 8 additions & 1 deletion src/ast/nodes/shared/Expression.ts
Expand Up @@ -2,7 +2,12 @@ import type { DeoptimizableEntity } from '../../DeoptimizableEntity';
import type { WritableEntity } from '../../Entity';
import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext';
import type { NodeInteraction, NodeInteractionCalled } from '../../NodeInteractions';
import type { ObjectPath, PathTracker, SymbolToStringTag } from '../../utils/PathTracker';
import type {
ObjectPath,
ObjectPathKey,
PathTracker,
SymbolToStringTag
} from '../../utils/PathTracker';
import { EMPTY_PATH, UNKNOWN_PATH } from '../../utils/PathTracker';
import type { LiteralValue } from '../Literal';
import type SpreadElement from '../SpreadElement';
Expand All @@ -29,6 +34,8 @@ export interface InclusionOptions {
export class ExpressionEntity implements WritableEntity {
protected flags: number = 0;

includedPaths = new Set<ObjectPathKey>();

get included(): boolean {
return isFlagSet(this.flags, Flag.included);
}
Expand Down
14 changes: 8 additions & 6 deletions src/ast/variables/LocalVariable.ts
Expand Up @@ -29,8 +29,6 @@ import Variable from './Variable';
export default class LocalVariable extends Variable {
calledFromTryStatement = false;

init: ExpressionEntity;

readonly declarations: (Identifier | ExportDefaultDeclaration)[];
readonly module: Module;
readonly kind: VariableKind;
Expand All @@ -44,14 +42,13 @@ export default class LocalVariable extends Variable {
constructor(
name: string,
declarator: Identifier | ExportDefaultDeclaration | null,
init: ExpressionEntity,
private init: ExpressionEntity,
context: AstContext,
kind: VariableKind
) {
super(name);
this.declarations = declarator ? [declarator] : [];
this.deoptimizationTracker = context.deoptimizationTracker;
this.init = init;
this.module = context.module;
this.kind = kind;
}
Expand Down Expand Up @@ -185,8 +182,13 @@ export default class LocalVariable extends Variable {
}
}

includePath(): void {
if (!this.included) {
includePath(path?: ObjectPath): void {
if (this.included) {
if (path?.length && !this.includedPaths.has(path[0])) {
this.includedPaths.add(path[0]);
this.init.includePath(path, createInclusionContext(), false);
}
} else {
super.includePath();
for (const declaration of this.declarations) {
// If node is a default export, it can save a tree-shaking run to include the full declaration now
Expand Down
2 changes: 1 addition & 1 deletion src/ast/variables/Variable.ts
Expand Up @@ -79,7 +79,7 @@ export default class Variable extends ExpressionEntity {
* previously.
* Once a variable is included, it should take care all its declarations are included.
*/
includePath(): void {
includePath(_path?: ObjectPath): void {
this.included = true;
this.renderedLikeHoisted?.includePath();
}
Expand Down
@@ -0,0 +1,11 @@
define(['exports'], (function (exports) { 'use strict';

const foo = () => {};
const bar = () => {};
const baz = () => {};

exports.bar = bar;
exports.baz = baz;
exports.foo = foo;

}));
@@ -0,0 +1,10 @@
define(['require'], (function (require) { 'use strict';

(async () => {
const module = await new Promise(function (resolve, reject) { require(['./generated-module'], resolve, reject); });
module.foo();
module[global.unknown]();
module.baz();
})();

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

const foo = () => {};
const bar = () => {};
const baz = () => {};

exports.bar = bar;
exports.baz = baz;
exports.foo = foo;

0 comments on commit c6c2366

Please sign in to comment.