调试

在vscode 中创建调试文件,进行debug, 点击debug,自动创建即可

{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [

    {    
        "type": "node",   
          "request": "launch",   
          "name": "debugWebpack",   
          "program": "${workspaceFolder}/bin/webpack.js",  
          "console": "integratedTerminal",   
          "cwd": "${workspaceFolder}",   
          "args": [        
            //  "${workspaceFolder}/examples/examples.js",   
            // webpack example 目录,我们选其中一个进行调试即可, 详细 example/README.md
            "${workspaceFolder}/examples/commonjs/example.js",   
        ]
    }
  ]
}

接之前一篇文章, 创建compiler

  1. 接下来可以配合官方文档进行阅读compiler hook按照官方顺序就是compiler整个生命周期

梳理 compiler 生命周期

  1. 首先进行环境初始化, 即compiler.hook.environment.call(), 但全局搜索没有做订阅
  2. 环境初始化后会到 compiler.hook.afterEnvironment.call(), 说明环境初始化完成, IgnoringWatchFileSystem, 应该是做忽略某些文件的监听
  3. lib/webpack 中的 new WebpackOptionsApply().process(options, compiler); 这里其实是对 option 的配置进行plugin 的加载,且对compiler 对应周期进行注册监听事件。
    所以这里会经历一个 entryOption 的周期,
    例如:entry 入口,
// 处理入口文件, SyncBailHook , 当返回为非undefined时停止往下执行
new EntryOptionPlugin().apply(compiler);
// 执行入口文件回调
compiler.hooks.entryOption.call(options.context, options.entry);
// EntryOptionPlugin
class EntryOptionPlugin {
    apply(compiler) {
        compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
            EntryOptionPlugin.applyEntryOption(compiler, context, entry);
            return true;
        });
    }
  /** 省略其余code */
}

其余配置也差不多, 然后会执行compiler.hooks.afterPlugins.call(compiler); 说明所有的插件已经加载完毕。
最后调用compiler.hooks.afterResolvers.call(compiler); 表示compiler 中3种类型的解析器已经全部设置完成,new WebpackOptionsApply().process(options, compiler); 执行完毕

  1. 然后 执行 compiler.hook.initialize.call()
  2. 最后回到 lib/webpack 执行 compiler.run()

Compiler

上面已经通过 new Compiler(options.context) 创建compiler, 然后调用compiler.run()
进入lib/compiler.js 可以看到创建了多个hooks, 关于tapable, 我觉得这篇文章挺好

  1. run 方法执行, 经历了两个hooks (hooks.beforeRun, hooks.run), 最后执行 this.compiler()
compile(callback) {
  // 创建 normalFactory, 跟 contextFactory, 创建完成之后调用 hooks.normalModuleFactory.call(); hooks.contextModuleFactory.call()
  const params = this.newCompilationParams();
  this.hooks.beforeCompile.callAsync(params, err => {
    if (err) return callback(err);

    this.hooks.compile.call(params);

    const compilation = this.newCompilation(params);

    const logger = compilation.getLogger("webpack.Compiler");

    logger.time("make hook");
    this.hooks.make.callAsync(compilation, err => {
      logger.timeEnd("make hook");
      if (err) return callback(err);

      logger.time("finish make hook");
      this.hooks.finishMake.callAsync(compilation, err => {
        logger.timeEnd("finish make hook");
        if (err) return callback(err);

        process.nextTick(() => {
          logger.time("finish compilation");
          compilation.finish(err => {
            logger.timeEnd("finish compilation");
            if (err) return callback(err);

            logger.time("seal compilation");
            compilation.seal(err => {
              logger.timeEnd("seal compilation");
              if (err) return callback(err);

              logger.time("afterCompile hook");
              this.hooks.afterCompile.callAsync(compilation, err => {
                logger.timeEnd("afterCompile hook");
                if (err) return callback(err);

                return callback(null, compilation);
              });
            });
          });
        });
      });
    });
  });
}

