携手创作,共同成长!这是我参与「日新计划 8 月更文挑战」的第5天,点击查看活动详情
webpack是目前最流行的构建工具,当前比较主流的React、Vue(2)前端框架都内置了webpack进行javaScript模块编译。
前言
loader和plugin是webpack最核心的两个概念,最近正在学习plugin的知识,为了加深对plugin的理解,尝试仿写一个功能简单的plugin — clean-webpack-plugin
完整代码在文末
前置知识
需要对node内置模块有一定的了解,如:fs、path模块 需要了解webpack Plugins的APIPlugins
clean-webpack-plugin
clean-webpack-plugin:用于清空/删除构建文件夹的内容。
为什么要用?
当每次执行项目打包命令时,都会在指定路径中生成打包后的代码文件:
// webpack配置:
module.exports = {
mode: 'development',
output: {
path: __dirname + '/dist',
filename: '[name]-[chunkhash:5].js'
},
}
打包结果:
每次修改了文件并重新打包后,都会生成不同的js文件:
这样就导致最终构建出来的代码文件夹“不干净”,所以就有了我们的clean-webpack-plugin
插件,每次执行打包时先清空/删除构建文件夹,再将编译出来的资源添加到构建文件夹,确保构建文件夹中的内容都是最新的。
怎么实现?
了解了clean-webpack-plugin
是干什么的再去实现它就会相对容易些了,代码实现:
class CleanWebpackPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('CleanWebpackPlugin', (compilation, callback) => {
// 获取webpack - output的配置项
const outputOpts = compiler.options.output;
// 删除目标文件夹
removeDir(outputOpts.path);
callback();
})
}
}
1、通过Plugins使用clean-webpack-plugin的方式也能看出来,clean-webpack-plugin暴露了一个CleanWebpackPlugin类
2、apply:apply
方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象(详情可以了解一下webpack的编译过程)。
3、compiler.hooks.emit.tapAsync: 在webpack编译的生命周期钩子中注册回调函数。emit指的是在输出构建资源到output目录之前执行。更多compiler钩子
4、compiler.options.output:获取webpack config中的output配置项
5、removeDir:自定义函数,用node内置模块fs、path清空构建目录内容,代码:
function removeDir(outputPath, exclude) {
// 文件夹路径存在
if(fs.existsSync(outputPath)) {
// 读取文件夹内容
fs.readdirSync(outputPath).forEach(file =>{
const dirPath = path.join(outputPath, file);
if (fs.statSync(dirPath).isDirectory()) {
// 目标文件夹中还存在文件夹,则递归删除
removeDir(dirPath, exclude)
} else {
// 文件直接删除
fs.unlinkSync(dirPath);
}
});
// 删除目标文件夹
fs.rmdirSync(outputPath);
}
}
这样就实现了一个丐版的clean-webpack-plugin
插件。
为什么是丐版?
查看clean-webpack-plugin
源码仓库会发现他还提供了很多可选配置项,而本文实现的插件并不支持这些配置。为了摆脱丐版这个称号,那就支持配置吧!
可选配置: 允许在创建clean-webpack-plugin实例时传递一个Object进行配置:
Options: {
// 配置构建文件夹中哪些文件不需要清空/删除
exclude: {
type: Array,
default: []
},
// 没了( ̄▽ ̄)"
}
在webpack.config.js文件中添加配置:
plugins: [
new CleanWebpackPlugin({ exclude:['main-85e85.js'] })
]
改造cleanWebpackPlugin.js
//
class CleanWebpackPlugin {
// 接收创建实例时传递的配置
constructor(options) {
// 混合配置,若未进行任何配置,则使用默认值
this.options = { exclude: [], ...options };
}
apply(compiler) {
compiler.hooks.emit.tapAsync('CleanWebpackPlugin', (compilation, callback) => {
// 获取webpack - output的配置项
const outputOpts = compiler.options.output;
// 删除目标文件夹时传递配置项(配置项多时可以传递这个options,removeDir收参进行相应修改)
removeDir(outputOpts.path, this.options.exclude);
callback();
})
}
}
修改removeDir方法
function removeDir(outputPath, exclude) {
if(fs.existsSync(outputPath)) {
fs.readdirSync(outputPath).forEach(file =>{
const dirPath = path.join(outputPath, file);
// 判断当前文件是否不需要删除
if(exclude.length && !exclude.includes(file)) {
if (fs.statSync(dirPath).isDirectory()) {
removeDir(dirPath, exclude)
} else {
fs.unlinkSync(dirPath);
}
}
});
// 添加配置项后就需要多一步这个操作: 再次读取文件夹内容,内容为空则删除文件夹
if(!fs.readdirSync(outputPath).length) {
fs.rmdirSync(outputPath);
}
}
}
结语
至此就实现了一个可配置的mini版clean-webpack-plugin
插件了。
完整代码
webpack.config.js
const { CleanWebpackPlugin } = require('./plugins/CleanWebpackPlugin');
module.exports = {
mode: 'development',
devtool: 'source-map',
output: {
path: __dirname + '/dist',
filename: '[name]-[chunkhash:5].js'
},
plugins: [
new CleanWebpackPlugin({ exclude:['main-85e85.js'] })
]
}
clean-webpack-plugin
const fs = require('fs');
const path = require('path');
function removeDir(outputPath, exclude) {
// 文件夹路径存在
if(fs.existsSync(outputPath)) {
// 读取文件夹内容,
fs.readdirSync(outputPath).forEach(file =>{
const dirPath = path.join(outputPath, file);
if(exclude.length && !exclude.includes(file)) {
if (fs.statSync(dirPath).isDirectory()) {
// 目标文件夹中还存在文件夹,则递归删除
removeDir(dirPath, exclude)
} else {
// 文件直接删除
fs.unlinkSync(dirPath);
}
}
});
// 再次读取文件夹内容,如果内容为空则删除文件夹
if(!fs.readdirSync(outputPath).length) {
fs.rmdirSync(outputPath);
}
}
}
class CleanWebpackPlugin {
constructor(options) {
this.options = { exclude: [], ...options };
}
apply(compiler) {
compiler.hooks.emit.tapAsync('CleanWebpackPlugin', (compilation, callback) => {
// 获取webpack - output的配置项
const outputOpts = compiler.options.output;
// 删除目标文件夹
removeDir(outputOpts.path, this.options.exclude);
callback();
})
}
}
module.exports = {
CleanWebpackPlugin
}