rollup技术揭秘系列九——module.include()(可能是全网最系统性的rollup源码分析文章)

这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

module.include()分析

上一个章节我们对所有的 nodeType.hasEffects 方法进行了详细的逻辑分析。现在我们结合 index 和 user 模块的源码,对 module.include() 更深入的分析。

在第五章节中我们知道调用 graph.sortModules() 之后会得到 orderedModules :

[
  {
    id: 'c:\\Users\\**\\Desktop\\study\\rollup-master\\rollup\\example\\user.js',
    ast: Program,
    //...
  },
  {
    id: 'c:\\Users\\**\\Desktop\\study\\rollup-master\\rollup\\example\\index.js',
    ast: Program,
    //...
  }
]

orderedModules 就是将 index 和 user 排序后的 modules 。

module.include() 执行流程:

  1. this.ast.shouldBeIncluded(context)
  2. this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext()))
  3. node.hasEffects()

node.hasEffects:

hasEffects(context: HasEffectsContext): boolean {
  // ...
  for (const node of this.body) {
    if (node.hasEffects(context)) {
      return (this.hasCachedEffect = true);
    }
  }
  return false;
}

user.js模块的 module.include()

const name = 'victor jiang';
const age = 17;

function foo() {
console.log(123);
function innerFunc() {
// tree-shaking
console.log(3);
}
return 'foo';
var bar = 'bar'; // 函数已经返回了,这里的赋值语句永远不会执行
}

export { name, age, foo };

在 user 模块中调用 node.hasEffects 会对 this.ast.body (即 Program.body) 循环判断 node.hasEffects(context) 执行结果。

  • const name = ‘victor jiang’; 是一个 VariableDeclaration。执行 VariableDeclaration.hasEffects(context) 会遍历其所有子节点执行 VariableDeclarator.hasEffects(context), 实际上是判断 this.init 或 this.id 是否是 hasEffects 的。最终都是返回了false。
  • const age = 17; 也和上一行代码的执行过程类似,它的结果也是返回了 false。
  • foo 函数是一个函数声明(FunctionDeclaration),FunctionDeclaration.hasEffects 的判断依据是看 this.id.hasEffects(context)。 this.id 是一个 Identifier 的节点,它的值是 “foo”。因为这个 foo函数是在当前作用域申明的,既不是 var 声明也不是一个 isPossibleTDZ() 的变量更不是 GlobalVariable 。因此最终也返回了 false 。
  • 最后是 export { name, age, foo }; 他是一个 ExportNamedDeclaration 的节点。因此执行 this.declaration.hasEffects(context) , 因为this.declaration = null,所以最终也返回了false。

经过上述逻辑分析,因此 user 模块的 this.hasEffects(createHasEffectsContext()) 最终返回了 false。所以 module.included() 方法里面没有执行 this.ast.include(context, false); 逻辑。

index.js模块的 module.include()


import { age, foo, name } from './user';
const fname = foo();
if (0) {
console.log('这段代码不会被执行');
} else {
console.log('这段代码保留');
}
// 导出一个foo函数
export default function hello() {
console.log(fname);
console.log(`hello! ${name}`);
}

在 index 模块中调用 node.hasEffects 会对 this.ast.body (即 Program.body) 循环判断 node.hasEffects(context) 执行结果。

  • import { age, foo, name } from ‘./user’; 是一个 ImportDeclaration。执行 ImportDeclaration.hasEffects(context) 直接返回了false。
  • const fname = foo(); 是一个 VariableDeclaration。执行 VariableDeclaration.hasEffects(context) 会遍历其所有子节点执行 VariableDeclarator.hasEffects(context), 实际上是判断 this.init 或 this.id 是否是 hasEffects 的。this.init.hasEffects 实际上会执行 CallExpression.hasEffects() 。又因为 “foo” 函数是从 user 模块导入的,因此在执行 this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) 的时候会返回 true。
