webpack4流程分析4

NormalModule

// 经过上面的分析 我们知道调用 NormalModule 的build方法是真正的开始
// NormalModule 继承 Module
class NormalModule extends Module {
  build() {
    // doBuild就是runLoaders过程 先不考虑loader的情况
    return this.doBuild(options, compilation, resolver, fs, (err) => {
      // 使用acorn解析生成ast 然后遍历
      const result = this.parser.parse(
        //  this._source = this.createSource()
        // return new OriginalSource(source, this.request);
        this._ast || this._source.source(),
        {},
        cb
      );
    });
  }
}
// Module 继承 DependenciesBlock
class Module extends DependenciesBlock {}
//里面有个重要的方法
class DependenciesBlock {
  constructor() {
    this.dependencies = [];
    this.blocks = [];
    this.variables = [];
  }
  addDependency(dependency) {
    this.dependencies.push(dependency);
  }
}

复制代码

NormalModuleFactory

// 在上一章 我们知道 NormalModule 是在 NormalModuleFactory 中执行factory的时候new的
// 在 resolver 的过程中返回了 new module 的参数 我们先看下参数
// 暂时先不考虑loader的过程 这个步骤找到了绝对路径
callback(null, {
    context,
    // request, // request: loaders.map(),
    request: path.posix.join(context, request), // 绝对路径
    dependencies: data.dependencies, // 依赖 这个时候是 SingleEntryDependency
    userRequest: path.posix.join(context, request), // 绝对路径 暂时使用path
    rawRequest: request, // './src/index.js'
    loaders: [],
    // 这里暂时使用entry的绝对路径来替代 忽略loader的部分
    resource: path.posix.join(context, request),
    // matchResource,
    // resourceResolveData,
    settings, // {type: 'javascript/auto'}
    type: settings.type,
    // js解析器 JavascriptModulesPlugin插件
    parser: this.getParser(settings.type, settings.parser),
    // 不同的模块使用不同的生成器 为模版生成提供api方法
    generator: this.getGenerator(settings.type, settings.generator),
    resolveOptions: data.resolveOptions,
});
复制代码

JavascriptModulesPlugin

// parser 哪来的? 不同类型的文件我们会有不同的解析器
// 在执行webpack的过程中 会处理 WebpackOptionsApply
new JavascriptModulesPlugin().apply(compiler); // js模块
new JsonModulesPlugin().apply(compiler); // json模块
... 其他模块对应的插件处理

class JavascriptModulesPlugin {
  apply(compiler) {
    // 在compiler的hooks上有很多 compilation 的钩子
    // 当创建 compilation 的时候 会触发对应的钩子 执行一些列的hook
    compiler.hooks.compilation.tap(
      "JavascriptModulesPlugin",
      (compilation, { normalModuleFactory }) => {
        normalModuleFactory.hooks.createParser
          .for("javascript/auto") // 针对不同的type会有不同的钩子
          .tap("JavascriptModulesPlugin", (options) => {
            // js的解析器就是这里得到的
            return new Parser(options, "auto");
          });
        // 模版主要和生成代码相关逻辑之后分析
        compilation.mainTemplate.hooks.modules.tap();
      }
    );
  }
}
复制代码

Parser

// 在上一篇的时候 我们直接跳过了parse的过程 现在简单看下 
// 我们在初始化compilation之后通过模块工厂创建模块就可以拿到这里的js解析器
class Parser extends Tapable {
  constructor(options, sourceType = "auto") {
    super();
    // 一堆的hooks
    this.hooks = {};
  }
  // 我们调用parser方法 source是经过loader-runner处理过的代码
  // 主要是分两步 一个是parse解析 一个是遍历(触发各种hooks)
  parse(source, initialState) {
    // 生成ast语法树
    let ast = Parser.parse(source, { sourceType: "module" });
    // 和作用域相关的 例如函数作用域 块级作用域等 先不考虑
    this.scope = {};
    const state = (this.state = initialState || {});
    // 对语法树进行分析 主要就是用 module 的 addDependency 方法增加一些依赖 还有模板的处理
    if (this.hooks.program.call(ast, comments) === undefined) {
      // 针对不同的节点做不同的处理
      this.detectMode(ast.body);
      this.prewalkStatements(ast.body);
      this.blockPrewalkStatements(ast.body);
      this.walkStatements(ast.body);
    }
    return state;
  }
}
复制代码

