webpack编译流程
webpack编译module都是这么个调用流程:
- 将配置文件导出的对象作为webpack的参数,返回一个Compiler的实例compiler。这里会做很多参数处理、还有注册各种插件形成一个插件的事件流。
- 然后调用实例方法 compiler.run(callback),依次触发事件流,执行插件任务。
|
|
下面来看调用webpack(webpackConfig)都发生了什么?
|
|
这里主要做了几件事情:
- 设置配置参数的默认值
- new一个Compiler实例
- 调用实例方法compiler.apply(plugins)或plugin.apply(compiler)注册插件,比如将nodeEveironmentPlugin插件注册在before-run阶段
- 调用compiler.applyPlugins()触发插件执行,比如compiler.applyPlugins(‘before-run’),就会通知注册在before-run这个阶段的插件执行
- 最后导出webpack,以及webpack官方提供的一些插件
那下面看下Compiler里面有什么?Compiler有两个类: Watching和Compiler
- Compiler 存放输入输出相关信息、编译方法
- Watching 监听文件变化的一些处理方法
|
|
这里的核心在于Compiler类继承了Tapable,使用Tapable实现了事件发布订阅处理的插件架构。
前面讲到的compiler.apply(plugins)和compiler.applyPlugins(‘before-run’)方法都是继承自Tapable。我们来看下Tapable有什么来了解更多插件的发布订阅细节。
Tapable是一个典型的订阅发布模式的实现
|
|
目前来看,webpack已经根据参数配置注册了很多插件、并且注册了一些内部插件。但这里我们需要调用compiler.run()方法触发下一步操作:
|
|
执行run方法就开始了webpack的编译流程,显示异步触发了before-run
,执行完对应的插件回调后再触发run
。最后执行this.compile(onCompiled)。这是下一个重要步骤。
其实核心编译流程就在这里,更细节的流程在Compilation.js中,下面的代码可以看到在make
阶段传入了Compilation的实例compilation。
|
|
那我们要知道在make
这个阶段做了什么,查看初始化时注册插件的那段源码可以知道,在make
这个阶段根据配置文件中entry字段的值注册了对应的插件:SingleEntryPlugin、MultiEntryPlugin、DynamicEntryPlugin。entry配置与插件怎么对应的可以查看源码 EntryOptionPlugin.js
那我们webpack编译的入口从这几个入口插件开始,这里只看下 SingleEntryPlugin
|
|
前面看过注册插件时会调用插件的apply方法,注册某个阶段的执行函数。在这里可以看到注册在make
阶段的SingleEntryPlugin插件,在触发make
阶段时会做什么。当触发make
阶段时会执行这个函数:
|
|
在这里调用了 compilation.addEntry(this.context, dep, this.name, callback);
,就这样,webpack编译从入口模块开始了。
至于调用compilation.addEntry后干了什么,大概就是 解析模块、分析模块依赖、对每个模块用相应的loader处理。整个make
阶段处理完毕后进入seal
阶段,封装构建结果,最后进入emit阶段输出结果。
比较核心的几个文件和方法: webpack.js > Compiler.js(run > compile) > Compilation.js(addEntry)。 其中 Compiler 和 Compilation都继承了 Tapable.
编译过程大致经历的阶段以及在各阶段注册的插件:
阶段 | 插件 |
---|---|
before-run | NodeEnvironmentPlugin |
run | CachePlugin |
before-compile | DllReferencePlugin |
compile | DllReferencePlugin ExternalsPlugin DelegatedPlugin |
this-compilation | … |
compilation | … |
make | SingleEntryPlugin MultiEntryPlugin DynamicEntryPlugin |
build-module | SourceMapDevToolModuleOptionsPlugin |
finish-modules | … |
seal | … |
… | … |
optimize-chunks | CommonsChunkPlugin |
optimize-chunk-assets | UglifyJsPlugin |
… | … |
after-seal | … |
after-compile | … |
emit | LibManifestPlugin |
after-emit | SizeLimitsPlugin |
done | … |
有些插件会在不同的阶段都有注册不同的处理方法,比如 CachePlugin、ProgressPlugin。webpack编译过程分的很细致,提供很多个阶段,涉及很多的插件,就没有一一列出来了。
参考源码
- webpack/lib/webpack.js
- webpack/lib/Compiler.js
- webpack/lib/WebpackOptionsApply.js
- tapable-0.2/lib/Tapable.js
- webpack/lib/Compilation.js
插件开发
插件开发:
- 实现一个apply方法,以Compiler对象compiler作为参数,Compiler类继承Tapable
- 在apply方法中调用compiler.plugin(name,fn)注册插件,其中fn是订阅name的函数。(Compilation中的插件同理)
EntryOptionPlugin
|
|
常用插件
loader开发
loader主要接收一个模块文件,以及loader处理需要的参数,然后执行相应的处理逻辑返回新的模块文件。然后还要支持链式进入下一个loader处理,比如 style-loader!css-loader!less-loader这种,依次经过less-loader、css-loader、style-loader处理。
babel-loader
babel-loader最终调用的是babel-core提供的babel.transform(source, options),剩下的就是babel转码的事了。
|
|