webpack的核心功能是经过抽离出不少插件来实现的,所以系统内功能的划分粒度很细,这样作到了完美解偶同时又分工明确,代码容易维护。能够说插件就是webpack的基石,这些基石又影响着流程的走向。这些钩子是经过Tapable串起来的,能够类比Vue框架的生命周期,webpack也有本身的生命周期,在周期里边会顺序地触发一些钩子,挂载在这些钩子上的插件得以执行,从而进行一些特定的逻辑处理。在插件里边,构建的实体或构建出来的数据结果都是可触达的,这样作实现了webpack的高度可扩展。了解了这些以后,咱们就再也不怀疑webpack是如何拥有如此丰富的生态体系及社区、如何达到了今天的高度。webpack
Compiler对象就是webpack的实体(是Tapable的实例),掌控者整个webpack的生命周期,他不执行具体的任务,只是进行一些调度工做(调兵遣将)。他建立了Compilation对象,Compilation任务执行完毕后会将最终的处理结果返回给Compiler。官网列出了该对象暴露出的全部钩子。git
Compilation是编译阶段的主要执行者,(是Tapable的实例),执行模块建立、依赖收集、分块、打包等主要任务的对象。官网列出了该对象暴露出的全部钩子。github
在第二章节,咱们了解到获取到配置数据以后,启动了compiler.run方法。其实在compiler启动run以前,在webpack.js咱们会发现还作了不少初始化操做,好比增长默认操做,好比针对不一样的配置项(如target、devtool)初始化相应的插件等。web
webpack支持传入多个配置对象,好比一个library有多个构建目标,就须要传入多个配置对象,每一个配置对象都会执行。若是传入一个数组,初始化的就不是Compiler,而是MultiCompiler,最后会运做MultiCompiler上的run方法,在里边遍历compilers对象,存放着Compiler数组,而后会依次调用Conmpiler的run方法。api
compiler的启动是run方法,run方法里边主要关注两个动做:调用了compile方法;声明了调用compile传入的回调函数onCompiled。
复制代码
compile()数组
涉及webpack构建生命周期的几个重要钩子:promise
onCompiled()markdown
涉及webpack构建生命周期的最后几个重要钩子:emit、done。该方法至关于将Compilation的权限又收取回来。此时拿到的compilation对象是聚集了通过module解析、loader处理、template编译后的全部资源文件。
该方法里边主要调用了emitAssets方法,该方法调用了emit钩子(这一步咱们能够获取完整的构建数据),获取compilation构建出来的全部的assets资源数据,里边递归的调用writeOut写入最终的chunk文件,并调用done钩子。框架
compilation开始于addEntry方法并结束于addEntry。
复制代码
上述阶段完成后,cimpiler调用了compilation的finish()、seal(),这里咱们重点关注seal方法。async
为方便源码的学习,想获取webpack执行过程当中钩子的挂载及触发状况,改造了Tapable。主要是修改Hook.js文件。顶部须要引入fs库。
const fs = require('fs') 复制代码
_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); // ... } 复制代码
// 改造insert方法,在方法最后插入一条语句 _insert(item){ fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/taps.js', `${item.type}: ${item.name} \n`, { 'flag': 'a' }, () => {}) } 复制代码
至此,咱们大体理了一下webpack构建的脉络。webpack体系很是庞大,内部封装了不少webpack本身的库。学习webpack源码的目的一个是学习好的构建思想,一个是方便本身在业务中开发插件,这里有源码注释版及其余资料可供拓展。