自定义plugin篇|博客系列
# 前期准备
# 从使用的角度来看插件
好了,我已经准备好阅读的这篇文章然后写一个炒鸡牛x的插件了,赶紧的。
额,等等,在这之前我们不是得知道需要怎么去做吗?我们总是听到的插件插件的,它到底是个啥啊?
对象?函数?类?
小伙伴们不妨结合我们已经用过的一些插件来猜猜,比如HtmlWebpackPlugin,我们会这样使用它:
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'custom-plugin'
})
]
}
可以看到,这很明显的就是个构造函数,或者是一个类嘛。我们使用new就可以实例化一个插件的对象。并且,这个函数或者类是可以让我们传递参数进去的。
那你脑子里是不是已经脑补出一个轮廓了呢?
function CustomPlugin (options) {}
// or
class CustomPlugin {
constructor (options) {}
}
# 从构建的角度来看插件
知道了plugin大概的轮廓,让我们从构建的角度来看看它。插件不同于loader一个很大的区别就是,loader它是一个转换器,它只专注于转换这一个领域,例如babel-loader能将ES6+的代码转换为ES5或以下,以此来保证兼容性,那么它是运行在打包之前的。
而plugin呢?你会发现市场上有各种让人眼花缭乱的插件,它可能运行在打包之前,也可能运行在打包的过程中,或者打包完成之后。总之,它不局限于打包,资源的加载,还有其它的功能。所以它是在整个编译周期都起作用。
那么如果让我们站在一个编写插件者的角度上来看的话,是不是在编写的时候需要明确两件事情:
- 我要如何拿到完整的
webpack环境配置呢?因为我在编写插件的时候肯定是要与webpack的主环境结合起来的 - 我如何告诉
webpack我的插件是在什么时候发挥作用呢?在打包之前?还是之后?也就是我们经常听到的钩子。
所以这时候我们就得清楚这几个硬知识点:
-
compiler对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。 -
compilation对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。 -
钩子的本质其实就是事件
# 案例准备
老规矩,为了能更好的让我们掌握本章的内容,我们需要本地创建一个案例来进行讲解。
创建项目的这个过程我就快速的用指令来实现一下哈:
mkdir webpack-custom-plugin && cd webpack-custom-plugin
npm init -y
cnpm i webpack webpack-cli clean-webpack-plugin html-webpack-plugin --save-dev
touch webpack.config.js
mkdir src && cd src
touch index.js
(mkdir:创建一个文件夹;touch:创建一个文件)
OK👌,此时项目目录变成了:
webpack-custom-plugin
|- package.json
|- webpack.config.js
|- /src
|- index.js
接着让我们给src/index.js随便加点东西意思一下,省得太空了:
src/index.js
function createElement () {
const element = document.createElement('div')
element.innerHTML = '孔子曰:中午不睡,下午崩溃!孟子曰:孔子说的对!';
return element
}
document.body.appendChild(createElement())
webpack.config.js也简单的来配置一下吧,这些应该都是基础了,之前有详细说过了哟:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
title: 'custom-plugin'
}),
new CleanWebpackPlugin()
]
}
clean-webpack-plugin插件会在我们每次打包之前自动清理掉旧的dist文件夹
另外还需要在package.json中配置一条打包指令哈:
{
"script": {
"build": "webpack --mode development"
}
}
这里的"webpack"实际上是"webpack --config webpack.config.js"的缩写,这点在基础篇中也有说到咯。
--mode development就是指定一下环境为开发环境,因为我们后续可能有需要看到打包之后的代码内容,如果指定了为production的话,那么webpack它会自动开启UglifyJS的也就是会对我们打包成功之后的代码进行压缩输出,那一坨一坨的代码我们就不利于我们查看了。
# 传统形式的compiler.plugin
从易到难,让我们来实现这么一个简单的功能:
- 当我们在完成打包之后,控制台会输出一个
"good boy!"
在刚刚的案例目录中新建一个plugins文件夹,然后在里面创建上我们的第一个插件: No1-webpack-plugin:
webpack-custom-plugin
|- package.json
|- webpack.config.js
|- /src
|- index.js
+ |- /plugins
+ |-No1-webpack-plugin.js
现在依照前面所说的插件的结构,以及我们的需求,可以写出以下代码:
plugins/No1-webpack-plugin.js:
// 1. 创建一个构造函数
function No1WebpackPlugin (options) {
this.options = options
}
// 2. 重写构造函数原型对象上的 apply 方法
No1WebpackPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', () => {
console.log(this.options.msg)
})
}
// 3. 将我们的自定义插件导出
module.exports = No1WebpackPlugin;
接着,让我们来看看如何使用它,也就是:
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
+ const No1WebpackPlugin = require('./plugins/No1-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
title: 'custom-plugin'
}),
new CleanWebpackPlugin(),
+ new No1WebpackPlugin({ msg: 'good boy!' })
]
}
OK👌,代码已经编写完啦,快npm run build一下看看效果吧:

可以看到,控制台已经在夸你"good boy!"了😄。
那么让我们回到刚刚的那段自定义插件的代码中:
plugins/No1-webpack-plugin.js:
// 1. 创建一个构造函数
function No1WebpackPlugin (options) {
this.options = options
}
// 2. 在构造函数原型对象上定义一个 apply 方法
No1WebpackPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', () => {
console.log(this.options.msg)
})
}
// 3. 将我们的自定义插件导出
module.exports = No1WebpackPlugin;
注意到这里,我们一共是做了这么三件事情,也就是我在代码中的注释。
很显然,为了能拿到webpack.config.js中我们传递的那个参数,也就是{ msg: 'good boy!' },我们需要在构造函数中定义一个实例对象上的属性options。
并且在prototype.apply中呢:
- 我们需要调用
compiler.plugin()并传入第一个参数来指定我们的插件是发生在哪个阶段,也就是这里的"done"(一次编译完成之后,即打包完成之后); - 在这个阶段我们要做什么事呢?就可以在它的第二个参数回调函数中来写了,请注意这里我们的回调函数是一个箭头函数哦,这也是能够保证里面的
this获取到的是我们的实例对象,也就是为了能保证我们拿到options,并成功的打印出msg
所以,现在你的思维是不是已经很清晰了呢?我们想要编写一个插件,只需要这么几步:
- 明确你的插件是要怎么调用的,需不需要传递参数(对应着
webpack.config.js中的配置); - 创建一个构造函数,以此来保证用它能创建一个个插件实例;
- 在构造函数原型对象上定义一个 apply 方法,并在其中利用
compiler.plugin注册我们的自定义插件。
那么除了用构造函数的方式来创建插件,是否也可以用类呢?让我们一起来试试,将刚刚的代码改动一下:
plugins/No1-webpack-plugin.js:
