说起webpack,相信对于前端工程师们而言早已经不是什么新鲜的事物。但是由于webpack有着较为复杂和灵活的配置项,所以给人的第一感觉是难以完全掌握。
这次就跟大家分享一下有关webpack构建过程的相关知识,希望对大家进一步理解webpack有所帮助。
本次分析的对象是webpack(v3.6.0),这是gayhub地址 摸我
由于webpack的内容实在太多,一下子讲太多东西容易懵逼,我们这次就从最最最简单的例子开始讲起。
以下是我写的一个炒鸡简单的例子:
// 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.js
这个文件主要做了以下几件事:
- 引入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)
由于这里讲的例子比较简单,不涉及到其他参数相关的内容,感兴趣的同学可以进一步了解参数处理的部分,这里就不多做解释了。重点我们还是看一下这个编译的过程。
作为complier的入口文件,我们接下来看一下: /lib/webpack.js
- 这个文件首先调用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过程
- 触发“before-compile”回调函数,DllReferencePlugin注册了回调函数
- 触发“compile”回调函数,ExternalsPlugin & DllReferencePlugin & DelegatedPlugin注册了回调函数
- 调用newCompilation方法,创建Compilation实例,这个实例包含了编译过程的所有属性和方法
- 触发“this-compilation”回调函数
- 触发“compilation”回调函数
触发“make”回调函数
- 如果是单入口项目,这里就会触发SingleEntryPlugin插件注册的“make”回调,其中调用了compilation的addEntry方法进行模块构建
- 通过compilation的processModuleDependencies方法收集模块的依赖
- 最后通过buildModule方法构建模块
- 调用compilation.finish()方法
- 调用compilation.seal()方法
- 触发“after-compile”回调函数
- compile方法执行完之后,就执行onCompiled回调
- 触发“should-emit”回调函数
- 触发“done”回调函数
- 调用emitAssets方法,触发了“emit”回调函数
- 调用emitFiles方法,触发“after-emit”回调函数
- 最后执行emitRecords方法
这就是compiler的run方法的主要内容分析
以上就是webpack简单的构建流程的分析。哈哈今天就分享到这里,希望大家喜欢,祝大家周末愉快啊。。。