一、项目简介:
开发一款可以压缩构建资源为zip包的webpack插件。
实现功能点
- 自动压缩构建资源,生成后缀为.zip的压缩包
- 压缩包命名通过插件传参设定
二、项目背景:
为什么开发打包压缩zip的webpack插件?
开发webpack插件,可以让我们从webpack配置工程师,深入理解webpack工作机制及原理,然后走向webpack开发工程师。那选择开发压缩打包zip的webpack,是因为项目部署经常需要将编译构建的项目打包成zip包。因此选择最常用简单的功能,开发webpack插件。
三、实践过程:
3.1 首先恶补下webpack插件的基础知识
webpack插件是伴随 webpack 构建的初始化到最后文件生成的整个工作过程,插件的目的是在于解决loader无法实现的其他事情。
在 Webpack 整个工作过程会有很多环节,为了便于插件的扩展,Webpack 几乎在每一个环节都埋下了一个钩子。这样我们在开发插件的时候,通过往这些不同节点上挂载不同的任务,就可以轻松扩展 Webpack 的能力。
3.1.1 插件的运行环境
插件只能运行在webpack环境中。简单来讲,要开发调试webpack插件前就需要创造webpack运行环境,创建配置文件webpack.config.js,并安装webpack、webpack-cli依赖。将开发好的插件放到配置文件的plugins数组中,跟随webpack进行编译执行。
3.1.2 插件的基本结构
// 1. 插件名称
class Plugin{
// 接受插件传递的参数
constructor(options){
}
// 2. 插件上提供apply方法,webpack提供compiler对象
apply(compiler){
// 3. 指定一个挂载到 webpack 自身的事件钩子
// compiler提供hooks钩子方法,作用于构建流程中
compiler.hooks.done.tap('Plugin',// 4. 处理 webpack 内部实例的特定数据
// 5. 功能完成后调用 webpack 提供的回调
()=>{
})
}
}
module.exports = Plugin
3.1.3 插件的事件处理
1)插件中如何获取传递的参数?
通过插件的构造函数进行获取。
class Plugin{
// 接受插件传递的参数
constructor(options){
this.options = options
}
}
2)插件中的错误处理
- 参数校验阶段可以直接throw的方式将错误抛出
throw new Error('error options') - 如果已经进入到hooks钩子阶段,可以通过compilation对象的warnings和errors接收
compilation.warnings.push('warnings') compilation.errors.push('error')
3)对资源文件处理:通过Compilation进行文件写入
对于一些插件,本身需要产生一些静态资源。比如生成zip包,之前源码浏览时候有了解到,文件生成阶段 是在emit阶段,则我们可以监听compiler的emit钩子阶段,获取到compilation对象,并将最终的内容 设置到compilation.assets对象上输出到磁盘目录。
另外,文件的写入,一般需要借助 webpack-sources。
3.2 开始敲代码,开发压缩资源为zip包插件
3.2.1 准备知识:Node.js里面如何将文件压缩成zip包?
我们可以使用 jszip 进行压缩生成zip包。
3.2.2 初始化插件文件
新建 zipPlugin.js 文件,并参考官方文档中插件的基本结构,初始化插件代码:
class ZipPugin{
// 接受插件传递的参数
constructor(options){
this.options = options
}
// 插件上提供apply方法,webpack提供compiler对象
apply(compiler){
}
}
module.exports = ZipPugin
3.2.3 选择插件触发时机
选择插件触发时机,其实是选择插件触发的 compiler 钩子(即何时触发插件)。 Webpack 提供钩子有很多,这里简单介绍几个,完整具体可参考官方文档“Compiler Hooks”:
entryOption: 在 webpack 选项中的 entry 配置项 处理过之后,执行插件。afterPlugins: 设置完初始插件之后,执行插件。compilation: 编译创建之后,生成文件之前,执行插件。emit: 生成资源到 output 目录之前。done: 编译完成。 我们压缩资源为zip包的插件需要在资源生成之前处理,所以选择了emit钩子。
3.2.4 选择触发钩子的方法
在 compiler.hooks 下指定事件钩子函数,便会触发钩子时,执行回调函数。
Webpack 提供三种触发钩子的方法:
tap:以同步方式触发钩子;tapAsync:以异步方式触发钩子;tapPromise:以异步方式触发钩子,返回 Promise; 因为上面选择了emit钩子是异步的,因此选择tapAsync方式触发钩子。
3.2.5 编写插件逻辑
实现插件功能的逻辑代码:
// zipPlugin.js
const JSZip = require('jszip');
const path = require('path');
const RawSource = require('webpack-sources').RawSource;
const zip = new JSZip();
module.exports = class ZipPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
// 创建目录名称
const folder = zip.folder(this.options.filename);
// 遍历compilation.assets对象
for (let filename in compilation.assets) {
// 获取source
const source = compilation.assets[filename].source();
// 将source添加到folder中
folder.file(filename, source);
}
// 将内容生成zip
zip.generateAsync({
type: 'nodebuffer'
}).then((content) => {
// 获取output(绝对路径)
const outputPath = path.join(
compilation.options.output.path,
this.options.filename + '.zip'
);
const outputRelativePath = path.relative(
compilation.options.output.path,
outputPath
);
// 将内容挂载到compilation.assets上,并将buffer转换为source
compilation.assets[outputRelativePath] = new RawSource(content);
callback();
});
});
}
}
使用插件的方式:
// webpack.config.js
const path = require('path');
const ZipPlugin = require('./plugins/zipPlugin');
module.exports = {
entry: './src/main.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'main.js'
},
mode: 'production',
plugins: [
new ZipPlugin({
filename: 'offline'
})
]
}
四、总结思考:
我们通过一个简单插件的开发过程,了解了webpack插件机制的工作原理,总结下来就是一句话:Webpack 为每一个工作环节都预留了合适的钩子,我们在扩展时只需要找到合适的时机去做合适的事情就可以了。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情