携手创作,共同成长!这是我参与「日新计划 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 }
评论(0)