Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • Plan 计划
  • typescript-utility

  • single-spa源码

  • qiankun源码

  • webpack

    • 开始阅读
    • tapable源码

    • init阶段

    • make阶段

      • 本章概要
      • make 阶段:compilation
      • make 阶段:module
      • make 阶段:walk
        • 目录
        • ParserHook.program
        • UseStrictPlugin
        • HarmonyDetectionParserPlugin
        • preWalkStatements
        • defineVariable
        • blockPreWalkStatements
        • walkStatements
        • HarmonyImportDependencyParserPlugin
        • HarmonyExportDependencyParserPlugin
        • import/export
        • 总结
    • 总结

  • axios

  • solid

  • vite源码

  • jquery源码

  • snabbdom

  • am-editor

  • html2canvas

  • express

  • acorn源码

  • immutable.js源码

  • web
  • webpack
  • make阶段
jonsam
2022-04-25
目录

make 阶段:walk

上一节中,我们探讨了单一 Module 的 Build 和 Parse 过程。webpack 的 make 过程是从 EntryModule 开始的。我们知道,文件系统本身是文件树,ModuleDependence 本身也是 DependenceTree,因此从 EntryModule 开始的 make 过程必然是一个递归的过程。这本节内容中,我们来探讨 ModuleTree 的 make 过程。

# 目录

  • 目录
  • ParserHook.program
  • UseStrictPlugin
  • HarmonyDetectionParserPlugin
  • preWalkStatements
  • defineVariable
  • blockPreWalkStatements
  • walkStatements
  • HarmonyImportDependencyParserPlugin
  • HarmonyExportDependencyParserPlugin
  • import/export
  • 总结

上一节在 JavascriptParser parser.pase 函数的内容中,我们了解到对 Module 的 AST,有如下的过程:

if (this.hooks.program.call(ast, comments) === undefined) {
  // preWalk AST
  this.preWalkStatements(ast.body);
  // blockPreWalk
  this.blockPreWalkStatements(ast.body);
  // Walk AST
  this.walkStatements(ast.body);
}
1
2
3
4
5
6
7
8

这里针对 ast.body 有 preWalk 和 walk 的过程。所谓 preWalk 就是在 walk 之前的处理工作。walk 的过程就是对 AST 进行遍历查找依赖的过程。

# ParserHook.program

这个 Hook 为什么叫 program,事情上 acorn 根据 ESTree 的规范 parse 出来的 AST 顶层对象就叫 Program。如下:

{
  "type": "Program",
  "start": 0,
  "end": 25,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 24,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 18,
        "name": "printTips"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [],
      "body": {
        "type": "BlockStatement",
        "start": 21,
        "end": 24,
        "body": []
      }
    }
  ],
  "sourceType": "module"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

参考:

  • AST explorer (opens new window)

既然 ParserHook.program 在此时触发,那么我们就来看下有哪些插件在此注册了 Callback。事实上,这样的插件有:

  • CompatibilityPlugin
  • DefinePlugin
  • UseStrictPlugin
  • HarmonyDetectionParserPlugin
  • InnerGraphPlugin
  • SideEffectsFlagPlugin

现在我们只能深入这些插件来查看他们针对 AST 做了哪些事情。现在有一个疑点,在 lib/WebpackOptionsApply.js 中注册这些插件时,只传入了 compiler,那么插件中 parser 是怎么获取的呢?我们能在这些插件中找到这样的代码:

normalModuleFactory.hooks.parser
.for("javascript/auto")
.tap("CompatibilityPlugin", handler);
normalModuleFactory.hooks.parser
.for("javascript/dynamic")
.tap("CompatibilityPlugin", handler);
normalModuleFactory.hooks.parser
.for("javascript/esm")
.tap("CompatibilityPlugin", handler);
1
2
3
4
5
6
7
8
9

原来这些插件是在 NormalModuleFactoryHook.parser 钩子中获得 parser 的,并在 handler 中在向 ParserHook.program 注入处理逻辑。

# UseStrictPlugin

