你想要的——webpack构建过程分析

提及webpack,相信对于前端工程师们而言早已经不是什么新鲜的事物。可是因为webpack有着较为复杂和灵活的配置项,因此给人的第一感受是难以彻底掌握。前端

此次就跟你们分享一下有关webpack构建过程的相关知识,但愿对你们进一步理解webpack有所帮助。webpack

本次分析的对象是webpack(v3.6.0),这是gayhub地址 摸我git

因为webpack的内容实在太多,一会儿讲太多东西容易懵逼,咱们此次就从最最最简单的例子开始讲起。github

如下是我写的一个炒鸡简单的例子:web

// webpack.config.js

var path = require("path");
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "bundle.js"
  }
}

假设咱们的工做目录下有一个这样的webpack.config.js文件,那么当咱们执行webpack命令的时候【前提是你已经安装了webpack】,那么首先会执行的是 /bin 目录下的webpack.js前端工程师

OK,做为入口文件,咱们首先要分析的就是: /bin/webpack.jsapp

这个文件主要作了如下几件事:函数

  • 引入yargs模块,对命令行参数进行注释和重命名等工做
  • 添加处理默认参数(好比当咱们没有指定--config参数的时候,默认去找当前目录下的 webpack.config.js文件做为配置文件)
  • 处理完参数相关的内容以后,就引入lib目录下的webpack.js文件,获得编译对象compiler,而后进行编译(compiler.run())
var webpack = require("../lib/webpack.js");
...
...
try {
    // 这里的options就是转换以后的参数,convert-argv文件主要负责这些工做
    // 获得compiler对象
    compiler = webpack(options);
}catch(e) {
    ...
    ...
}
...
...
// 执行编译
compiler.run(compilerCallback)

因为这里讲的例子比较简单,不涉及到其余参数相关的内容,感兴趣的同窗能够进一步了解参数处理的部分,这里就很少作解释了。重点咱们仍是看一下这个编译的过程。ui

做为complier的入口文件,咱们接下来看一下: /lib/webpack.jsthis

  • 这个文件首先调用validateSchema方法对传入的options进行格式校验
  • 而后又调用了WebpackOptionsDefaulter实例的process方法对options进一步的处理(保存了两个实例属性 default={} , config= {},分别往这两个对象上面挂属性,而后再挂载到options上)
  • 而后实例化Compiler类,因为这个类继承自Tapable类,因此他具备父类上实现插件的一套机制(applyPlugin,plugin等方法,后面会具体分析这两个类),获得compiler对象
  • 而后执行NodeEnvironmentPlugin插件,主要是使用enhanced-resolve模块修饰compiler对象,注册“before-run”回调方法
  • 而后调用complier的apply方法,内部其实调用插件的apply方法,至关于注册各类插件的回调方法
  • 触发“environment” 和 “after-environment” 回调方法
  • 实例化WebpackOptionsApply类,调用process方法;后面咱们会展开分析这个方法
  • 往webpack这个方法上挂一下静态属性(各类插件方法)
  • 导出webpack这个方法

接下来咱们先分析WebpackOptionsApply类:/lib/WebpackOptionsApply.js

从上面看到,在编译以前,webpack会先实例化WebpackOptionsApply类,而后调用其process方法
咱们看到process方法其实就是注册了N多个插件,而后触发了某些插件的回调函数

  • 首先判断options.target,若是值为“web”的话(这种状况是最多见的,其余状况的逻辑也是相似的),则注册插件JsonpTemplatePlugin【注册“this-compilation”回调】,FunctionModulePlugin【注册“compilation”回调】,NodeSourcePlugin【注册“compilation” & “after-resolver”回调】,LoaderTargetPlugin【注册“compilation”回调】。

  • 注册插件LibraryTemplatePlugin【注册“compilation”回调】,ExternalsPlugin【注册“compile”回调】
  • 注册插件EntryOptionPlugin【注册“entry-option”回调】
  • 触发“entry-option”回调,因此进入了EntryOptionPlugin插件的回调函数
  • EntryOptionPlugin类中,经过itemToPlugin方法判断单入口仍是多入口文件,这里以单入口为例,因此进入了SingleEntryPlugin类中,注册插件SingleEntryPlugin【“compilation” & “make”回调】
  • 继续回到WebpackOptionsApply的process方法,而后又继续经过compile.apply方法添加插件,插件太多了,不一一列举,感兴趣的能够跟踪代码了解详情
  • 最后触发“after-plugins” 和 “after-resolvers” 的回调函数

以上就是WebpackOptionsApply实例调用process的全过程

在/bin/webpack中,获得的是/lib/webpack返回的compiler对象,最后调用compiler对象的run方法。

做为编译过程的核心类,咱们接下来看看Compiler这个类:/lib/Compiler.js

咱们看到Compiler类继承自Tapable类 github地址

Tapable类提供了一种调用插件的方式,webpack所有插件都是基于这种方式来注册和调用的。

Tapable类定义了plugin方法,用于注册插件,将插件及其回调函数以key-value的形式保存在内部_plugins={}对象中;
又定义了applyPlugins,applyPluginsWaterfall等方法来触发插件的回调函数。其实就是一个订阅-发布模式的实现。

因此当Compiler类继承Tapable类后,也一样具备注册插件和触发回调函数的功能。

接下来看看Compiler中的run方法

  • 首先触发的“before-run”回调函数,NodeEnvironmentPlugin插件注册了回调函数
  • 而后触发“run”回调函数,CachePlugin插件注册了回调函数
  • 调用readRecords方法()
  • 调用compile方法,进入compile过程

    1. 触发“before-compile”回调函数,DllReferencePlugin注册了回调函数
    2. 触发“compile”回调函数,ExternalsPlugin & DllReferencePlugin & DelegatedPlugin注册了回调函数
    3. 调用newCompilation方法,建立Compilation实例,这个实例包含了编译过程的全部属性和方法
    4. 触发“this-compilation”回调函数
    5. 触发“compilation”回调函数
    6. 触发“make”回调函数

      1. 若是是单入口项目,这里就会触发SingleEntryPlugin插件注册的“make”回调,其中调用了compilation的addEntry方法进行模块构建
      2. 经过compilation的processModuleDependencies方法收集模块的依赖
      3. 最后经过buildModule方法构建模块
    7. 调用compilation.finish()方法
    8. 调用compilation.seal()方法
    9. 触发“after-compile”回调函数
  • compile方法执行完以后,就执行onCompiled回调
  • 触发“should-emit”回调函数
  • 触发“done”回调函数
  • 调用emitAssets方法,触发了“emit”回调函数
  • 调用emitFiles方法,触发“after-emit”回调函数
  • 最后执行emitRecords方法

这就是compiler的run方法的主要内容分析

以上就是webpack简单的构建流程的分析。哈哈今天就分享到这里,但愿你们喜欢,祝你们周末愉快啊。。。

相关文章
相关标签/搜索