webpack构建流程及梳理

摘要

webpack的核心功能是经过抽离出不少插件来实现的,所以系统内功能的划分粒度很细,这样作到了完美解偶同时又分工明确,代码容易维护。能够说插件就是webpack的基石,这些基石又影响着流程的走向。这些钩子是经过Tapable串起来的,能够类比Vue框架的生命周期,webpack也有本身的生命周期,在周期里边会顺序地触发一些钩子,挂载在这些钩子上的插件得以执行,从而进行一些特定的逻辑处理。在插件里边,构建的实体或构建出来的数据结果都是可触达的,这样作实现了webpack的高度可扩展。了解了这些以后,咱们就再也不怀疑webpack是如何拥有如此丰富的生态体系及社区、如何达到了今天的高度。webpack

关于Compiler

Compiler对象就是webpack的实体(是Tapable的实例),掌控者整个webpack的生命周期,他不执行具体的任务,只是进行一些调度工做(调兵遣将)。他建立了Compilation对象,Compilation任务执行完毕后会将最终的处理结果返回给Compiler。官网列出了该对象暴露出的全部钩子。git

关于Compilation

Compilation是编译阶段的主要执行者,(是Tapable的实例),执行模块建立、依赖收集、分块、打包等主要任务的对象。官网列出了该对象暴露出的全部钩子。github

在第二章节,咱们了解到获取到配置数据以后,启动了compiler.run方法。其实在compiler启动run以前,在webpack.js咱们会发现还作了不少初始化操做,好比增长默认操做,好比针对不一样的配置项(如target、devtool)初始化相应的插件等。web

webpack支持传入多个配置对象,好比一个library有多个构建目标,就须要传入多个配置对象,每一个配置对象都会执行。若是传入一个数组,初始化的就不是Compiler,而是MultiCompiler,最后会运做MultiCompiler上的run方法,在里边遍历compilers对象,存放着Compiler数组,而后会依次调用Conmpiler的run方法。api

Compiler

compiler的启动是run方法,run方法里边主要关注两个动做:调用了compile方法;声明了调用compile传入的回调函数onCompiled。
复制代码
  1. compile()数组

    涉及webpack构建生命周期的几个重要钩子:promise

    1. compile上的钩子:beforeCompile、compile、make(关键钩子)、afterCompile、thisCompilation、compilation
    2. compilation上的钩子:finish、seal
      在这个方法里边,主要是构建建立Compilation所须要的参数并建立了Compilation,这个参数就是后边解析module须要用的工厂函数。
      make钩子是一个关键的钩子,调用make钩子时传入的是新建的compilation对象,在这个钩子上挂载了一些入口插件的处理逻辑,这些入口插件里边调用compilation.addEntry(),此后,控制权就由Compiler转移到了Compilation。在make钩子的回调函数里边调用了compilation的finish、seal钩子。
  2. onCompiled()markdown

    涉及webpack构建生命周期的最后几个重要钩子:emit、done。该方法至关于将Compilation的权限又收取回来。此时拿到的compilation对象是聚集了通过module解析、loader处理、template编译后的全部资源文件。
    该方法里边主要调用了emitAssets方法,该方法调用了emit钩子(这一步咱们能够获取完整的构建数据),获取compilation构建出来的全部的assets资源数据,里边递归的调用writeOut写入最终的chunk文件,并调用done钩子。框架

Compilation

compilation开始于addEntry方法并结束于addEntry。
复制代码
  1. addEntry(): 根据入口的配置模式,分为单入口和多入口。该方法里边主要调用了_addModuleChain()。
  2. _addModuleChain(): 建立module。根据入口的不一样,使用不一样的模块工厂(从建立Compilation传入的参数中获取,包括ContextModuleFactory或NormalModuleFactory)的create方法建立模块,获取模块相关的parser、loader、hash等数据信息。
  3. buildModule(): 调用module.build()进行module构建。
  4. addModuleDependencies(): 构建成功以后,递归地获取依赖模块并构建(逻辑同_addModuleChain)。
  5. successEntry: 执行完上述的操做以后,在_addModuleChain的回调函数里边调用succeedEntry钩子,在这个钩子里边能够获取刚建立的module。而后将控制权返回给Compiler。

上述阶段完成后,cimpiler调用了compilation的finish()、seal(),这里咱们重点关注seal方法。async

  1. seal(): 该方法主要完成了chunk的构建。主要是收集modules、chunks,使用template(在Compilation的构建函里初始化了几种Template:MainTemplate、ChunkTemplate等)对chunk进行编译。entry属性配置的入口模块使用的是MainTemplate,里边会加入启动webpack多须要的代码,建立并更新hash信息等,并调用emitAsset()会将最终的资源文件所有收集在assets对象里边。
  2. emitAsset(): 收集assets资源数据,多个插件都有调用该方法。

Tapable改造

为方便源码的学习,想获取webpack执行过程当中钩子的挂载及触发状况,改造了Tapable。主要是修改Hook.js文件。顶部须要引入fs库。

const fs = require('fs')
复制代码
  1. 在call方法里插入逻辑
_fnCp(fn, name, type) {
      const _fn = (...arg) => {
            // console.log('hahaha:', arg)
            fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/calls.js', `${type}: ${name} \n`, { 'flag': 'a' },  () => {})
                  return fn(...arg)
            }
      return _fn
}
// 改造tap、tapAsync、tapPromise方法
tap(options, fn) {
      // ...
      // options = Object.assign({ type: "sync", fn: fn }, options);
      options = Object.assign({ type: "sync", fn: this._fnCp(fn, options.name, "sync") }, options);
      // ...
}
tapAsync(options, fn) {
      // ...
      // options = Object.assign({ type: "async", fn: fn }, options);
      options = Object.assign({ type: "async", fn: this._fnCp(fn, options.name, "async") }, options);
      // ...
}
tapPromise(options, fn) {
      // ...
      // options = Object.assign({ type: "promise", fn: fn }, options);
      options = Object.assign({ type: "promise", fn: this._fnCp(fn, options.name, "promise") }, options);
      // ...
}
复制代码
  1. 在tap方法插入逻辑
// 改造insert方法,在方法最后插入一条语句
_insert(item){
      fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/taps.js', `${item.type}: ${item.name} \n`, { 'flag': 'a' },  () => {})
}
复制代码

总结

至此,咱们大体理了一下webpack构建的脉络。webpack体系很是庞大,内部封装了不少webpack本身的库。学习webpack源码的目的一个是学习好的构建思想,一个是方便本身在业务中开发插件,这里有源码注释版及其余资料可供拓展。

相关文章
相关标签/搜索