记录一次Webpack插件优化的经历

去年12月份,我一个才毕业半年的前端新人接触了Webpack。跟着网上教程学习时,遇到了一个难题:文件名加上hash编码后,多次打包会有之前残留的文件。

当时看的那篇文章,并没有提到clean-webpack-plugin,所以我就傻傻的手动删除。还很奇怪为什么这么严重的问题没人说过……其实就是当时google的关键字有问题,没搜到正确的结果。

不过也是那次误打误撞,让我开始维护一个自己的Webpack插件——webpack-remove-hashed-files。具体功能可以看我之前的文章

本来这个插件吧,功能也不是很复杂,所以我开发好第一版后就没管了。只有后面收到一封邮件,按要求我新增了白名单的功能。接着就搁置了三个月吧。

不过呢,昨天在新项目里需要用到几个本地的图片,于是就发现了一个bug

就是说,我在项目中本来要用file-loader去处理js和css中引入的本地图片,将他们打包到dist文件夹下。然后呢,我打算把它们都放在dist/assets/images子文件夹下。于是就配置file-loader,option为{name: 'assets/images/[name].[ext]'}

然而就是这个配置导致我的删除插件出了问题。

本来我插件的设计是遍历dist目录,将所有文件的文件名与Webpack的compliation.assets的键名比较,如果文件名能够对应于某一个键名,说明是本次打包的内容,将其保留。其他的都删除。这是不考虑白名单的思路。

但是,不知道为什么,file-loader处理后的文件,在assets中是以类似于assets/images/a.jpg的键名保存的,这就导致遍历dist文件夹时,我是用a.jpgassets/images/a.jpg做比较。虽然这是同一个文件,但实际上被误认为两个文件,从而导致删除bug。

那么我该怎么解决呢?

首先要明确问题出现所涉及到的两个角色:file-loaderwebpack-remove-hashed-files。其次,能够修改的是file-loader的options与删除插件的源码。而我的目的就是要把所有图片都存放在dist/assets/images文件夹下,所以file-loader的options是不能修改的,应该重构插件的源码来适应file-loader的配置。

说实话,昨天想了一天才解决好这个问题。因为一直没明白,file-loader到底是怎么做到又改文件名又能创建个新文件夹的……

继续说怎么修改删除插件的源码吧。我苦思冥想N个小时后,决定推翻以前的判断逻辑。因为我想到了一个更简单的判断因素,而且这个因素才是删除插件的核心,只是一直被我忽略了。那就是【文件路径】。

compliation.assets中,每一键对应一个文件对象,其中有一个字段existsAt表明打包后的文件所在的绝对路径。那么我们先将这些路径保存到一个数组dirArrays中,然后在遍历dist下所有文件时,在dirArrays中搜索每个不在白名单中的文件的绝对路径,如果存在则保留;不存在则说明不是本次打包的内容,应该删除。

听上去是不是既不影响原有功能,而且还能解决file-loader的bug问题?另外还比之前的逻辑要简单一些。

至于为什么说文件路径是删除插件的核心?因为插件实质上做了两件事:比较assets与dist中所有文件、删除非本次打包产生的文件。原本只有在删除时才要调用fs.unlink方法,传入文件路径参数。而现在在比较的时候就使用了文件路径。说明它贯穿了本插件的所有流程,说是核心也不为过了。

以上就是本次优化插件的完整记录了,总结一下就是自己造的轮子要多测试多维护,这样才能有理由写推广的文章,让更多人去使用hhh。

另外,在遇到bug后我分析了一下竞品clean-webpack-plugin,发现它的思路跟去年我看得时候相比,还是默认在生命周期emit时遍历目标文件夹dist,如果文件不在白名单里就直接删除。这点也是它能够不受file-loader影响的原因。

但也是我一开始文章中说过的,本次打包与上一次打包也许只有一个文件被修改,实际上只要删除旧的这一个文件就行,但clean-webpack-plugin是将所有文件都删除了。我觉得这一点在大项目中还是会导致打包时间问题的,所以就自己造了个轮子。

还发现了一点,就是判断了是否有compiler.hooks,这是Webpack 4的新特性。后面我再修补下这个问题。

最后,感谢阅读。