// Identifier.ts
hasEffectsOnInteractionAtPath(
  path: ObjectPath,
  interaction: NodeInteraction,
  context: HasEffectsContext
): boolean {
  switch (interaction.type) {
    case INTERACTION_ACCESSED: {
      return (
        this.variable !== null &&
        this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context)
      );
    }
    case INTERACTION_ASSIGNED: {
      return (
        path.length > 0 ? this.getVariableRespectingTDZ() : this.variable
      )!.hasEffectsOnInteractionAtPath(path, interaction, context);
    }
    case INTERACTION_CALLED: {
      return this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(
        path,
        interaction,
        context
      );
    }
  }
}

EMPTY_PATH 是一个”[]”, this.interaction 是一个描述 Identifier 调用方式的描述对象:{args: Array(0), thisArg: null, type: 2, withNew: false},因此在调用 this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) 后会走到 case INTERACTION_CALLED 分支的逻辑。在执行 this.getVariableRespectingTDZ() 时会得到 this.variable,其实就是 Identifier.variable。在排序模块章节中分析过 module.bindReferences(); 的逻辑会对 Identifier.variable 进行赋值的操作,即执行 this.variable = this.scope.findVariable(this.name)(绑定变量的引用信息)。最后执行 this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path,interaction,context) 实际上执行的就是 Identifier.variable.hasEffectsOnInteractionAtPath(path,interaction,context)。

//LocalVariable.ts
hasEffectsOnInteractionAtPath(
  path: ObjectPath,
  interaction: NodeInteraction,
  context: HasEffectsContext
): boolean {
  switch (interaction.type) {
    case INTERACTION_ACCESSED: {
      if (this.isReassigned) return true;
      return (this.init &&
        !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) &&
        this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!;
    }
    case INTERACTION_ASSIGNED: {
      if (this.included) return true;
      if (path.length === 0) return false;
      if (this.isReassigned) return true;
      return (this.init &&
        !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) &&
        this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!;
    }
    case INTERACTION_CALLED: {
      if (this.isReassigned) return true;
      return (this.init &&
        !(
          interaction.withNew ? context.instantiated : context.called
        ).trackEntityAtPathAndGetIfTracked(path, interaction.args, this) &&
        this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!;
    }
  }
}  

逻辑走到 hasEffectsOnInteractionAtPath 方法内部会命中 case INTERACTION_CALLED:分支。this.isReassigned 为 false。this.init 就是 user 模块语法树的 FunctionDeclaration节点。interaction.withNew 为 false,代表不是 new 调用。context.called.trackEntityAtPathAndGetIfTracked(path, interaction.args, this) 实际上调用的就是 DiscriminatedPathTracker.trackEntityAtPathAndGetIfTracked(path, interaction.args, this)。 DiscriminatedPathTracker 类定义在 :src/ast/utils/PathTracker.ts 中

class DiscriminatedPathTracker {
private entityPaths: DiscriminatedEntityPaths = Object.create(null, {
[EntitiesKey]: { value: new Map<unknown, Set<Entity>>() }
});

trackEntityAtPathAndGetIfTracked(
path: ObjectPath,
discriminator: unknown,
entity: Entity
): boolean {
let currentPaths = this.entityPaths;
for (const pathSegment of path) {
currentPaths = currentPaths[pathSegment] =
currentPaths[pathSegment] ||
Object.create(null, { [EntitiesKey]: { value: new Map<unknown, Set<Entity>>() } });
}
    //trackedEntities 是一个 Set,它用于保存跟踪过的实体。
const trackedEntities = getOrCreate(currentPaths[EntitiesKey], discriminator, () => new Set());
if (trackedEntities.has(entity)) return true;
trackedEntities.add(entity);
return false;
}
}

DiscriminatedPathTracker.trackEntityAtPathAndGetIfTracked(path, interaction.args, this) 的参数中 path和interaction 都是 “[]”, this 就是 一个LocalVariable 的对象: {included: false, name: ‘foo’, kind: ‘function’, alwaysRendered: false, initReached: false, isId: true, …}。程序首次执行到这里的时候 trackedEntities 是一个空的 Set。然后执行 trackedEntities.add(entity) 将跟踪过的 LocalVariable 保存起来。最后 return false。

然后再执行 this.init.hasEffectsOnInteractionAtPath(path, interaction, context)), 代码定义在:src/ast/nodes/shared/FunctionNode.ts

