这是我参与「掘金日新计划 · 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() 执行流程:
- this.ast.shouldBeIncluded(context)
- this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext()))
- 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。
暂无评论内容