背景
最近遇到一个问题,把一个 6 个月前在git ci上重新打包后,发现打包后的文件名的 hash 值跟之前不一样了,明明代码也没改啊,怎么回事?
分析1
分析一下,肯定是 webpack 打包的问题,我们的项目中用的是 contenthash,是不是换成 chunkhash 就好了呢?
hash, chunkhash 和 contenthash 的区别
网上搜了一下 chunkhash 和 contenthash 的区别,写的挺模糊的。我们动手试验一下:
contenthash
创建一个 create-react-app,yarn eject 后,可以看到 config/webpack.config.js 中,默认配置的 hash 是 contenthash
1 | output: { |
- 执行 yarn build 进行打包,可以得到打包后的文件名。main.chunk.js 和 main.chunk.css 的 hash 值不同:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
- 再次打包,在内容无修改的情况下,打包后的文件名是不变的:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
- 随意修改一下 App.js 的内容,然后再次打包,main.xxx.chunk.js 的 hash 值变了,但是 main.xxx.chunk.css 的 hash 没有变化:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
- 将 App.js 的内容复原,再次打包,main.xxx.chunk.js 又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
- 随意修改一下 App.css 的内容,然后再次打包,main.xxx.chunk.css 的 hash 值变了,但是 main.xxx.chunk.js 的 hash 没有变化:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
- 将 App.css 的内容复原,再次打包,main.xxx.chunk.css 又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB build/static/js/2.0b773d50.chunk.js |
将 webpack.config.js 中的 contenthash 改为 chunkhash,重复上面 6 个步骤
- 执行 yarn build 进行打包,可以得到打包后的文件名。main.chunk.js 和 main.chunk.css 的 hash 值是相同的:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
- 再次打包,在内容无修改的情况下,打包后的文件名是不变的:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
- 随意修改一下 App.js 的内容,然后再次打包,main.xxx.chunk.js 和 main.xxx.chunk.css 的 hash 值都变了:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
- 将 App.js 的内容复原,再次打包,main.xxx.chunk.js 和 main.xxx.chunk.css 又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
- 随意修改一下 App.css 的内容,然后再次打包,main.xxx.chunk.js 和 main.xxx.chunk.css 的 hash 值都变了:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
- 将 App.css 的内容复原,再次打包,main.xxx.chunk.css 又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB build/static/js/2.8308284b.chunk.js |
将 webpack.config.js 中的 contenthash 改为 hash,重复上面 6 个步骤
- 执行 yarn build 进行打包,可以得到打包后的文件名。所有文件的 hash 值都是相同的:
1 | 41.21 KB (-2 B) build/static/js/2.c17725a0.chunk.js |
- 再次打包,在内容无修改的情况下,打包后的文件名是不变的:
1 | 41.21 KB build/static/js/2.c17725a0.chunk.js |
- 随意修改一下 App.js 的内容,然后再次打包,所有文件的 hash 值都变了:
1 | 41.21 KB (+2 B) build/static/js/2.375a482c.chunk.js |
- 将 App.js 的内容复原,再次打包,所有文件的 hash 值又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB (-2 B) build/static/js/2.c17725a0.chunk.js |
- 随意修改一下 App.css 的内容,然后再次打包,所有文件的 hash 值都变了:
1 | 41.21 KB (-1 B) build/static/js/2.e437e80e.chunk.js |
- 将 App.css 的内容复原,再次打包,所有文件的 hash 值又与第 1 步、第 2 步的 hash 值相同了:
1 | 41.21 KB (+1 B) build/static/js/2.c17725a0.chunk.js |
总结一下
- 三者的区别:
- hash 计算与整个项目的构建相关;
整个项目公用一个 hash 值。
- chunkhash 计算与同一 chunk 内容相关;
同一个 chunk 的 js 和 css 文件,公用一个 hash 值。任意一个文件内容改变,公用 hash 值改变。
- contenthash 计算与文件内容本身相关。
当文件内容不变时,hash 值不变。
- 试验结论:
- 当代码内容不变的时候,contenthash和chunkhash都不会造成打包后文件hash值变化。
- 当代码变化时,contenthash比chunkhash引发变化的hash值更少。
- 所以,能用contenthash就用contenthash。
分析2
拉下来的代码是没有变化的,但项目hash值确实变了,那会是打包机器不同造成的吗?
经过试验发现:
- 打包的机器不同,打包后的hash值是不同的
- 同一台机器,在不同的文件位置,打包后的hash值也是不同的
但我这个问题是在同一个git ci上执行的打包,机器肯定是相同的。
分析3
如果都没变化,那会不会是依赖的npm包变了呢?
如果项目代码没变,但引用的 npm 包的版本变了,打包后的文件 hash 会变化吗?
我们来动手试验一下,webpack 的 hash 设置为 contenthash。
在 package.json 中引入 query-string,但不 import
- yarn add query-string@6.13.8,执行打包:
1 | 41.22 KB build/static/js/2.0b773d50.chunk.chunkFilename.js |
- 只改变 package.json 中版本号,但不重新 install,打包后 hash 值不变。(废话,执行代码内容都没变)
1 | 41.22 KB build/static/js/2.0b773d50.chunk.chunkFilename.js |
- yarn add query-string@6.12.0,再次打包。(肯定不变啊,代码又没引用这个包)
1 | 41.22 KB build/static/js/2.0b773d50.chunk.chunkFilename.js |
在 App.js 中 import query-string,但不执行任何方法
- yarn add query-string@6.13.8,执行打包:
1 | 43.79 KB build/static/js/2.1874ca7b.chunk.chunkFilename.js |
- yarn add query-string@6.12.0,再次打包,2.xxx.chunk.chunkFilename.js 的 hash 值变了
1 | 43.66 KB build/static/js/2.02dac1eb.chunk.chunkFilename.js |
在 App.js 中 import query-string,执行方法
1 | const parsed = queryString.parse(window.location.search) |
- yarn add query-string@6.13.8,执行打包:
1 | 43.78 KB build/static/js/2.5b8a76de.chunk.chunkFilename.js |
- yarn add query-string@6.12.0,再次打包
1 | 43.65 KB build/static/js/2.6d24736a.chunk.chunkFilename.js |
结论:
- 如果 npm 包只是在 package.json 中引入,但没有在代码里引入,相当于没有用这个 npm。该 npm 的版本变化不会引发打包后代码的 hash 值变化。(不用的包尽量删除)
- npm 包在代码里 import 引入后,该 npm 的版本变化会影响打包后的代码的 hash 值,即便只是 import,并没有真正使用。(这样冗余的代码,记得早点删除。可以使用 eslint 帮忙)
- 提交到 git 的代码,要带上 yarn.lock 或者 package-lock.json(二者选你常用的那个即可,不要一起提交到 git),便于依赖包管理。
这样子,就不会引发一段时间后,明明代码没有改变,打包后的文件 hash 值却变了的问题了。
补充知识
- chunk 是什么?
存在依赖关系的模块,在打包时被封装为一个 chunk。webpack 会从入口文件开始检索,并将具有依赖关系的模块生成一棵依赖树,最终得到一个 chunk。
如果 webpakc 定义了多个入口文件,就会得到多个 chunk 文件。
webpack 中 filename 和 chunkFilename 的区别
- filename 指列在 entry 中,打包后输出的文件的名称。
- chunkFilename 指未列在 entry 中,却又需要被打包出来的文件的名称。
改一下 webpack 的名字规则,让大家看起来更清晰:
1 | 41.22 KB build/static/js/2.0b773d50.chunk.chunkFilename.js |
- MiniCssExtractPlugin
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
- SplitChunksPlugin
本插件自动拆分代码到不同的 chunk 文件中。参考官方文档
1 | 默认情况下,它仅影响按需块,因为更改初始块会影响HTML文件应包含的脚本标签以运行项目。 |
- runtimeChunk
单独分离出webpack的一些运行文件