parser.hooks.program.tap("UseStrictPlugin", (ast) => {
 const firstNode = ast.body[0];
 // 如果是 use strict
 if (
  firstNode &&
  firstNode.type === "ExpressionStatement" &&
  firstNode.expression.type === "Literal" &&
  firstNode.expression.value === "use strict"
 ) {
  // Remove "use strict" expression. It will be added later by the renderer again.
  // This is necessary in order to not break the strict mode when webpack prepends code.
  // @see https://github.com/webpack/webpack/issues/1970
  // 删除 use strict 语句,添加 ConstDependency
  const dep = new ConstDependency("", firstNode.range);
  dep.loc = firstNode.loc;
  parser.state.module.addPresentationalDependency(dep);
  parser.state.module.buildInfo.strict = true;
 }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# HarmonyDetectionParserPlugin

parser.hooks.program.tap("HarmonyDetectionParserPlugin", (ast) => {
 // 如果 Module 是 javascript/esm,则严格使用 HarmonyCompatibilityDependency
 const isStrictHarmony = parser.state.module.type === "javascript/esm";
 // 如果包含 import/export 语句,也需要使用 HarmonyCompatibilityDependency
 const isHarmony =
  isStrictHarmony ||
  ast.body.some(
   (statement) =>
    statement.type === "ImportDeclaration" ||
    statement.type === "ExportDefaultDeclaration" ||
    statement.type === "ExportNamedDeclaration" ||
    statement.type === "ExportAllDeclaration"
  );
 // 添加 HarmonyCompatibilityDependency
 if (isHarmony) {
  const module = parser.state.module;
  const compatDep = new HarmonyCompatibilityDependency();
  compatDep.loc = {
   start: {
    line: -1,
    column: 0,
   },
   end: {
    line: -1,
    column: 0,
   },
   index: -3,
  };
  module.addPresentationalDependency(compatDep);
  // ......
  parser.scope.isStrict = true;
 }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

ParserHook.program 时主要针对 AST 中是否有 'use strict' 语句和 import/export 语句,分别向 Module 添加 ConstDependency 和 HarmonyCompatibilityDependency。

# preWalkStatements

深度遍历(DFS)AST,触发 ParserHook.preStatement,并修改变量声明语句的 scope。

// Pre walking iterates the scope for variable declarations
preWalkStatements(statements) {
  for (let index = 0, len = statements.length; index < len; index++) {
   const statement = statements[index];
   this.preWalkStatement(statement);
  }
}

preWalkStatement(statement) {
 this.statementPath.push(statement);
  // 触发 ParserHook.preStatement => call
 if (this.hooks.preStatement.call(statement)) {
  this.prevStatement = this.statementPath.pop();
  return;
 }
 switch (statement.type) {
  case "BlockStatement":
   this.preWalkBlockStatement(statement);
   break;
  case "DoWhileStatement":
   this.preWalkDoWhileStatement(statement);
   break;
  case "ForInStatement":
   this.preWalkForInStatement(statement);
   break;
  case "ForOfStatement":
   this.preWalkForOfStatement(statement);
   break;
  case "ForStatement":
   this.preWalkForStatement(statement);
   break;
  case "FunctionDeclaration":
   this.preWalkFunctionDeclaration(statement);
   break;
  case "IfStatement":
   this.preWalkIfStatement(statement);
   break;
  case "LabeledStatement":
   this.preWalkLabeledStatement(statement);
   break;
  case "SwitchStatement":
   this.preWalkSwitchStatement(statement);
   break;
  case "TryStatement":
   this.preWalkTryStatement(statement);
   break;
  case "VariableDeclaration":
   this.preWalkVariableDeclaration(statement);
   break;
  case "WhileStatement":
   this.preWalkWhileStatement(statement);
   break;
  case "WithStatement":
   this.preWalkWithStatement(statement);
   break;
 }
 this.prevStatement = this.statementPath.pop();
}
// ......

_preWalkVariableDeclaration(statement, hookMap) {
 for (const declarator of statement.declarations) {
  switch (declarator.type) {
   case "VariableDeclarator": {
    //  触发 ParserHook.preDeclarator => call
    if (!this.hooks.preDeclarator.call(declarator, statement)) {
     this.enterPattern(declarator.id, (name, decl) => {
      let hook = hookMap.get(name);
      if (hook === undefined || !hook.call(decl)) {
       hook = this.hooks.varDeclaration.get(name);
       if (hook === undefined || !hook.call(decl)) {
        this.defineVariable(name);
       }
      }
     });
    }
    break;
   }
  }
 }
}

defineVariable(name) {
 const oldInfo = this.scope.definitions.get(name);
 // ......
 // 收集变量和 scope 到 this.scope.definitions
 this.scope.definitions.set(name, this.scope);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# defineVariable

# blockPreWalkStatements

遍历 AST,触发 ParserHook.blockPreStatement,查找 block 变量声明(import/export),以执行不同的操作。

// Block pre walking iterates the scope for block variable declarations
blockPreWalkStatements(statements) {
 for (let index = 0, len = statements.length; index < len; index++) {
  const statement = statements[index];
  this.blockPreWalkStatement(statement);
 }
}

blockPreWalkStatement(statement) {
 this.statementPath.push(statement);
 // 触发 ParserHook.blockPreStatement => call
 if (this.hooks.blockPreStatement.call(statement)) {
  this.prevStatement = this.statementPath.pop();
  return;
 }
 switch (statement.type) {
  case "ImportDeclaration":
   // 触发 ParserHook.import
   // 触发 ParserHook.importSpecifier
   this.blockPreWalkImportDeclaration(statement);
   break;
  case "ExportAllDeclaration":
   // 触发 ParserHook.exportImport
   // 触发 ParserHook.exportImportSpecifier
   this.blockPreWalkExportAllDeclaration(statement);
   break;
  case "ExportDefaultDeclaration":
   // 触发 ParserHook.exportSpecifier
   this.blockPreWalkExportDefaultDeclaration(statement);
   break;
  case "ExportNamedDeclaration":
   //  触发 ParserHook.exportImport 或者 ParserHook.export
   //  触发 ParserHook.exportDeclaration
   //  触发 ParserHook.exportImportSpecifier 或者 ParserHook.exportSpecifier
   this.blockPreWalkExportNamedDeclaration(statement);
   break;
  case "VariableDeclaration":
   this.blockPreWalkVariableDeclaration(statement);
   break;
  case "ClassDeclaration":
   this.blockPreWalkClassDeclaration(statement);
   break;
 }
 this.prevStatement = this.statementPath.pop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# walkStatements

walk 阶段很重要的作用就是通过遍历 AST,触发 Parser 上的各种 Hook,因此在 ParserHook 上注册的各种插件就会被触发。在这些插件当中,很重要的就是收集依赖的插件。

这个遍历的过程十分繁琐,下面我们从插件的角度反向探讨 Hook 的触发时机和产生的依赖。

# HarmonyImportDependencyParserPlugin

关注 hook 和 parser.state.module.addDependency 的关系。源码位置 lib/dependencies/HarmonyImportDependencyParserPlugin.js 。

ParserHook addDependency Hook 触发时机
import HarmonyImportSideEffectDependency blockPreWalkStatement => blockPreWalkImportDeclaration
binaryExpression HarmonyEvaluatedImportSpecifierDependency walkArrayExpression...... => walkExpressions => walkExpression => walkBinaryExpression
expression HarmonyImportSpecifierDependency walkExpressions => walkExpression => walkMetaProperty
expressionMemberChain HarmonyImportSpecifierDependency walkExpressions => walkExpression => walkMemberExpression
callMemberChain HarmonyImportSpecifierDependency walkExpressions => walkExpression => walkCallExpression

# HarmonyExportDependencyParserPlugin

ParserHook addDependency Hook 触发时机
exportImport HarmonyImportSideEffectDependency blockPreWalkStatements => blockPreWalkStatement => blockPreWalkExportNamedDeclaration
exportExpression HarmonyExportExpressionDependency walkStatements => walkStatement => walkExportDefaultDeclaration
exportSpecifier HarmonyExportImportedSpecifierDependency blockPreWalkStatements => blockPreWalkStatement => blockPreWalkExportDefaultDeclaration
exportImportSpecifier HarmonyExportImportedSpecifierDependency 或 HarmonyExportImportedSpecifierDependency blockPreWalkStatements => blockPreWalkStatement => blockPreWalkExportAllDeclaration

在代码中搜索 .addDependency(dep) ,如果代码是在 ParserHook 中执行的,就是在处理 Module 依赖的插件。

// lib/DependenciesBlock.js
addDependency(dependency) {
 this.dependencies.push(dependency);
}
1
2
3
4

注意 NormalModule 继承自 Module,Module 继承自 DependenciesBlock。因此 每个 Module 都有一个 dependencies 数组。

# import/export

# 总结

编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16
make 阶段:module
Webpack中的设计模式探讨

← make 阶段:module Webpack中的设计模式探讨→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式