HarmonyModulesPlugin

// webpack对不同的依赖模块会有不同的模版处理
// ES module最终会给 HarmonyModulesPlugin 里面的依赖来处理
// CommonJS Module 的会给 CommonJsPlugin 里面的依赖处理 对其他的模块也有处理
// 我们以Esmodule为例分析 其他的跳过

// 1. 这些hook是什么时候注册的
还是和之前一样 我们在执行webpack的时候 会执行 WebpackOptionsApply 里面是做了很多事的
new ConstPlugin().apply(compiler);
new ImportPlugin(options.module).apply(compiler);
new HarmonyModulesPlugin(options.module).apply(compiler);
new APIPlugin().apply(compiler);
new CommonJsPlugin(options.module).apply(compiler);

// 2.HarmonyModulesPlugin主要做了些啥
// 引入一些列的 不同语法的依赖类型
const HarmonyInitDependency = require("./HarmonyInitDependency");
...
// 不同的语法在编译过程中挂载的hooks
const HarmonyDetectionParserPlugin = require("./HarmonyDetectionParserPlugin");
const HarmonyImportDependencyParserPlugin = require("./HarmonyImportDependencyParserPlugin");
const HarmonyExportDependencyParserPlugin = require("./HarmonyExportDependencyParserPlugin");

class HarmonyModulesPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      "HarmonyModulesPlugin",
      (compilation, { normalModuleFactory }) => {
        // 设置工厂和模块
        compilation.dependencyFactories.set(
          HarmonyImportSpecifierDependency,
          normalModuleFactory
        );
        compilation.dependencyTemplates.set(
          HarmonyImportSpecifierDependency,
          new HarmonyImportSpecifierDependency.Template()
        );
        const handler = (parser, parserOptions) => {
           // 判断 esmodule
           new HarmonyDetectionParserPlugin().apply(parser);
          // 处理import
          new HarmonyImportDependencyParserPlugin(this.options).apply(parser);
          // 处理export
          new HarmonyExportDependencyParserPlugin(this.options).apply(parser);
        };

        // 注册parser钩子 创建新的 normalModule 时钩子会被执行
        // handler会初始化各种plugin 注册相关的hooks
        normalModuleFactory.hooks.parser
          .for("javascript/auto")
          .tap("HarmonyModulesPlugin", handler);
      }
    );
  }
}

复制代码

HarmonyDetectionParserPlugin

class HarmonyDetectionParserPlugin {
  apply(parser) {
    parser.hooks.program.tap("HarmonyDetectionParserPlugin", (ast) => {
      // const isHarmony 判断import export的声明
      const module = parser.state.module;
      const compatDep = new HarmonyCompatibilityDependency(module);
      // 增加依赖
      module.addDependency(compatDep);
      const initDep = new HarmonyInitDependency(module);
      module.addDependency(initDep);
    });
  }
}
复制代码

import export

// 我们些代码 es module 一般是 import 和 export 语法
// 但是也会有多种写法 对应ast不同的称谓  之后学习babel的时候在重要学习 这里先简单有点印象
import x1 from './xx' 
import {x2} from './xx' 
import * as x3 from './xx'
复制代码

WX20210617-092838@2x.png

// export也会有很多不同的写法 感觉平时主要是 默认导出和具名导出
// import中type是一样的 export的type是不一样的 之后在处理ast的时候要注意
export default {name: 'name'}
export const a = 'a'
export function b() {}
复制代码

WX20210617-093225@2x.png

HarmonyImportDependencyParserPlugin

// 主要用来处理import的
class HarmonyImportDependencyParserPlugin {
  apply(parser) {
    // import的钩子
    parser.hooks.import.tap(
      "HarmonyImportDependencyParserPlugin",
      (statement, source) => {
        // 根据条件增加依赖
        parser.state.module.addDependency(clearDep);
        parser.state.module.addDependency(sideEffectDep);
      }
    );
    parser.hooks.importSpecifier.tap()
    parser.hooks.expression()
  }
}
复制代码

