node.hasEffects
通俗的理解 node.hasEffects 其实就是指节点是否影响最终的 bundle 。rollup 中的 "Tree shaking" 的主要原理就是先将所有的 node 标记为 included 为 true 或者 false, 最终统一对 included 为 false 的 node 进行字符串的删除或者替换操作来达到删除无用代码的效果。 因此我们这一章节将会对所有的 nodeType 进行 hasEffects 逻辑分析。
NodeBase
class NodeBase {
//...
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
for (const key of this.keys) {
const value = (this as GenericEsTreeNode)[key];
if (value === null) continue;
if (Array.isArray(value)) {
for (const child of value) {
if (child?.hasEffects(context)) return true;
}
} else if (value.hasEffects(context)) return true;
}
return false;
}
//...
}
1.ArrayExpression (数组表达式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ArrayExpression.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ArrayExpression {
type: 'ArrayExpression';
elements: Array<null | Expression | SpreadElement>;
}
例:
[1, 2, 3];
2.ArrayPattern (数组解析模式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ArrayPattern.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ArrayPattern {
type: 'ArrayPattern';
elements: Array<null | PatternLike>;
}
例:
const [a, b, c] = [1, 2, 3];
3.ArrowFunctionExpression (箭头函数表达式)
hasEffects 默认返回 false
hasEffects(): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
return false;
}
AST Node shape:
interface ArrowFunctionExpression {
type: 'ArrowFunctionExpression';
params: Array<Identifier | Pattern | RestElement>;
body: BlockStatement | Expression;
async: boolean;
expression: boolean;
generator: boolean;
predicate: DeclaredPredicate | InferredPredicate;
returnType: TypeAnnotation | TSTypeAnnotation | Noop;
typeParameters: TypeParameterDeclaration | TSTypeParameterDeclaration | Noop;
}
例:
const getArray = () => [1, 2, 3];
4.AssignmentExpression (赋值表达式)
AssignmentExpression 的 hasEffects 逻辑首先会判断 right 节点 hasEffects ,否则继续判断 left.hasEffectsAsAssignmentTarget(context, operator !== '=')。 left.hasEffectsAsAssignmentTarget 实际上执行的是 NodeBase.hasEffectsAsAssignmentTarget。
hasEffects(context: HasEffectsContext): boolean {
const { deoptimized, left, operator, right } = this;
if (!deoptimized) this.applyDeoptimizations();
// MemberExpressions do not access the property before assignments if the
// operator is '='.
return (
right.hasEffects(context) || left.hasEffectsAsAssignmentTarget(context, operator !== '=')
);
}
AST Node shape:
interface AssignmentExpression {
type: 'AssignmentExpression';
operator:
| '='
| '+='
| '-='
| '*='
| '/='
| '%='
| '<<='
| '>>='
| '>>>='
| '|='
| '^='
| '&='
| '**=';
left: Expression;
right: Expression;
}
例:
a += 1;
5.AssignmentPattern (赋值模式。常用于解构赋值)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 AssignmentPattern.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface AssignmentPattern {
type: 'AssignmentPattern';
left:
| Identifier
| ObjectPattern
| ArrayPattern
| MemberExpression
| TSAsExpression
| TSTypeAssertion
| TSNonNullExpression;
right: Expression;
}
例:
const [a, b, c = 3] = [1, 2, 3]; // AssignmentPattern => c = 3
6.AwaitExpression (await 表达式)
hasEffects 默认返回 true
hasEffects(): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
return true;
}
AST Node shape:
interface AwaitExpression {
type: 'AwaitExpression';
argument: Expression;
}
例:
await foo();
7.BinaryExpression (二元表达式)
BinaryExpression 的 hasEffects 逻辑是判断 this.operator === '+' 并且父节点是一个表达式节点。而且操作符的左节点调用 getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this)返回了空字符串这三个条件同时满足的情况就返回 true。否则就判断 super.hasEffects(context)。
hasEffects(context: HasEffectsContext): boolean {
// support some implicit type coercion runtime errors
if (
this.operator === '+' &&
this.parent instanceof ExpressionStatement &&
this.left.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this) === ''
) {
return true;
}
return super.hasEffects(context);
}
AST Node shape:
interface BinaryExpression {
type: 'BinaryExpression';
operator:
| '+'
| '-'
| '/'
| '%'
| '*'
| '**'
| '&'
| '|'
| '>>'
| '>>>'
| '<<'
| '^'
| '=='
| '==='
| '!='
| '!=='
| 'in'
| 'instanceof'
| '>'
| '<'
| '>='
| '<='
| '|>';
left: Expression;
right: Expression;
}
例:
let a = 1;
let b = 2;
if (a === b) {
//
}
8.BlockStatement (块语句)
块语句判断 hasEffects 就是判断其所有的子节点中有任一节点 hasEffects 就返回 true, 否则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.deoptimizeBody) return true;
for (const node of this.body) {
if (context.brokenFlow) break;
if (node.hasEffects(context)) return true;
}
return false;
}
AST Node shape:
interface BlockStatement {
type: 'BlockStatement';
body: Array<Statement>;
}
例:
{
console.log(123);
}
9.BreakStatement (break 语句)
break 语句判断 hasEffects 首先得判断存在 label 的情况。如果 context.ignore.labels.has(this.label.name) 返回了 false,这行代码的意思是如果 this.label.name 不可以忽略,则 hasEffects 返回 true。否则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.label) {
if (!context.ignore.labels.has(this.label.name)) return true;
context.includedLabels.add(this.label.name);
context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL;
} else {
if (!context.ignore.breaks) return true;
context.brokenFlow = BROKEN_FLOW_BREAK_CONTINUE;
}
return false;
}
AST Node shape:
interface BreakStatement {
type: 'BreakStatement';
label: Identifier;
}
例:
for (const node of [1, 2, 3]) {
if (node >= 2) break;
console.log(node);
}
10.CallExpression (函数执行表达式)
CallExpression 节点判断 hasEffects 首先会判断 argument.hasEffects(context), 是则直接返回 true。否则接着判断 this.context.options.treeshake.annotations 和 this.anotations 成立就会返回 false。最后判断 this.callee.hasEffects 或者 this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
hasEffects(context: HasEffectsContext): boolean {
try {
for (const argument of this.arguments) {
if (argument.hasEffects(context)) return true;
}
if (
(this.context.options.treeshake as NormalizedTreeshakingOptions).annotations &&
this.annotations
)
return false;
return (
this.callee.hasEffects(context) ||
this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
);
} finally {
if (!this.deoptimized) this.applyDeoptimizations();
}
}
AST Node shape:
interface CallExpression {
type: 'CallExpression';
callee: Expression | V8IntrinsicIdentifier;
arguments: Array<Expression | SpreadElement | JSXNamespacedName | ArgumentPlaceholder>;
optional: true | false;
}
例:
const foo = () => {};
foo();
11.CatchClause (catch 语句)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 CatchClause.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface CatchClause {
type: 'CatchClause';
param: Identifier | ArrayPattern | ObjectPattern;
body: BlockStatement;
}
例:
try {
foo();
} catch (e) {
console.error(e);
} finally {
bar();
}
12.ClassBody (class Body)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ClassBody.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ClassBody {
type: 'ClassBody';
body: Array<
| ClassMethod
| ClassPrivateMethod
| ClassProperty
| ClassPrivateProperty
| ClassAccessorProperty
| TSDeclareMethod
| TSIndexSignature
| StaticBlock
>;
}
例:
class foo {
constructor() {}
method() {}
}
13.ClassDeclaration (class 声明)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ClassDeclaration.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ClassDeclaration {
type: 'ClassDeclaration';
id: Identifier;
superClass: Expression;
body: ClassBody;
}
例:
class foo {
constructor() {}
method() {}
}
14.ClassExpression (class 表达式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ClassExpression.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ClassExpression {
type: 'ClassExpression';
id: Identifier;
superClass: Expression;
body: ClassBody;
}
例:
const foo = class {
constructor() {}
method() {}
};
15.ConditionalExpression (条件表达式)
ConditionalExpression 判断 hasEffects 是基于条件,例如 this.test.hasEffects(context) 的结果返回了 true,则证明 hasEffects 为 true。否则会接着判断 else 代码块的 hasEffects 是否为 true。
hasEffects(context: HasEffectsContext): boolean {
if (this.test.hasEffects(context)) return true;
const usedBranch = this.getUsedBranch();
if (!usedBranch) {
return this.consequent.hasEffects(context) || this.alternate.hasEffects(context);
}
return usedBranch.hasEffects(context);
}
AST Node shape:
interface ConditionalExpression {
type: 'ConditionalExpression';
test: Expression;
consequent: Expression;
alternate: Expression;
}
例:
const a = true ? 'consequent' : 'alternate';
16.ContinueStatement (continue 语句)
ContinueStatement 判断 hasEffects 和 break 语句判断 hasEffects 的逻辑类似。首先如果存在 label 并且 label 在 context.ignore.labels 中不存在就会直接返回 true。else 分支中如果 !context.ignore.continues 的条件成立也会返回 true。
hasEffects(context: HasEffectsContext): boolean {
if (this.label) {
if (!context.ignore.labels.has(this.label.name)) return true;
context.includedLabels.add(this.label.name);
context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL;
} else {
if (!context.ignore.continues) return true;
context.brokenFlow = BROKEN_FLOW_BREAK_CONTINUE;
}
return false;
}
AST Node shape:
interface ContinueStatement {
type: 'ContinueStatement';
label: Identifier;
}
例:
for (const node of [1, 2, 3]) {
if (node === 2) continue;
console.log(node);
}
17.DoWhileStatement (do-while 语句)
hasEffects 方法内部首先会判断 this.test.hasEffects(context) 如果成立的话直接返回 true。接着判断 this.body.hasEffects(context) 如果成立的话返回 true。否则就返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.test.hasEffects(context)) return true;
const { brokenFlow, ignore } = context;
const { breaks, continues } = ignore;
ignore.breaks = true;
ignore.continues = true;
if (this.body.hasEffects(context)) return true;
ignore.breaks = breaks;
ignore.continues = continues;
context.brokenFlow = brokenFlow;
return false;
}
AST Node shape:
interface DoWhileStatement {
type: 'DoWhileStatement';
test: Expression;
body: Statement;
}
例:
do {} while (a >= 1);
18.EmptyStatement (空语句)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface EmptyStatement {
type: 'EmptyStatement';
}
例:
for (const node of [1, 2, 3]);
19.ExportAllDeclaration (导出所有声明)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface ExportAllDeclaration {
type: 'ExportAllDeclaration';
source: StringLiteral;
}
例:
export * from './user';
20.ExportDefaultDeclaration (导出默认声明)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ExportDefaultDeclaration.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ExportDefaultDeclaration {
type: 'ExportDefaultDeclaration';
declaration: FunctionDeclaration | TSDeclareFunction | ClassDeclaration | Expression;
}
例:
export default 'foo';
21.ExportNamedDeclaration (导出带名称的声明)
hasEffects 直接根据 this.declaration.hasEffects(context) 的结果来决定。
hasEffects(context: HasEffectsContext): boolean {
return !!this.declaration?.hasEffects(context);
}
AST Node shape:
interface ExportNamedDeclaration {
type: 'ExportNamedDeclaration';
declaration: Declaration;
specifiers: Array<ExportSpecifier | ExportDefaultSpecifier | ExportNamespaceSpecifier>;
source: StringLiteral;
}
例:
const foo = 'foo';
export { foo };
// 或者 export const foo = 'foo';
22.ExpressionStatement (表达式语句)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ExpressionStatement.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ExpressionStatement {
type: 'ExpressionStatement';
expression: Expression;
}
例:
(function foo() {})();
23.ForInStatement (for...in 语句)
hasEffects 内部判断 left.hasEffectsAsAssignmentTarget(context, false) 或者 right.hasEffects(context) 再或者 body.hasEffects(context) 三者任一成立就会返回 true。
hasEffects(context: HasEffectsContext): boolean {
const { body, deoptimized, left, right } = this;
if (!deoptimized) this.applyDeoptimizations();
if (left.hasEffectsAsAssignmentTarget(context, false) || right.hasEffects(context)) return true;
const { brokenFlow, ignore } = context;
const { breaks, continues } = ignore;
ignore.breaks = true;
ignore.continues = true;
if (body.hasEffects(context)) return true;
ignore.breaks = breaks;
ignore.continues = continues;
context.brokenFlow = brokenFlow;
return false;
}
AST Node shape:
interface ForInStatement {
type: 'ForInStatement';
left: VariableDeclaration | LVal;
right: Expression;
body: Statement;
}
例:
for (let item in [1, 2, 3]) {
}
24.ForOfStatement (for...of 语句)
hasEffects 默认返回 true
hasEffects(): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
// Placeholder until proper Symbol.Iterator support
return true;
}
AST Node shape:
interface ForOfStatement {
type: 'ForOfStatement';
left: VariableDeclaration | LVal;
right: Expression;
body: Statement;
}
例:
for (let item of [1, 2, 3]) {
}
25.ForStatement (for 语句)
for 语句和 for...in 语句的 hasEffects 判断逻辑类似。 首先判断 this.init、this.test、this.update 三者任一一个调用 hasEffects(context) 的结果返回 true 则直接 return true。如果前面条件不符合则继续判断 this.body.hasEffects(context) 是否成立。如果都不满足条件则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (
this.init?.hasEffects(context) ||
this.test?.hasEffects(context) ||
this.update?.hasEffects(context)
)
return true;
const { brokenFlow, ignore } = context;
const { breaks, continues } = ignore;
ignore.breaks = true;
ignore.continues = true;
if (this.body.hasEffects(context)) return true;
ignore.breaks = breaks;
ignore.continues = continues;
context.brokenFlow = brokenFlow;
return false;
}
AST Node shape:
interface ForStatement {
type: 'ForStatement';
init: VariableDeclaration | Expression; //例如:let a = 1;
test: Expression; //例如:a++;
update: Expression; //例如:a < 3
body: Statement;
}
例:
for (let a = 1; a++; a < 3) {}
26.FunctionDeclaration (for 语句)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 FunctionDeclaration.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface FunctionDeclaration {
type: 'FunctionDeclaration';
id: Identifier;
expression: boolean;
generator: boolean;
async: boolean;
params: Array<Identifier | Pattern | RestElement>;
body: BlockStatement;
}
例:
function foo() {}
//或者 function *foo(){}
27.FunctionExpression (函数表达式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 FunctionExpression.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface FunctionExpression {
type: 'FunctionExpression';
id: Identifier;
expression: boolean;
generator: boolean;
async: boolean;
params: Array<Identifier | Pattern | RestElement>;
body: BlockStatement;
}
例:
const foo = function () {};
28.Identifier (标识符。就是我们写 JS 时自定义的名称,如变量名,函数名,属性名,统称为标识符。)
hasEffects 方法内部判断标识符是否是 TDZ (暂时性死区) 并且 this.variable 不是通过 var 关键字声明的则直接返回 true。否则得继续判断 this.context.options.treeshake.unknownGlobalSideEffects 和 this.variable instanceof GlobalVariable 以及 this.variable.hasEffectsOnInteractionAtPath( EMPTY_PATH, NODE_INTERACTION_UNKNOWN_ACCESS, context ) 是否同时满足条件。
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
if (this.isPossibleTDZ() && this.variable!.kind !== 'var') {
return true;
}
return (
(this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects &&
this.variable instanceof GlobalVariable &&
this.variable.hasEffectsOnInteractionAtPath(
EMPTY_PATH,
NODE_INTERACTION_UNKNOWN_ACCESS,
context
)
);
}
AST Node shape:
interface Identifier {
type: 'Identifier';
name: string;
}
例:
const foo = function () {};
//这个foo就是 Identifier
29.IfStatement (if 语句)
hasEffects 方法内部首先判断 this.test.hasEffects(context)满足条件就会直接返回 true。否则会拿到 testValue 作进一步判断,如果 testValue 是 symbol 类型的则依次判断 if 语句或者 else 语句中是否满足条件。如果 this.test.hasEffects(context)不满足条件并且 testValue 也不是 symbol 类型则最后执行 return testValue ? this.consequent.hasEffects(context) : !!this.alternate?.hasEffects(context);
hasEffects(context: HasEffectsContext): boolean {
if (this.test.hasEffects(context)) {
return true;
}
const testValue = this.getTestValue();
if (typeof testValue === 'symbol') {
const { brokenFlow } = context;
if (this.consequent.hasEffects(context)) return true;
// eslint-disable-next-line unicorn/consistent-destructuring
const consequentBrokenFlow = context.brokenFlow;
context.brokenFlow = brokenFlow;
if (this.alternate === null) return false;
if (this.alternate.hasEffects(context)) return true;
context.brokenFlow =
// eslint-disable-next-line unicorn/consistent-destructuring
context.brokenFlow < consequentBrokenFlow ? context.brokenFlow : consequentBrokenFlow;
return false;
}
return testValue ? this.consequent.hasEffects(context) : !!this.alternate?.hasEffects(context);
}
AST Node shape:
interface IfStatement {
type: 'IfStatement';
test: Expression;
consequent: Statement;
alternate: Statement;
}
例:
if (a > 1) {
}
30.ImportDeclaration (import 声明)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface ImportDeclaration {
type: 'ImportDeclaration';
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>;
source: StringLiteral;
}
例:
import { foo } from './user';
31.ImportDeclaration (import 声明)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface ImportDeclaration {
type: 'ImportDeclaration';
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>;
source: StringLiteral;
}
例:
import { foo } from './user'; //这个“foo” 就是 ImportSpecifier
32.ImportDefaultSpecifier (默认导入的标识符)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ImportDefaultSpecifier.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ImportDefaultSpecifier {
type: 'ImportDefaultSpecifier';
local: Identifier;
}
例:
import foo from './user'; //这个“foo” 就是 ImportDefaultSpecifier
33.ImportExpression (import 表达式)
hasEffects 默认返回 true
hasEffects(): boolean {
return true;
}
AST Node shape:
interface ImportExpression {
type: 'ImportExpression';
source: StringLiteral;
}
例:
import('./user'); //source.value = './user'
34.ImportNamespaceSpecifier (带命名空间的导入标识符)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ImportNamespaceSpecifier.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ImportNamespaceSpecifier {
type: 'ImportNamespaceSpecifier';
local: Identifier;
}
例:
import * as foo from './foo'; //ImportNamespaceSpecifier => '* as foo'
35.ImportSpecifier (导入标识符)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ImportSpecifier.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ImportSpecifier {
type: 'ImportSpecifier';
local: Identifier;
imported: Identifier | StringLiteral;
}
例:
import { foo } from './foo';
//ImportSpecifier => 'foo'
// local => 'foo'
36.LabeledStatement (label 语句, 又叫标记语句。作用是在语句前面设置一个标识符。相当于将一条语句存储在一个变量里面,类似函数的函数名。)
hasEffects 内部判断如果 this.body.hasEffects(context) 为 true 直接返回 true,否则返回 false。
hasEffects(context: HasEffectsContext): boolean {
const brokenFlow = context.brokenFlow;
context.ignore.labels.add(this.label.name);
if (this.body.hasEffects(context)) return true;
context.ignore.labels.delete(this.label.name);
if (context.includedLabels.has(this.label.name)) {
context.includedLabels.delete(this.label.name);
context.brokenFlow = brokenFlow;
}
return false;
}
AST Node shape:
interface LabeledStatement {
type: 'LabeledStatement';
label: Identifier;
body: Statement;
}
例:
/**
* label语句一般与 break 或者 continue 代码块一起使用
* */
//下面这个例子在双重for循环中,实现了在内层循环结束掉外层循环的功能。
out: for (let i = 0; i < 5; i++) {
console.log('i', i);
for (let j = 0; j < 5; j++) {
console.log('j', j);
if (i == 2) {
//to do something
break out;
}
}
}
//下面的例子中当i===2的时候会跳过最外层的for循环一次,因此两个for循环都只能循环4次
out: for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
console.log('j', j);
if (i == 2) {
//to do something
continue out;
}
}
console.log('i', i);
}
/**
* 与代码块一起使用
* */
out: {
console.log(1);
break out;
console.log(2);
}
console.log(3);
//依次打印 1、3
37.Literal (字面量。一般指布尔值、数值、字符串、正则表达式、null)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 Literal.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface Literal {
type: 'Literal';
value: boolean | number | string | RegExp | null;
raw: string;
regex?: { pattern: string; flags: string };
}
例:
let a = 1;
38.LogicalExpression (逻辑运算符表达式)
hasEffects 内部依次判断 this.left.hasEffects(context) 和 this.right.hasEffects(context) 是否成立,否则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.left.hasEffects(context)) {
return true;
}
if (this.getUsedBranch() !== this.left) {
return this.right.hasEffects(context);
}
return false;
}
AST Node shape:
interface LogicalExpression {
type: 'LogicalExpression';
operator: '||' | '&&' | '??';
left: Expression;
right: Expression;
}
例:
let a = true || null;
39.MemberExpression (属性成员表达式)
hasEffects 判断 this.property、this.object、this.hasAccessEffect 是否任一条件成立
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
return (
this.property.hasEffects(context) ||
this.object.hasEffects(context) ||
this.hasAccessEffect(context)
);
}
AST Node shape:
interface MemberExpression {
type: 'MemberExpression';
object: Expression;
property: Expression;
computed: boolean;
optional: boolean;
}
例:
let user = { name: 'victor', age: 17 };
user.name; //MemberExpression, computed: false
user['name']; //MemberExpression, computed: true
40.MetaProperty (元数据属性。元数据,简单的来说就是描述数据的数据。例如,一个 HTML 文件是一种数据,但 HTML 文件也能在 <head> 元素中包含描述该文档的元数据,比如该文件的作者和概要。)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface MetaProperty {
type: 'MetaProperty';
meta: Identifier;
property: Identifier;
}
例:
function Vue(options) {
if (!new.target) {
// 这个 new.target 就是 MetaProperty, 通常"new."的作用是提供属性访问的上下文,但这里"new."其实不是一个真正的对象。
throw new Error('Vue is a constructor and should be called with the `new` keyword');
}
}
import.meta; //MetaProperty 通常"import."的作用是提供属性访问的上下文,但这里"import."其实不是一个真正的对象。这个对象可以扩展,并且它的属性都是可写,可配置和可枚举的。
41.MethodDefinition (类中的方法声明)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 MethodDefinition.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface MethodDefinition {
type: 'MethodDefinition';
static: boolean;
computed: boolean;
key: Expression | Identifier;
kind: 'constructor' | 'method' | 'get' | 'set';
value: FunctionExpression;
}
例:
class Point {
x = 0;
y = 0;
set point({ x, y }) {
this.x = x;
this.y = y;
}
get point() {
return this.toString();
}
toString() {
return `${this.x},${this.y}`;
}
}
42.NewExpression (new 表达式)
NewExpression 的 hasEffects 方法逻辑也和 CallExpression 的 hasEffects 逻辑类似。
hasEffects(context: HasEffectsContext): boolean {
try {
for (const argument of this.arguments) {
if (argument.hasEffects(context)) return true;
}
if (
(this.context.options.treeshake as NormalizedTreeshakingOptions).annotations &&
this.annotations
) {
return false;
}
return (
this.callee.hasEffects(context) ||
this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
);
} finally {
if (!this.deoptimized) this.applyDeoptimizations();
}
}
AST Node shape:
interface NewExpression {
type: 'NewExpression';
callee: Expression;
arguments: Expression[];
}
例:
new Promise();
43.ObjectExpression (对象表达式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 MethodDefinition.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ObjectExpression {
type: 'ObjectExpression';
properties: Property[];
}
例:
let user = { name: 'victor', age: 17 };
44.ObjectPattern (对象解析模式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 MethodDefinition.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ObjectPattern {
type: 'ObjectPattern';
properties: Property[];
}
例:
const { name, age } = { name: 'victor', age: 17 }; //ObjectPattern 就是 { name, age }
45.Program (源代码树)
Program 判断 hasEffects 方法内部增加了缓存,如果 this.hasCachedEffect 为 false 则继续判断 body 节点的所有子节点是否存在任一 node.hasEffects(context) 条件成立。如果都不成立则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.hasCachedEffect) return true;
for (const node of this.body) {
if (node.hasEffects(context)) {
return (this.hasCachedEffect = true);
}
}
return false;
}
AST Node shape:
interface Program {
type: 'Program';
body: StatementNode[];
sourceType: 'module' | 'script';
}
例:
const { name, age } = { name: 'victor', age: 17 }; //指整个js文件的代码
46.Property (属性)
hasEffects 判断如果父节点是一个 ObjectPattern(对象解构模式)并且 propertyReadSideEffects === 'always' 或者 this.key.hasEffects(context) 和 this.value.hasEffects(context) 任一条件满足就返回 true。
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
const propertyReadSideEffects = (this.context.options.treeshake as NormalizedTreeshakingOptions)
.propertyReadSideEffects;
return (
(this.parent.type === 'ObjectPattern' && propertyReadSideEffects === 'always') ||
this.key.hasEffects(context) ||
this.value.hasEffects(context)
);
}
AST Node shape:
interface Property {
type: 'Property';
key: Expression;
value: Expression | null;
kind: 'init' | 'get' | 'set';
method: boolean;
shorthand: boolean;
computed: boolean;
}
例:
const user = { name: 'victor', age: 17 };
47.PropertyDefinition (属性定义)
hasEffects 判断 this.key.hasEffects(context) 或者是 static 代码块并且 this.value.hasEffects(context) 两者任一条件满足就返回 true。
hasEffects(context: HasEffectsContext): boolean {
return this.key.hasEffects(context) || (this.static && !!this.value?.hasEffects(context));
}
AST Node shape:
interface PropertyDefinition {
type: 'PropertyDefinition';
computed: boolean;
static: boolean;
key: Expression;
value: Expression | null;
}
例:
class Point {
x = 0; //PropertyDefinition
y = 0; //PropertyDefinition
set point({ x, y }) {
this.x = x;
this.y = y;
}
get point() {
return this.toString();
}
toString() {
return `${this.x},${this.y}`;
}
}
48.RestElement (rest 参数。形式为...变量名)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 RestElement.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface RestElement {
type: 'RestElement';
argument: Identifier | PatternNode;
}
例:
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
49.ReturnStatement (return 语句)
this.argument.hasEffects(context) 返回 true 则 hasEffects 成立,否则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (!context.ignore.returnYield || this.argument?.hasEffects(context)) return true;
context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL;
return false;
}
AST Node shape:
interface ReturnStatement {
type: 'ReturnStatement';
argument: Expression | null;
}
例:
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum; //ReturnStatement
}
50.SequenceExpression (序列表达式)
序列表达式判断 hasEffects 就是遍历子节点执行 expression.hasEffects(context) 逻辑。如果所有子节点都不满足条件则返回 false。
hasEffects(context: HasEffectsContext): boolean {
for (const expression of this.expressions) {
if (expression.hasEffects(context)) return true;
}
return false;
}
AST Node shape:
interface SequenceExpression {
type: 'SequenceExpression';
expressions: Expression[];
}
例:
let a, b;
(a = 1), (b = 2); //SequenceExpression
51.SpreadElement (扩展运算符节点)
hasEffects 方法会判断 this.argument.hasEffects(context) 或者 当 options.treeshake。propertyReadSideEffects 为'always' 并且 this.argument.hasEffectsOnInteractionAtPath( UNKNOWN_PATH, NODE_INTERACTION_UNKNOWN_ACCESS, context ) 的任一条件成立就会返回 true。
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
const { propertyReadSideEffects } = this.context.options
.treeshake as NormalizedTreeshakingOptions;
return (
this.argument.hasEffects(context) ||
(propertyReadSideEffects &&
(propertyReadSideEffects === 'always' ||
this.argument.hasEffectsOnInteractionAtPath(
UNKNOWN_PATH,
NODE_INTERACTION_UNKNOWN_ACCESS,
context
)))
);
}
AST Node shape:
interface SpreadElement {
type: 'SpreadElement';
argument: Expression;
}
例:
// 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3]);
// 1 2 3
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
52.StaticBlock (静态块)
StaticBlock 的 hasEffects 逻辑就是判断所有子节点中 node.hasEffects(context) 是否成立。如果都不成立则返回 false。
hasEffects(context: HasEffectsContext): boolean {
for (const node of this.body) {
if (node.hasEffects(context)) return true;
}
return false;
}
AST Node shape:
interface StaticBlock {
type: 'StaticBlock';
body: StatementNode[];
}
例:
// ES2022 引入了静态块(static block),允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化。以后,新建类的实例时,这个块就不运行了。
class C {
static x;
static y;
static {
//to do something
}
}
53.Super (父类关键字)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 Super.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface Super {
type: 'Super';
}
例:
class Foo {}
class Bar extends Foo {
constructor() {
super();
}
}
54.SwitchCase
hasEffects 内部首先判断 case 语句内部的 test 条件是否成立,如果成立直接返回 true。否则遍历 this.consequent 执行 node.hasEffects(context) 的判断逻辑。如果前面条件都不满足则返回 false。
hasEffects(context: HasEffectsContext): boolean {
if (this.test?.hasEffects(context)) return true;
for (const node of this.consequent) {
if (context.brokenFlow) break;
if (node.hasEffects(context)) return true;
}
return false;
}
AST Node shape:
interface SwitchCase {
type: 'SwitchCase';
consequent: Statement[];
test: Expression | null;
}
例:
let a = 1;
switch (a) {
case (a = 1): //SwitchCase
//to do something
}
55.SwitchStatement (Switch 语句)
hasEffects 先判断 switch.discriminant.hasEffects(context) 是否满足条件,是则直接返回 true。接着使用 for 循环对 SwitchCase 节点进行 hasEffects 判断。switchCase.hasEffects(context) 判断逻辑参照 SwitchCase.hasEffects 方法。
hasEffects(context: HasEffectsContext): boolean {
if (this.discriminant.hasEffects(context)) return true;
const { brokenFlow, ignore } = context;
const { breaks } = ignore;
let minBrokenFlow = Infinity;
ignore.breaks = true;
for (const switchCase of this.cases) {
if (switchCase.hasEffects(context)) return true;
// eslint-disable-next-line unicorn/consistent-destructuring
minBrokenFlow = context.brokenFlow < minBrokenFlow ? context.brokenFlow : minBrokenFlow;
context.brokenFlow = brokenFlow;
}
if (this.defaultCase !== null && !(minBrokenFlow === BROKEN_FLOW_BREAK_CONTINUE)) {
context.brokenFlow = minBrokenFlow;
}
ignore.breaks = breaks;
return false;
}
AST Node shape:
interface SwitchStatement {
type: 'SwitchStatement';
cases: SwitchCase[];
discriminant: Expression;
}
例:
let a = 1;
switch (a) {
case (a = 1): //SwitchCase
//to do something
}
56.TaggedTemplateExpression (带标签的模板字符串表达式)
hasEffects 首先遍历 this.quasi.expressions(this.quasi.expressions 就是模板字符串部分) 执行 argument.hasEffects(context)如果返回了true则证明 hasEffects 为 true。否则继续判断 this.tag.hasEffects(context) || this.tag.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) 二者之间是否存在满足条件的。
hasEffects(context: HasEffectsContext): boolean {
try {
for (const argument of this.quasi.expressions) {
if (argument.hasEffects(context)) return true;
}
return (
this.tag.hasEffects(context) ||
this.tag.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
);
} finally {
if (!this.deoptimized) this.applyDeoptimizations();
}
}
AST Node shape:
interface TaggedTemplateExpression {
type: 'TaggedTemplateExpression';
quasi: TemplateLiteral;
tag: Expression;
}
例:
// 带标签的函数的规则是普通字符串会被传到第一个参数,其他在 ${} 中的内容,则会依次传入后面的参数。
function sayHello(arg1, arg2) {
console.log(`hello ${arg2}`);
}
sayHello`hello ${'victor'}`; //hello victor
// tag => sayHello
// quasi => `hello ${'victor'}`
57.TemplateElement (模板元素)
hasEffects 默认返回 false
hasEffects(): boolean {
return false;
}
AST Node shape:
interface TemplateElement {
type: 'TemplateElement';
tail: boolean;
value: {
cooked: string | null;
raw: string;
};
}
例:
function sayHello(arg1, arg2) {
console.log(`hello ${arg2}`); //TemplateElement => 'hello '
}
sayHello`hello ${'victor'}`; //hello victor
// tag => sayHello
// quasi => `hello ${'victor'}`
58.TemplateLiteral (模板字面量)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 TemplateLiteral.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface TemplateLiteral {
type: 'TemplateLiteral';
expressions: Expression[];
quasis: TemplateElement[];
}
例:
let foo = `foo`;
59.ThisExpression (this 表达式)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 ThisExpression.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface ThisExpression {
type: 'ThisExpression';
}
例:
class Foo {
constructor() {
this.name = 'foo';
}
}
60.ThrowStatement (throw 语句)
hasEffects 默认返回 true
hasEffects(): boolean {
return true;
}
AST Node shape:
interface ThrowStatement {
type: 'ThrowStatement';
argument: Expression;
}
例:
throw Error('uncatch error');
61.TryStatement (try 语句)
this.context.options.treeshake.tryCatchDeoptimization 默认为true, 因此默认情况下 try语句的 hasEffects 由 this.block.body.length > 0 来决定。即body块内存在代码就返回true。
hasEffects(context: HasEffectsContext): boolean {
return (
((this.context.options.treeshake as NormalizedTreeshakingOptions).tryCatchDeoptimization
? this.block.body.length > 0
: this.block.hasEffects(context)) || !!this.finalizer?.hasEffects(context)
);
}
AST Node shape:
interface TryStatement {
type: 'TryStatement';
block: BlockStatement;
finalizer: BlockStatement | null;
handler: CatchClause | null;
}
例:
try {
} catch {}
62.UnaryExpression (一元操作符表达式)
hasEffects 首先判断如果是类似 ”typeof Identifier” 的操作则直接返回 false。否则后面的判断逻辑。
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
// 例如 typeof Identifier 的时候返回 false
if (this.operator === 'typeof' && this.argument instanceof Identifier) return false;
return (
this.argument.hasEffects(context) ||
(this.operator === 'delete' &&
this.argument.hasEffectsOnInteractionAtPath(
EMPTY_PATH,
NODE_INTERACTION_UNKNOWN_ASSIGNMENT,
context
))
);
}
AST Node shape:
interface UnaryExpression {
type: 'UnaryExpression';
argument: Expression;
operator: '!' | '+' | '-' | 'delete' | 'typeof' | 'void' | '~';
prefix: boolean;
}
例:
+1;
63.UpdateExpression (更新操作符表达式)
hasEffects 会运行 this.argument.hasEffectsAsAssignmentTarget(context, true);
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
return this.argument.hasEffectsAsAssignmentTarget(context, true);
}
AST Node shape:
interface UpdateExpression {
type: 'UpdateExpression';
argument: Expression;
operator: '++' | '--';
prefix: boolean;
}
例:
++1;
64.VariableDeclaration (变量声明。包含了 kind 部分)
没有自己的 hasEffects 方法。它继承自 NodeBase 因此 VariableDeclaration.hasEffects 实际上执行的是 NodeBase.hasEffects。
AST Node shape:
interface VariableDeclaration {
type: 'VariableDeclaration';
declarations: VariableDeclarator[];
kind: 'var' | 'let' | 'const';
}
例:
let foo = 'foo';
65.VariableDeclarator (变量声明。不含 kind 部分)
hasEffects 首先会判断“=”右边的值调用hasEffects(context)是否返回true,否则继续判断this.id.hasEffects(context);
hasEffects(context: HasEffectsContext): boolean {
const initEffect = this.init?.hasEffects(context);
this.id.markDeclarationReached();
return initEffect || this.id.hasEffects(context);
}
AST Node shape:
interface VariableDeclarator {
type: 'VariableDeclarator';
id: PatternNode;
init: Expression | null;
}
例:
let foo = 'foo';
66.WhileStatement (while 语句)
hasEffects 先判断 this.test.hasEffects(context)是否满足条件,否则再看 this.body.hasEffects(context) 是否满足条件。
hasEffects(context: HasEffectsContext): boolean {
if (this.test.hasEffects(context)) return true;
const { brokenFlow, ignore } = context;
const { breaks, continues } = ignore;
ignore.breaks = true;
ignore.continues = true;
if (this.body.hasEffects(context)) return true;
ignore.breaks = breaks;
ignore.continues = continues;
context.brokenFlow = brokenFlow;
return false;
}
AST Node shape:
interface WhileStatement {
type: 'WhileStatement';
body: Statement;
test: Expression;
}
例:
while (true) {}
67.YieldExpression (yield 表达式)
context.ignore.returnYield 默认为false。
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
return !(context.ignore.returnYield && !this.argument?.hasEffects(context));
}
AST Node shape:
interface YieldExpression {
type: 'YieldExpression';
argument: Expression | null;
delegate: boolean;
}
例:
function* foo() {
yield 'a';
yield 'b';
}
rollup 揭秘相关文章
- rollup 技术揭秘系列一 准备篇(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列二 源码目录结构及打包入口分析(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列三 rollup 函数(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列四 graph.build(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列五 构建依赖图谱(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列六 模块排序(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列七 includeStatements(可能是全网最系统性的 rollup 源码分析文章
- rollup 技术揭秘系列八 node.hasEffects(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列九 module.include()(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十 includeStatements 总结(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十一 rollup 打包配置选项整理(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十二 handleGenerateWrite(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十三 bundle.generate(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十四 renderChunks(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十五 renderModules(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十六 Rollup 插件开发指南(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十七 rollup-cli 的开发(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十八 Rollup 打包流程示意图(可能是全网最系统性的 rollup 源码分析文章)