- Published on
写一个webpack插件到底有多复杂?
- Authors

- Name
- 薯仔
- @Henry_Yangs
在前端工程化的今天,如果说一个前端工程师说自己没用过甚至没听说webpack,我想一定会让人吓掉下巴。毕竟无论是三大框架的工程化脚手架解决方案,还是各个团队基于自身特点构建的前端打包工具,我们都可以在这里面看到webpack或者其它构建工具的身影。
在我们直接或者间接地接触这些构建工具时,已经被各种各样的插件所包围了,但是我们是否有考虑过,一个构建工具,或者说webpack,的插件到底是怎么样实现的呢?webpack又是怎样做到可以让插件处理各种各样的文件的呢?
以下的内容,我会用一个很简单的已经写好了的webpack插件作为demo来介绍如何写一个webpack的插件(插件暂时不支持webpack5,插件的源代码请移步阅读原文)
背景
至于为什么要写这个插件,原因其实很简单。webpack虽然本身支持使用dll将第三方依赖包不打到最后的vender.js中,但是所有的依赖包仍然会被打到同一个js文件中。那么在依赖包的数量过多的情况下,该文件依然会很大。因此这个插件的目的就是通过配置,将这些依赖抽取出来,使用script标签的形式将这些插件有序地插入到HTML模板文件中去。因此,这个插件是基于html-webpack-plugin的。
思路
在确定了插件的作用之后,我们来思考一下插件实现的大致思路。这里我们需要参考webpack的开发文档(https://www.webpackjs.com/concepts/plugins/)。
通过文档我们可以看到,webpack的插件最重要的两个钩子 -- compiler和compilation,前者是webpack的支柱,在webpack4中,可以通过它的hooks属性,访问各个生命周期的钩子函数,而在webpack3中,是通过compiler.plugin给不同的生命周期注册回调函数来等待调用的;
而后者能访问所有的模块和它们的依赖,在编译阶段,模块会被加载(loaded)、封存(sealed)、优化(optimized)、分块(chunked)、哈希(hashed)和重新创建(restored),在webpack3/4中,compilation的调用方式和compiler都是相同的。
另外,webpack其实是一个具有apply属性的JavaScript对象,这个apply属性会被compiler调用。
同时,对于我们的插件来说,重点就是把传入的配置处理后回填到webpack的配置中。为了实现这一点,在我们的插件中,对于每一个依赖包,需要收集如下信息:
1.依赖包/库的名称:libName
2.依赖包/库在全局暴露的名称:globalName
3.依赖包/库需要请求的地址,这个地址可以是自己服务器上的地址,也可以是CDN的地址:url
通过这三个配置就能看出来,前两个字段是为了往webpack的配置信息中回填内容的,而url字段是为了往HTML模板中插入script标签用的。
在有了这样的思路之后,我们可以先构建项目目录了。
项目结构
对于webpack插件来说,其实只需要一个index.js文件,然后在package.json中配置好入口文件即可。
代码
在这个index.js文件中,首先需要对外导出一个函数,用于给开发者调用并传入参数。
function main(options) {
this.options = options
}
module.exports = main
然后给这个函数的原型添加apply属性,并且针对不同的webpack版本注册生命周期函数
main.prototype.apply = function (complier) {
// 处理参数
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap('xxx', (compilation) => {
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(
'htmlWebpackScriptPlugin',
(htmlPluginData, callback) => {
// 核心代码
}
)
})
} else {
compiler.plugin('compilation', function (compilation) {
compilation.plugin(
'html-webpack-plugin-before-html-processing',
function (htmlPluginData, callback) {
// 核心代码
}
)
})
}
}
因为我们的插件需要在html-webpack-plugin之后再处理,所以需要用compilation找到html-webpack-plugin的钩子再注册一个回调函数。具体的核心代码可以在阅读原文的链接中找到。
总结
至此,一个极简的支持webpack3/4的插件就完成了。
我们知道了webpack插件的本质就是一个有apply属性的JavaScript对象,同时最重要的两个钩子是compiler和compilation,它们可以访问webpack整个生命周期并且可以访问所有模块及其依赖等。
当然,要完全搞懂webpack的插件机制也并不是写一两个简单的插件就够的,但是,至少现在已经入门了不是吗?