HarmonyExportDependencyParserPlugin

// 主要是用来处理export
class HarmonyExportDependencyParserPlugin {
  apply(parser) {
    // export 在parse的过程中会触发各种的hook
    parser.hooks.export.tap(
      "HarmonyExportDependencyParserPlugin",
      (statement) => {
        // new HarmonyExportHeaderDependency() 不同的依赖
        parser.state.current.addDependency(dep);
      }
    );
    parser.hooks.exportImport.tap();
    parser.hooks.exportExpression.tap();
    parser.hooks.exportSpecifier.tap();
    parser.hooks.exportImportSpecifier.tap();
  }
}
复制代码

demo

// index.js 我们些一个demo 先只考虑 es module的情况 不分析 commonjs规范
// 在 https://astexplorer.net/ 中查看ast的结构
import sync from "./sync";
console.log(sync);
import(/*webpackChunkName: 'async'*/ "./async").then((result) => {
  console.log(result.default);
});
复制代码

ast.png

// 我们得到的ast语法树 body 中有三个  我们会遍历处理这里面的节点

const obj = {
  // 第一个是import
  "type": "ImportDeclaration",
  "specifiers": [
    {
      "type": "ImportDefaultSpecifier",
      "local": { "type": "Identifier", "name": "sync"}
    }
  ],
  "source": { "type": "Literal",  "value": "./sync",  "raw": "\"./sync\""}
},
// 后面两个都是 表达式
const obj1 = {
  "type": "ExpressionStatement",
  "expression": {
    "type": "CallExpression",
    "callee": {
      "type": "MemberExpression",
      "object": {  "type": "Identifier",  "name": "console"  },
      "property": { "type": "Identifier",  "name": "log"  },
    },
    "arguments": [
      { "type": "Identifier",  "name": "sync"  }
    ]
  }
}

const obj2 = {
  "type": "ExpressionStatement",
  "expression": {
  "type": "CallExpression",
  "callee": {
    "type": "MemberExpression",
    "object": {
      "type": "ImportExpression",
      "source": { "type": "Literal",  "value": "./async",  "raw": "\"./async\""  }
    },
    "property": {"type": "Identifier",  "name": "then" },
  },
  "arguments": [
    {
      "type": "ArrowFunctionExpression",
      "params": [  { "type": "Identifier",  "name": "result" }  ],
      "body": { "type": "BlockStatement",  "body": [  /** console和上面一样的  */ ]
        }
      }
    ]
  }
}
复制代码

parse

// parse的过程是直接使用了 Parser.parse 生成语法树
// 遍历的工具 babel有 @babel/traverse esprima 是有 estraverse acorn自己处理
// 上面的过程主要是经过5个步骤 我们一个一个来看
this.hooks.program.call(ast, comments)
this.detectMode(ast.body);
this.prewalkStatements(ast.body);
this.blockPrewalkStatements(ast.body);
this.walkStatements(ast.body);
复制代码

1.program

// this.hooks.program.call(ast, comments)
// 会触发 HarmonyDetectionParserPlugin 中注册的钩子
// 根据条件给我们增加依赖
HarmonyCompatibilityDependency
HarmonyInitDependency
复制代码

2.detectMode

// 这一步主要是处理 严格模式 scope 的逻辑跳过
if (isLiteral && statements[0].expression.value === "use strict") {
    this.scope.isStrict = true;
}
复制代码

3.prewalkStatements

