通过解读 webpack-manifest-plugin,了解下 plugin 机制
先简单说一下这个插件的功能,生成一份资源清单的 json 文件,如下
如果是服务器端构造的 html,就可以根据当前的 manifest,引入 css 和 js 文件,而且这个文件是必须的,否则服务器端压根不知道 hash 之后的 JS 文件名字和 CSS 名字。
简单说下 webpack 执行,取得 webpack.config.js 的配置和默认配置合并, 然后执行 plugin,这里的执行其实只是简单的绑定 hooks,并非执行里面的逻辑,先看下源代码,再给大家撸一撸这里面的细节。
compiler 实例会作为参数传递,可以看到 new 之后,他就立刻去遍历的 plugin,然后 plugin.apply(compiler)去执行了当前的 plugin。难道说 plugin 在这里就执行了?说对也不对,看一段最简单的 plugin 的 demo
apply(compiler) {
compiler.hooks.compilation.tap(‘xxx’, (compilation) => {
do something
});
}
官网的 demo,用的 compiler.plugin, 但这个方法已经不推荐使用了,用 hooks 代替,更语义化一点。看上面的代码,apply 执行后,其实只是在对应的 hooks 上注册了一个方法而已,xxx 可以理解为一个 plugin 的标识。注册方法之后,当 webpack 执行了当前的 hooks,那么挂载正在当前 hooks 上的方法就会被执行。这个有点类似于发布订阅模式了。综上所述,webpack 在 compiler 被创建的之后,立刻就去遍历了 plugin,就是想要尽早的注册方法,否则挂载在一些 hooks 上的方法就没办法被正确触发。比如
在 webpack 开始编译之前,就能触发 beforeRun 钩子,webpack-manifest-plugin 就用到了当前的 hook。因此虽然 plugin 注册的早,但真正的执行顺序在于它绑定的到底是什么样的钩子。无关于它在 webpack 配置中 plugin 里面的顺序。
上面都是前置知识,下面通过解读一个 plugin 源码来巩固下。
先看一段简单的源码。这里他注册了好几个钩子,我们一个一个来说。compiler.hooks.webpackManifestPluginAfterEmit = new SyncWaterfallHook([‘manifest’]); 这里是自定义一个 hooks,webpack 允许自定义 hooks,这个 hooks 是干嘛的,这是给其他组件用的,意思就是,我注册了一个这样的 hooks,其他组件就能通过 tap 绑定对应的方法,仅此而已。
compiler.hooks.compilation.tap(pluginOptions, function (compilation) {
compilation.hooks.moduleAsset.tap(pluginOptions, moduleAsset);
});
hooks.compilation,compilations 是 compiler 众多的 hooks 的一个,官网的解释是:编译 (compilation) 创建之后,执行插件。简单的可以理解为某段编译过程(一个文件或者一个 chunk),一次 webpack,会触发多次的 compilation, 而 compilation 下面又有 N 多的 hooks,具体有哪些可以看官网,这里的 moduleAsset, 官网的解释是:一个模块中的一个资源被添加到编译中。比如图片资源。moduleAsset 回调函数接收到了两个参数,一个是 filename, 就是 hash 后的图片名字,将 filename, 保存到一个全局对象中。但这里的资源并不包括 JS,CSS,需要在其他的 hooks 中处理。
compiler.hooks.emit.tap(pluginOptions, emit);
emit 的钩子官网解释是:生成资源到 output 目录之前。说白了就是把构建好的 JS 和 CSS 文件写入到 dist 目录之前触发的 hooks。同样的 emit 函数里面能够拿到 compilation。compilation.chunks 是一个数组,代表着每一个 chunk, 通常是 entry 里面定义的文件,以及通过 splitChunks,拆开的 chunk。chunk 里面能拿到一个 files 字段,里面存到就是生成的 css 和 js 名字。
compiler.hooks.emit.tap(‘xxx’, (compilation) => {
compilation.chunks.forEach((chunk) => {
console.log(chunk.files);
});
});
到这里 webpack-manifest-plugin 的主功能就差不多了,将上面得到的各种 hash 后的 name 保存到对象里面,key 就是 chunk 名(不一定准),但 key 具体是什么不重要,到时候服务器端遍历 json 的时候,判断 value 的后缀即可,到底是 js,css 或者其他什么的一目了然。webpack-manifest-plugin 还有一些细节处理,比如取到了 publicPath,结合拿到的 fileName,组成了文件的真正地址。
plugin 其实还可以展开很多内容讲,但官网都有,很多时候也不用我们去写 plugin, 网上大把,我们只需要知道,他的基本原理即可。hooks,订阅发布等。