Webpack 详解

作者: ixlei

https://segmentfault.com/a/1190000013657042

webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢?

webpack的配置

从上面我们可以看到,webpack配置中需要理解几个核心的概念 EntryOutputLoadersPluginsChunk

  • Entry:指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库

  • Output:告诉webpack如何命名输出的文件以及输出的目录

  • Loaders:由于webpack只能处理javascript,所以我们需要对一些非js文件处理成webpack能够处理的模块,比如sass文件

  • Plugins: Loaders将各类型的文件处理成webpack能够处理的模块, plugins有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的 UglifyJsPlugin插件

  • Chunk:coding split的产物,我们可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我们都利用 CommonsChunkPlugin将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中 CommonsChunkPlugin被废弃,使用 SplitChunksPlugin

webpack详解

读到这里,或许你对webpack有一个大概的了解,那webpack 是怎么运行的呢?我们都知道,webpack是高度复杂抽象的插件集合,理解webpack的运行机制,对于我们日常定位构建错误以及写一些插件处理构建任务有很大的帮助。

不得不说的tapable

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的 Compiler和负责创建bundles的 Compilation都是Tapable的实例。在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括:

  • plugin(name:string,handler:function)注册插件到Tapable对象中

  • apply(…pluginInstances:(AnyPlugin|function)[])调用插件的定义,将事件监听器注册到Tapable实例注册表中

  • applyPlugins*(name:string,…)多种策略细致地控制事件的触发,包括 applyPluginsAsync、 applyPluginsParallel等方法实现对事件触发的控制,实现

(1)多个事件连续顺序执行

(2)并行执行

(3)异步执行

(4)一个接一个地执行插件,前面的输出是后一个插件的输入的瀑布流执行顺序

(5)在允许时停止执行插件,即某个插件返回了一个 undefined的值,即退出执行

我们可以看到,Tapable就像nodejs中 EventEmitter,提供对事件的注册 on和触发 emit,理解它很重要,看个栗子:比如我们来写一个插件

在webpack的生命周期中会适时的执行:

当然上面提到的Tapable都是1.0版本之前的,如果想深入学习,可以查看Tapable和事件流(https://segmentfault.com/a/1190000008060440)。

那1.0的Tapable又是什么样的呢?1.0版本发生了巨大的改变,不再是此前的通过 plugin注册事件,通过 applyPlugins*触发事件调用,那1.0的Tapable是什么呢?

暴露出很多的钩子,可以使用它们为插件创建钩子函数

我们来看看怎么使用。

对于所有的hook的构造函数均接受一个可选的string类型的数组。

对于一个 SyncHook,我们通过 tap来添加消费者,通过 call来触发钩子的顺序执行。

对于一个非 sync*类型的钩子,即 async*类型的钩子,我们还可以通过其它方式注册消费者和调用

通过上面的栗子,你可能已经大致了解了 Tapable的用法,它的用法:

  • 插件注册数量

  • 插件注册的类型(sync, async, promise)

  • 调用的方式(sync, async, promise)

  • 实例钩子的时候参数数量

  • 是否使用了 interception

Tapable详解

对于 Sync*类型的钩子来说:

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。

  • 只能使用 tap注册,不能使用 tapPromise和 tapAsync注册

对于 Async*类型钩子:支持 taptapPromisetapAsync注册。