// this.prewalkStatements(ast.body);
// 1. 遍历执行
this.prewalkStatement(statement);
// 2. 根据不同的类型执行不同的分支 我们demo中的三个 type为 ImportDeclaration 和 ExpressionStatement
// prewalkStatement不处理 ExpressionStatement 的类型 主要还是在处理 import和export
// ast.body的第一个节点会进入
function prewalkStatement(statement) {
  switch (statement.type) {
    case "ImportDeclaration":
      this.prewalkImportDeclaration(statement);
      break;
  }
}
// 3.处理import prewalkImportDeclaration import 又会根据 specifiers 分不同的类型
function prewalkImportDeclaration(statement) {
  for (const specifier of statement.specifiers) {
    const name = specifier.local.name;
    switch (specifier.type) {
      case "ImportDefaultSpecifier":
        // 不同的类型会触发不同的 hooks
        this.hooks.importSpecifier.call(statement, source, "default", name);
        break;
      case "ImportSpecifier":
        this.hooks.importSpecifier.call(
          statement,
          source,
          specifier.imported.name,
          name
        );
        break;
      case "ImportNamespaceSpecifier":
        this.hooks.importSpecifier.call(statement, source, null, name);
        break;
    }
  }
}
// 4. 触发hooks执行 HarmonyImportDependencyParserPlugin 增加依赖
const clearDep = new ConstDependency("", statement.range);
const sideEffectDep = new HarmonyImportSideEffectDependency()
parser.state.module.addDependency(clearDep);
parser.state.module.addDependency(sideEffectDep);

// 5.又新增了两个依赖
HarmonyCompatibilityDependency
HarmonyInitDependency 

ConstDependency 
HarmonyImportSideEffectDependency 
复制代码

4.blockPrewalkStatements

// this.blockPrewalkStatements(ast.body);
// 1. 同理遍历执行 blockPrewalkStatement
this.blockPrewalkStatement(statement);
// 2.判断statement的type类型 很明显我们的demo没有对应的分支流程
function blockPrewalkStatement(statement) {
  switch (statement.type) {
    case "VariableDeclaration":
      this.blockPrewalkVariableDeclaration(statement);
      break;
    case "ExportDefaultDeclaration":
      this.blockPrewalkExportDefaultDeclaration(statement);
      break;
    case "ExportNamedDeclaration":
      this.blockPrewalkExportNamedDeclaration(statement);
      break;
    case "ClassDeclaration":
      this.blockPrewalkClassDeclaration(statement);
      break;
  }
}
复制代码

5. walkStatements

// this.walkStatements(ast.body);
// 1.还是遍历执行
this.walkStatement(statement);
// 2. 也是判断type的不同类型
// 在我们的case中 body第一个节点不走这个分支 后面两个都是ExpressionStatement
function walkStatement(statement) {
  switch (statement.type) {
    case "ExpressionStatement":
      this.walkExpressionStatement(statement);
      break;
  }
}
// 3. walkExpressionStatement 其实是处理expression
this.walkExpression(statement.expression);
// 4.回顾下ast的结构
// type callee arguments
// 5. 判断不同的类型 我们的demo都是CallExpression
function walkExpression(expression) {
  switch (expression.type) {
    case "CallExpression":
      this.walkCallExpression(expression);
      break;
  }
}
// 6. 执行 walkCallExpression
function walkCallExpression(expression) {
  // 判断	expression.callee.type 的类型
  if (
    expression.callee.type === "MemberExpression" &&
    expression.callee.object.type === "FunctionExpression"
  ) {
  } else if (expression.callee.type === "FunctionExpression") {
  } else if (expression.callee.type === "Import") {
  } else {
    // 计算函数表达式
    const callee = this.evaluateExpression(expression.callee);
    if (callee.isIdentifier()) {
      // 判断是不是标识符
      const callHook = this.hooks.call.get(callee.identifier);
      if (callHook !== undefined) {
        // 执行callHook
        let result = callHook.call(expression);
      }
      const callAnyHook = this.hooks.callAnyMember.get(identifier);
      if (callAnyHook !== undefined) {
        let result = callAnyHook.call(expression);
      }
    }
    // 处理callee和arguments
    if (expression.callee) this.walkExpression(expression.callee);
    if (expression.arguments) this.walkExpressions(expression.arguments);
  }
}
// 7.执行walkExpression(expression.callee)
function walkExpression(expression) {
  switch (expression.type) {
    case "MemberExpression":
      this.walkMemberExpression(expression);
      break;
  }
}
// 8. walkMemberExpression
function walkMemberExpression(expression) {
  const exprName = this.getNameForExpression(expression);
  const expressionHook = this.hooks.expression.get(exprName.name);
  const expressionAnyMemberHook = this.hooks.expressionAnyMember.get();
  // 深度遍历执行object
  this.walkExpression(expression.object);
}
// 9.我们的object中是Identifier
function walkExpression(expression) {
  switch (expression.type) {
    case "Identifier":
      this.walkIdentifier(expression);
      break;
  }
}
// 10. 我们执行walkIdentifier
function walkIdentifier() {
  // 会判断作用域
  // if (!this.scope.definitions.has(expression.name)) { }
  const hook = this.hooks.expression.get(); // 有就执行
  const result = hook.call(expression);
}

