最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • webpack4流程分析4

    正文概述 掘金(dream_99517)   2021-07-10   487

    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'
    

    webpack4流程分析4

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

    webpack4流程分析4

    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);
    });
    

    webpack4流程分析4

    // 我们得到的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 
    

    起源地下载网 » webpack4流程分析4

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元