//FunctionNode.ts
hasEffectsOnInteractionAtPath(
  path: ObjectPath,
  interaction: NodeInteraction,
  context: HasEffectsContext
): boolean {
  if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true;
  if (interaction.type === INTERACTION_CALLED) {
    //this.scope 就是 FunctionScope 
    const thisInit = context.replacedVariableInits.get(this.scope.thisVariable);
    context.replacedVariableInits.set(
      this.scope.thisVariable,
      interaction.withNew
        ? new ObjectEntity(Object.create(null), OBJECT_PROTOTYPE)
        : UNKNOWN_EXPRESSION
    );
    const { brokenFlow, ignore, replacedVariableInits } = context;
    context.ignore = {
      breaks: false,
      continues: false,
      labels: new Set(),
      returnYield: true
    };
    if (this.body.hasEffects(context)) return true;
    context.brokenFlow = brokenFlow;
    if (thisInit) {
      replacedVariableInits.set(this.scope.thisVariable, thisInit);
    } else {
      replacedVariableInits.delete(this.scope.thisVariable);
    }
    context.ignore = ignore;
  }
  return false;
}

再来看 hasEffectsOnInteractionAtPath 方法内部:

path.length = 0,this.async 为 false, this.params = []。因此 super.hasEffectsOnInteractionAtPath(path, interaction, context) 返回 false。

在第二个条件判断中 interaction.type === INTERACTION_CALLED 成立,在它的内部执行 if (this.body.hasEffects(context)) 的时候其实就是执行 FunctionDeclaration.body.hasEffects(context) 。 FunctionDeclaration.body 就是 foo 函数的函数体(包含花括号的部分)。最终执行的是 BlockStatement.hasEffects(context)。

在foo函数内部的第一行代码 console.log(123) 中 “console.log” 是一个 MemberExpression,并且执行 this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) 返回了 true。

function foo() {
  console.log(123);
  function innerFunc() {
    // tree-shaking
    console.log(3);
  }
  return 'foo';
  var bar = 'bar'; // 函数已经返回了,这里的赋值语句永远不会执行
}

因此程序调用栈回到 module.include() 方法内部:

include(): void {
  //context => {"brokenFlow":0,"includedCallArguments":{},"includedLabels":{}}
  const context = createInclusionContext();
  if (this.ast!.shouldBeIncluded(context)) this.ast!.include(context, false);
}

this.ast.shouldBeIncluded(context) 返回了 true, 此时 this 指向 index 模块。接着继续执行 this.ast.include(context, false),this.ast.include 就是 Program.include 方法。代码定义在: src/ast/nodes/Program.ts


include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
  this.included = true;
  for (const node of this.body) {
    if (includeChildrenRecursively || node.shouldBeIncluded(context)) {
      node.include(context, includeChildrenRecursively);
    }
  }
}

Program.include

Program.include 方法内部首先执行 this.included = true ,意思是将 Program 标记为 included 。接着遍历 this.body 判断 includeChildrenRecursively || node.shouldBeIncluded(context) 的条件是否成立,如是则执行 node.include(context, includeChildrenRecursively) 将子节点也标记为 included。

includeChildrenRecursively 默认为 false。node.shouldBeIncluded(context)的方法实现中只有 ExpressionStatement 和 shouldBeIncluded 两个类型的节点才有自己的 shouldBeIncluded 方法,其他节点均继承自 NodeBase 这个类。

NodeBase.shouldBeIncluded 方法定义在: src/ast/nodes/shared/Node.ts

shouldBeIncluded(context: InclusionContext): boolean {
  return this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext()));
}

NodeBase.shouldBeIncluded 内部的逻辑是如果 this.included 为 true 则返回 true。否则继续看 !context.brokenFlow && this.hasEffects(createHasEffectsContext()) 的执行结果,其中比较关键的还是 this.hasEffects(createHasEffectsContext()) 的判断逻辑。this.hasEffects(createHasEffectsContext()) 的方法在前面章节我们已经分析过了,在此就不多赘述了。

总结

module.include() 会根据 this.ast.shouldBeIncluded(context) 来决定是否执行 this.ast.include(context, false)。this.ast.shouldBeIncluded(context) 的判断基于 Program.body.child.hasEffects , 如果是则将 Program.included 设置为 true。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容