// 11.处理完callee我们就要处理arguments 是多个 需要遍历处理
// 增加了一个依赖 HarmonyImportSpecifierDependency

// 12. 处理完ast.body中第二个元素之后 需要处理第三个元素
// 在处理callee的时候 object的type为ImportExpression
// 在处理arguments时为 ArrowFunctionExpression

// 13.处理callee
this.walkExpression(expression.object);
this.walkCallExpression(expression);
// 14. type为ImportExpression
walkCallExpression(expression) {
  // type为import 执行对应的hooks 进入到 ImportParserPlugin 插件中
  let result = this.hooks.importCall.call(expression);
}
复制代码

acorn

// webpack的ast和我们在网站上生成的代码有一些差异
// 当webpack在使用acorn的时候 有一些默认的参数 
// ecmaVersion: 11, sourceType: "module", acorn的版本也有一定的差异
// 主要看第二个节点的callee部分结构如下
Node {
  type: 'CallExpression',
  start: 47,
  end: 64,
  loc: SourceLocation {
    start: Position { line: 4, column: 0 },
    end: Position { line: 4, column: 17 }
  },
  range: [ 47, 64 ],
  callee: Node {
    type: 'Import',
    start: 47,
    end: 53,
    loc: SourceLocation { start: [Position], end: [Position] },
    range: [ 47, 53 ]
  },
  arguments: [
    Node {
      type: 'Literal',
      start: 54,
      end: 63,
      loc: [SourceLocation],
      range: [Array],
      value: './async',
      raw: '"./async"'
    }
  ]
}
复制代码

ImportParserPlugin

// 在webpackOptionsApply中  
new ImportPlugin(options.module).apply(compiler)
// ImportPlugin中会引入
const ImportParserPlugin = require("./ImportParserPlugin");
const ImportDependency = require("./ImportDependency");
// 也是设置依赖和模版 然后执行handler
class ImportPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      "ImportPlugin",
      (compilation, { contextModuleFactory, normalModuleFactory }) => {
        compilation.dependencyFactories.set(
          ImportDependency,
          normalModuleFactory
        );
        compilation.dependencyTemplates.set(
          ImportDependency,
          new ImportDependency.Template()
        );
        const handler = (parser, parserOptions) => {
          new ImportParserPlugin(options).apply(parser);
        };
        // 处理js
        normalModuleFactory.hooks.parser
          .for("javascript/auto")
          .tap("ImportPlugin", handler);
      }
    );
  }
}
复制代码

ImportParserPlugin

class ImportParserPlugin {
  apply(parser) {
    parser.hooks.importCall.tap("ImportParserPlugin", (expr) => {
      const depBlock = new ImportDependenciesBlock();
      // 增加block
      parser.state.current.addBlock(depBlock);
    });
  }
}
复制代码

dependencies

// 经过上面ast的处理 我们当前模块增加了5个依赖 对应不同的模板
// 这些依赖时做什么的 暂时还是不清楚的
HarmonyCompatibilityDependency // 用于定义 exports:__esModule
HarmonyInitDependency  // 

ConstDependency  // clean操作 删除
HarmonyImportSideEffectDependency  // 模版

HarmonyImportSpecifierDependency // 模版
复制代码

blocks

// blocks属性 对应我们 动态import的模块
// ImportDependenciesBlock 
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享