webpack 删除重复文件的一种优化思路

场景

很多人使用webpack打包文件,为了防止浏览器缓存,经常会这样设置:

1
2
3
4
output: {
path: path.resolve(__dirname, "./dist"),
filename: "bundle-[hash:5].js"
}

给文件hash编码后加在文件名后面,只要文件修改,都会生成不同的文件名。但是,经过多次打包后,往往会遇到这样的情况:

1
2
3
4
5
6
7
8
/dist目录
|-- bundle-6ece9.js
|-- bundle-b41c3.js
|-- bundle-d303d.js
|-- index.html
|-- style-6ece9.css
|-- style-b41c3.css
|-- style-d303d.css

除了我们的index.html如海边的礁石一般巍然不动,js、css文件都仿佛拉帮结派,一茬一茬的。该怎么删除这些重复的文件呢?

解决方法一

很多人都推荐使用“clean-webpack-plugin”插件,它能在Webpack编译之前就删除所有文件。
优点:轻量级插件,配置简单。
缺点:在大型项目中,如果只改变了一个文件却需要删除其他所有未改变的文件,这些文件数量不定但对性能还是有一定影响,因此这个插件性能上还有提升空间。

解决方法二

写过Webpack插件的前端同行,可能知道compiler对象以及相关的事件钩子。

在此介绍两种与本文相关的钩子:

  1. compile:开始编译,在创建 compilation 对象之前;
  2. done:所有的编译过程已完成。

方法一中的clean-webpack-plugin插件就是在compile阶段删除目标文件夹。而想要优化该插件,思路就是在done阶段删除上一次打包后留下的废弃文件。

这时有一个新的问题,在done阶段,项目中所有文件都编译完成,存放在dist文件夹中了,怎么区分哪些是这次打包产生的新文件,哪些是上一次打包产生的旧文件呢?

这里,再介绍一下compilation对象。

compilation 对象代表了一次单一的版本 webpack 构建和生成编译资源的过程,compilation 对象可以访问所有的模块和它们的依赖(大部分是循环依赖)。

了解compilation对象的概念后,介绍一下它的一个属性assets。compilation.assets的值是一个对象,其中每一属性名都是本次打包产生的文件的文件名,打印出来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assets:
{
'bundle-a82da.js':
{
existsAt: '...\\dist\\bundle-a82da.js'
},
'style-cc549.css':
ConcatSource {
children: [Object],
existsAt: '...\\dist\\style-cc549.css',
emitted: true
},
'index.html':
{
source: [Function: source],
size: [Function: size],
existsAt: '...\\dist\\index.html',
emitted: true
}
}

因此,通过assets属性我们就解决了如何区分本次打包与之前打包产生的文件的问题。只需要遍历一次dist目录所有文件,若assets对象中不包含该文件的文件名,那就说明这是“前朝余孽”,直接斩立决。

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

buildDir = this.buildDir;
compiler.plugin('done', function (compat) {
//获取资源数组对象
const newlyCreatedAssets = compat.compilation.assets;
const unlinked = [];
fs.readdir(path.resolve(buildDir), (err, files) => {
files.forEach(file => {
//删除当前打包资源组中不存在的文件
if (!newlyCreatedAssets[file]) {
fs.unlink(path.resolve(buildDir + file));
unlinked.push(file);
}
});
if (unlinked.length > 0) {
console.log('删除文件: ', unlinked);
}
});
});

使用时只需要传入目标文件夹的目录url即可,如“./dist/”。

插件

为了方便的使用,我做了一个插件上传到npm仓库。

1.初始化npm

1
npm init

2.安装模块

1
npm i webpack-remove-hashed-files --save-dev

3.修改webpack.config.js

1
2
3
4
5
6
7
8
9
const removeFiles = require('webpack-remove-hashed-files');
//你的打包目标文件夹
//your distnation folder
const buildDir = './dist/';
//……修改plugins
//...modify plugins
plugins:[
new removeFiles(buildDir)
]

源码请看我的github仓库

总结

我的方法主要是判断文件是否为本次打包产生来减少文件删除的时间,但是相比较于clean-webpack-plugin插件,功能还缺少删除白名单和文件名正则匹配等,这些都会在我有空的时间添加。

另外可能有的同行觉得clean-webpack-plugin不能满足某些需求,不妨试试我的这款插件,也许有意外之喜。

参考

看清楚真正的 Webpack 插件
如何编写一个插件?