TAT.heyli 如何写一个 webpack 插件
In Web开发 on 2016年03月16日 by view: 8,946
4

原文地址

前言

最近由于用着 html-webpack-plugin 觉得很不爽,于是乎想自己动手写一个插件。原以为像 gulp 插件一样半天上手一天写完,但令人郁闷的是完全找不到相关的文章。一进官方文档却是被吓傻了。首先是进入 how to write a plugin 看了一页简单的介绍。然后教程会告诉你,你需要去了解 compiler 和 compilation 这两个对象,才能更好地写 webpack 的插件,然后作者给了 github 的链接给你,让你去看源代码,我晕。不过幸好最后给了一个 plugins 的 API 文档,才让我开发的过程中稍微有点头绪。

how to write a plugin 这个教程还是可以好好看看的,尤其是那个 simple example,它会教你在 compilation 的 emit 事件或之前,将你需要生成的文件放到 webpack 的 compilation.assets 里,这样就可以借助 webpack 的力量帮你生成文件,而不需要自己手动去写 fs.writeFileSync。

 

主要就是这段代码

基本特性介绍

首先,定义一个函数 func,用户设置的 options 基本就在这里处理。

其次,需要设一个 func.prototype.apply 函数。这个函数是提供给 webpack 运行时调用的。webpack 会在这里注入 compiler 对象。

compiler 对象

输出 complier 对象,你会看到这一长串的内容(如下面代码),初步一看,我看出了两大类(有补充的可以告诉我)。一个 webpack 运行时的参数,例如_plugins,这些数组里的函数应该是 webpack 内置的函数,用于在 compiltion,this-compilation 和 should-emit 事件触发时调用的。另一个是用户写在 webpack.config.js 里的参数。隐约觉得这里好多未来都可能会是 webpack 暴露给用户的接口,使 webpack 的定制化功能更强大。

除此以外,compiler 还有一些如 run, watch-run 的方法以及 compilation, normal-module-factory 对象。我目前用到的,主要是 compilation。其它的等下一篇有机会再说。

compiler 还有 compiler.plugin 函数。这个相当于是插件可以进行处理的 webpack 的运行中的一些任务点,webpack 就是完成一个又一个任务而完成整个打包构建过程的。如下图:
task

其它的任务点如 invalid, after-plugins, after-resolvers 具体可参考 compiler 对象

compilation 对象

至于 compilation,它继承于 compiler,所以能拿到一切 compiler 的内容(所以你也会看到 webpack 的 options),而且也有 plugin 函数来接入任务点。在 compiler.plugin('emit') 任务点输出 compilation,会得到大致下面的对象数据,因为实在太长,我只保留了最重要的 assets 部份,如下

assets 部份重要是因为如果你想借助 webpack 帮你生成文件,你需要像官方教程 how to write a plugin 在 assets 上写上对应的文件信息。

除此以外,compilation.getStats() 这个函数也相当重要,能得到生产文件以及 chunkhash 的一些信息,如下:

这里的 chunks 数组里,是对应会生成的文件,以及 md5 之后的文件名和路径,里面还有文件对应的 chunkhash(每个文件不同,但如果你使用 ExtractTextPlugin 将 css 文件独立出来的话,它会与 require 它的 js 入口文件共享相同的 chunkhash),而 assets.hash 则是统一的 hash,对每个文件都一样。值得关注的是 chunks 里的每个文件,都有 source 这一项目,提供给开发者直接拿到源文件内容(主要是 js,如果是 css 且使用 ExtractTextPlugin,则请自行打印出来参考)。

例子

接下来,会以最近我写的一个插件 html-res-webpack-plugin 作为引子,来介绍基本的写插件原理。插件的逻辑就写在 index.js 里。

首先,将用户输入的参数在定好的函数中处理,HtmlResWebpackPlugin。

然后,新增 apply 函数,在里面写好插件需要切入的 webpack 任务点。目前 HtmlResWebpackPlugin 插件只用到 emit 这个任务点,其它几个仅作为演示。

第三步,调用 addFileToWebpackAsset 方法,写 compilation.assets,借助 webpack 生成 html 文件(项目里最新版本新增了添加 favicon 的功能,因此有 2 个 addFileToWebpackAsset 的方法)。这里的核心就是本文一开始那段给 compilation.assets 添加 source 和 size 的代码。

第四步,在开发模式下 (isWatch = true),直接生成 html,但在生产模式下 (isWatch = true),插件会开始对静态资源(js,css)进行 md5 或者内联。项目用了_this.options.isWatch 去进行判断。

第五步,调用 findAssets 方法是为了通过 compilation.getStats() 拿到的数据,去匹配对应的静态资源,还有找到对应的哈希 (是 chunkhash 还是 hash)。

最六步,调用 addAssets 方法,对静态资源分别做内联或者 md5 文件处理。内联资源的函数是 inlineRes, 你会看到我使用了 compilation.assets[hashFile].source() 及 compilation.assets[hashFile].children[1]._value。前者是针对于 js 的,后者是针对使用了 ExtractTextPlugin 的 css 资源。

最后一步,即是内联和 md5 完成后,再更新一下 compilation.assets 中对应生成 html 的 source 内容,才能正确地生成内联和 md5 后的内容。这一步在 addAssets 方法里有一处代码如下:

后记

有兴趣可以试用一下 html-res-webpack-plugin 这个插件(为什么要写一个新的 html 生成插件,我在 readme 里写了,此处不赘述),看看有哪些用得不爽之处。目前只是第一版,还不适合用于生产环境。希望第二版的时候能适用于更多的场景,以及性能更好。到是,我也会写第二篇插件开发文章,将本文还没提到的地方一一补充完整。也欢迎大家在这里发贴,或者指出本人的谬误之处。

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2016/03/how-to-write-a-plug-in-webpack/

  1. 美图共赏 2016 年 4 月 14 日

    美图在这里:http://www.fydzv.com/

  2. 海盗 2016 年 4 月 13 日

    webpack 的 plugin 尝试写过一个了 主要的困难还是整个 compile 的过程中什么样的数据存储在什么数据结构里这个是文档没有写的,也不知道改了会有什么效果,只能在 debug 的时候一点点尝试。
    我这里写了一个和项目相关的提取 css 的 plugin,比官方的版本简单多了,回头有时间写写文章说说这个。

  3. 雁过不留声 2016 年 3 月 23 日

    好文 之前一直想入手这方面的,但官方文档太吓人。。。

  4. 木棉创意学习机 2016 年 3 月 17 日

    gulp browserify 大法好

发表评论