前言
本文介绍了webpack插件的组成与如何编写,然后会实现一个将打包好的静态资源上传至七牛的插件。
关于插件
在webpack中,plugin比loader的作用更加广泛,plugin可以管理资源,注入全局变量,压缩代码等。
插件的组成:
- 一个js函数或者js类
- 在插件函数的prototype上定义一个apply方法
- 绑定webpack的事件钩子
- 在钩子函数中处理webpack中的数据
- 处理数据完成后,需要调用webpack提供的回调
// 1.一个js函数或者js类
class MyPlugin {
// 2.在插件函数的prototype上定义一个apply方法
apply(compiler) {
// 3.绑定webpack的事件钩子
compiler.hooks.emit.tapAsync(
'MyPlugin',
(compilation, callback) => {
// 4.在钩子函数中处理webpack中的数据
console.log('处理webpack中的数据');
// 5.处理数据完成后,需要调用webpack提供的回调
callback();
}
);
}
}
在插件开发中最重要的两个资源就是compiler 和 compilation 对象。
Compiler
Compiler
模块是webpack的主要引擎,它扩展自Tapable类,用来注册和调用插件。大多数面向用户的插件都会先在Compiler
上注册。
compiler
会存在于webpack的整个生命周期,直到node进程关闭。
Compilation
Compilation
模块会被Compiler
使用配置数据创建一个compilation
实例。compilation
实例可以访问所有的模块和他们的依赖。
Compilation
也扩展自Tapable类,提供了编译过程中的生命周期钩子。
compilation
实例在每次代码更新或者是打包时都会重新创建。
Webpack中的钩子函数
我们编写插件前,需要知道我们插件是在什么时候去被调用。这个就需要了解webpack中提供的钩子函数有哪些:
- compiler.hooks.compilation:启动编译创建出 compilation 对象后触发
- compiler.hooks.make:正式开始编译时触发
- compiler.hooks.emit:输出资源到output目录前执行
- compiler.hooks.afterEmit:输出资源到output目录后执行
- compiler.hooks.done:编译完成后触发
这里列举了一小部分,想要看完整的请查看官网。
自定义插件
清楚了如何编写插件,现在我们就来自定义一个将打包好的静态资源上传至七牛的插件。首先我们创建一个webpack项目,不知道如何创建的同学请看这篇文章:《Webpack5,了解从0到1搭建一个项目的细节》。
创建插件
然后在根目录下创建qiniu-s-webpack-plugin.js
,然后添加代码:
const PLUGIN_NAME = 'qiniu-s-webpack-plugin';
class QiuniuPlugin {
apply(compiler) {
console.log('执行到我啦!!!');
}
}
module.exports = QiuniuPlugin;
到这里我们可以将插件先引入,试一下效果:
const QiniuWebpackPlugin = require('../qiniu-s-webpack-plugin');
module.exports = {
...
plugins: [
new QiniuWebpackPlugin(),
]
}
然后运行一下,可以发现命令行中打输出了plugin中打印的log。
绑定钩子函数
这里我们需要考虑需要绑定什么钩子函数。我们的插件的作用是在打包完成后,将静态资源上传至七牛,在上面我们列举了几个钩子函数,其中有两个符合我们的需求:
- compiler.hooks.afterEmit:输出资源到output目录后执行
- compiler.hooks.done:编译完成后触发
这两个钩子函数都可以实现我们的需求,但是我们需要拿到输出的文件信息,在done
中无法拿到输出的文件信息,所以在本插件中使用了afterEmit
钩子函数。
const PLUGIN_NAME = 'qiniu-s-webpack-plugin';
class QiuniuPlugin {
apply(compiler) {
compiler.hooks.afterEmit.tapAsync(
PLUGIN_NAME,
async (compilation, callback) => {
const fileNameAry = Object.keys(compilation.assets);
const buildPath = compiler.options.output.path;
callback();
}
);
}
}
module.exports = QiuniuPlugin;
这里我们通过compilation.assets
拿到输出后的文件信息:
{
'main-e6f758da.js': SizeOnlySource { _size: 3873 },
'image/icon.d144096d.jpeg': SizeOnlySource { _size: 77904 },
'main-e6f758da.js.map': SizeOnlySource { _size: 2679 },
'index.html': SizeOnlySource { _size: 456 }
}
可以看到输出一个对象,对象的key是我们的文件路径,然后我们通过Object.keys
将key提取出来。
[
'main-e6f758da.js',
'image/icon.d144096d.jpeg',
'main-e6f758da.js.map',
'index.html'
]
我们要上传一个文件,那么必须获取到这个文件的绝对路径。在webpack的配置文件中我们配置了output.path
属性,是我们打包输出目录的绝对路径。我们通过compilation.options.output.path
获取到path
属性。
path = 'user/xx/xx/project/dist'
然后我们将两者进行拼接,就可以获取到完整的文件路径,为后续上传做准备:
class QiuniuPlugin {
apply(compiler) {
compiler.hooks.afterEmit.tapAsync(
PLUGIN_NAME,
async (compilation, callback) => {
const fileNameAry = Object.keys(compilation.assets);
const buildPath = compiler.options.output.path;
const filePathAry = fileNameAry.map((filename) => `${buildPath}/${filename}`);
callback();
}
);
}
}
上传至七牛
七牛官方提供了nodejs端上传的npm包,直接安装:
npm install qiniu
然后我们创建一个新的文件qiniu.js
,用于封装七牛的上传方法:
const qiniu = require('qiniu');
class Qiniu {
options = {
accessKey: '',
secretKey: '',
bucket: '',
};
constructor(options) {
const {accessKey, secretKey, bucket} = options;
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const _options = {
scope: bucket,
};
const putPolicy = new qiniu.rs.PutPolicy(_options);
const config = new qiniu.conf.Config();
this.options = options;
this.uploadToken=putPolicy.uploadToken(mac);
this.formUploader = new qiniu.form_up.FormUploader(config);
}
putFile(filePath) {
const putExtra = new qiniu.form_up.PutExtra();
return new Promise((resolve, reject) => {
this.formUploader.putFile(this.uploadToken, null, filePath, putExtra, function (respErr,
respBody, respInfo) {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode == 200) {
resolve();
} else {
console.log(respInfo.statusCode);
console.log(respBody);
reject(respBody);
}
});
});
}
}
module.exports = Qiniu;
七牛操作文件必须上传三个参数:
- accessKey:在个人中心 -> 密钥管理中获取
- secretKey:在个人中心 -> 密钥管理中获取
- bucket:空间名称
然后我们在插件的构造函数中要将这三个参数传入,通过实例化Qiniu
这个对象调用上传文件的方法:
const Qiniu = require('./qiniu');
class QiuniuPlugin {
constructor(options) {
this.qiniu = new Qiniu(options);
}
apply(compiler) {
compiler.hooks.afterEmit.tapAsync(
PLUGIN_NAME,
async (compilation, callback) => {
const fileNameAry = Object.keys(compilation.assets);
const buildPath = compiler.options.output.path;
const filePathAry = fileNameAry.map((filename) => `${buildPath}/${filename}`);
//上传文件
filePathAry.forEach(async (filePath) => {
await this.qiniu.putFile(filePath);
});
callback();
}
);
}
}
由于七牛没有提供批量上传的方法,所以我们这里通过遍历的方式一个个将文件上传。
到这里插件就完成了。
总结
webpack插件本身是一个构造函数,需要在函数内定义apply
方法。在webpack初始化时,会调用插件的apply
方法,传入compiler
对象,插件需要通过compiler
对象注册钩子函数。webpack在编译的不同阶段会调用相应的钩子函数,从而调用插件去处理webpack的内部数据,在处理过程中可以访问compilation
对象,compilation
对象中可以访问所有的模块和它们的依赖,以及配置的属性。在处理完数据后,需要调用webpack提供的回调函数,让剩下的钩子函数继续执行。