rollup技术揭秘系列八 node.hasEffects(可能是全网最系统性的rollup源码分析文章)

279 阅读16分钟

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 揭秘相关文章