2.创建 NormalModuleFactory, 跟 contextModuleFactory, 创建完成之后调用 hooks.normalModuleFactory.call(),hooks.contextModuleFactory.call()
NormalModuleFactory 用来生成模块的

  1. 之后调用 hooks.beforeCompilebeforeCompileLazyCompilationPlugin 下注册了, 这里跟hmr 热更新相关。 同时在DllReferencePlugin, 它是用来拆分bundle,提升构建速度的 具体可以看这

  2. 然后调用 hook.compile, 在这DllReferencePlugin, ExternalsPlugin, DelegatedPlugin三个文件中注册了方法

  3. 创建 compilation 实例,该实例可以用于factorizeModule, buildModule, addModule, 以及对module的依赖收集, 执行 compiler.hooks.thisCompilation.call 以及
    compiler.hooks.compilation.call

  4. 执行compiler.hooks.make 正式进入到编译阶段

    1. 找到 tapAsync 函数, 通过调用addEntry 添加入口文件

      compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
      compilation.addEntry(context, dep, options, err => {
      callback(err);
      });
      });
    2. addEntry(context, entry, optionsOrName, callback) {
        // TODO webpack 6 remove
        const options =
            typeof optionsOrName === "object"
                ? optionsOrName
                : { name: optionsOrName };
      
        this._addEntryItem(context, entry, "dependencies", options, callback);
      }
    3. 然后 _addEntryItem, 会判断是否是多入口等等,然后执行compilation.hooks.addEntry.call 表示添加入口文件完毕, 最后执行addModuleTree

      this.addModuleTree(
      {
      context,
      dependency: entry,
      contextInfo: entryData.options.layer
        ? { issuerLayer: entryData.options.layer }
        : undefined
      },
      (err, module) => {
      if (err) {
        this.hooks.failedEntry.call(entry, options, err);
        return callback(err);
      }
      this.hooks.succeedEntry.call(entry, options, module);
      return callback(null, module);
      }
      );
    4. 来看看 addModuleTree 函数, addModuleTree 里面会执行一个 handleModuleCreation

    const Dep = /** @type {DepConstructor} */ (dependency.constructor);
    // 这里会找到直接找到normalModuleFactory
    const moduleFactory = this.dependencyFactories.get(Dep);
    this.handleModuleCreation(
             {
                 factory: moduleFactory,
                 dependencies: [dependency],
                 originModule: null,
                 contextInfo,
                 context
             },
             (err, result) => {
                 if (err && this.bail) {
           // error
                 } else if (!err && result) {
                     callback(null, result);
                 } else {
                     callback();
                 }
             }
         );
    1. 然后 handleModuleCreation 会执行 factorizeModule, 先不用管回调
    this.factorizeModule(
     {
       currentProfile,
       factory,
       dependencies,
       factoryResult: true,
       originModule,
       contextInfo,
       context
     },
     (err, factoryResult) => {
       /** 省略部分代码, 只需要知道后续会进行 this.addModule(newModule, () => {}) */
       const newModule = factoryResult.module;
       this.addModule(newModule, () => {
         // 下面会执行模块创建以及 依赖收集
         this._handleModuleBuildAndDependencies(
           originModule,
           module,
           recursive,
           callback
         );
       })
     }
    )
    1. this.factorizeModule 其实就是简单的将当前模块 添加到一个异步队列中

      // this.factorizeModule 通过new AsyncQueue 创建
      this.factorizeQueue.add(options, callback);
    2. factorizeQueue.add 它是一个异步执行 队列,当有一个任务加入到队列中,最后会执行setImmediate(root._ensureProcessing);,
      但值得注意的是root 这个参数设计的有点巧妙,是一种父子关系, this.processDependenciesQueue 的children 包含 this.addModuleQueue, this.factorizeQueue , this.buildQueue 最后会执行 setImmediate(root._ensureProcessing); 当前root 是this.processDependenciesQueue。 setImmediate 会在下一次事件循环中调用

      /** @type {AsyncQueue<Module, Module, Module>} */
        this.processDependenciesQueue = new AsyncQueue({
            name: "processDependencies",
            parallelism: options.parallelism || 100,
            processor: this._processModuleDependencies.bind(this)
        });
        /** @type {AsyncQueue<Module, string, Module>} */
        this.addModuleQueue = new AsyncQueue({
            name: "addModule",
            parent: this.processDependenciesQueue,
            getKey: module => module.identifier(),
            processor: this._addModule.bind(this)
        });
        /** @type {AsyncQueue<FactorizeModuleOptions, string, Module | ModuleFactoryResult>} */
        this.factorizeQueue = new AsyncQueue({
            name: "factorize",
            parent: this.addModuleQueue,
            processor: this._factorizeModule.bind(this)
        });
        /** @type {AsyncQueue<Module, Module, Module>} */
        this.buildQueue = new AsyncQueue({
            name: "build",
            parent: this.factorizeQueue,
            processor: this._buildModule.bind(this)
        });
    3. ensureProcessing 会遍历当前实例的children,简单说就是遍历 addModuleQueue, factorizeQueue, buildQueue 三个AsyncQueue 然后从这三个实例的队列中找任务去执行

      _ensureProcessing() {
      // this = this.processDependenciesQueue,
      // children 包含 this.addModuleQueue, this.factorizeQueue, this.buildQueue
        this._willEnsureProcessing = false;
        if (this._queued.length > 0) return;
        if (this._children !== undefined) {
            for (const child of this._children) {
                while (this._activeTasks < this._parallelism) {
                    const entry = child._queued.dequeue();
                    if (entry === undefined) break;
                    this._activeTasks++;
                    entry.state = PROCESSING_STATE;
                    child._startProcessing(entry);
                }
                if (child._queued.length > 0) return;
            }
        }
        if (!this._willEnsureProcessing) this._needProcessing = false;
      }
    4. child._startProcessing, 因为一直从入口过来, 此时的child 是factorize

      _startProcessing(entry) {
        this.hooks.beforeStart.callAsync(entry.item, err => {
            if (err) {
                // error 处理
            }
            let inCallback = false;
            try {
                // compliation._addModule 或者 compliation._factorizeModule 或者 compliation._buildModule
        // 此时调用的是 compliation._factorizeModule
                this._processor(entry.item, (e, r) => {
                    inCallback = true;
                    this._handleResult(entry, e, r);
                });
            } catch (err) {
                if (inCallback) throw err;
                this._handleResult(entry, err, null);
            }
            this.hooks.started.call(entry.item);
        });
      }
    5. _factorizeModule 调用了 NormalModuleFactory.create
      调用了 NormalModuleFactory.hooks.beforeResolve.callAsync, NormalModuleFactory.hooks.factorize.callAsync
      NormalModuleFactory.hooks.factorize.tapAsync中调用了 resolve.callAsync, 在 resolve.tapAsync 主要目的是resolve 模块找到模块对应的loader
      以及loader的路径,描述文件等,这里会执行 enhance-loader, 并且创建 parsegenerator 赋值到resolveData.createData

    Object.assign(data.createData, {
     layer:
       layer === undefined ? contextInfo.issuerLayer || null : layer,
     request: stringifyLoadersAndResource(
       allLoaders,
       resourceData.resource
     ),
     userRequest,
     rawRequest: request,
     loaders: allLoaders,
     resource: resourceData.resource,
     context:
       resourceData.context || getContext(resourceData.resource),
     matchResource: matchResourceData
       ? matchResourceData.resource
       : undefined,
     resourceResolveData: resourceData.data,
     settings,
     type,
     parser: this.getParser(type, settings.parser),
     parserOptions: settings.parser,
     generator: this.getGenerator(type, settings.generator),
     generatorOptions: settings.generator,
     resolveOptions
    });
    1. 继续执行 nmf.hooks.afterResolve.callAsyncnmf.hooks.createModule.callAsync
    this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
     const createData = resolveData.createData;
     this.hooks.createModule.callAsync(createData, resolveData,
             (err, createdModule) => {
         if(!createModule) {
           createdModule = new NormalModule(createData);
         }
         // SideEffectsFlagPlugin 这里做sideEffect
         createdModule = this.hooks.module.call(
           createdModule,
           createData,
           resolveData
         );
         // 执行完成后调用 hooks.factorize.callAsync 回调 传入当前module 信息, 也就是factory.create 的callback 再callback 中执行AsyncQueue.handleResult
         return callback(null, createdModule);
     })
    })
    1. AsyncQueue.handleResult 执行了 加进异步队列的 callback 即 6.5 的回调 addModule, 此时又做了一次6.6 之后的循环 只是改成了addModule

    2. addModule 经过上面一轮 后执行

      this._handleModuleBuildAndDependencies(
      originModule,
      module,
      recursive,
      callback
      );
    3. _handleModuleBuildAndDependencies 会执行 this.buildModule 再回调中会构建依赖

流程图