背景:原有项目迁移到 vue-cli,关闭了
filenameHashing
,打包的chunk
文件虽然后面带了hash
,但是部分chunk
内容变了但hash
不变,导致被 cdn 缓存无法更新。
首先在 vue-cli
文档中看看 filenameHashing
的作用:
Type: boolean
Default: true
默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而,这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为 false 来关闭文件名哈希。
但是当关闭这个选项后,观察生成的 chunk 名,依然带有 hash(chunk-59ac5ce4.js),那看一下这个参数在 cli 中具体做了什么,chunk 名称是如何生成的:
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L26
const getAssetPath = require('../util/getAssetPath')
// isLegacyBundle : 是否使用面向现代浏览器带自动回退地构建应用,默认 false
const outputFilename = getAssetPath(
options,
`js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
)
webpackConfig
.output
.filename(outputFilename)
.chunkFilename(outputFilename)
默认情况下,当 filenameHashing = false
时,文件名为 [name].js
,即 chunk 文件名为单纯的 chunkname
// https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/config/app.js#L132
// keep chunk ids stable so async chunks have consistent hash (#1916)
webpackConfig
.plugin('named-chunks')
.use(require('webpack/lib/NamedChunksPlugin'), [chunk => {
if (chunk.name) {
return chunk.name
}
// ...
return `chunk-` + joinedHash
}])
对没有设置 chunk
命名的组件来说,chunk 文件名为 chunk-[joinedHash].js
,来看下 joinedHash
是如何生成的:
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L139
const hash = require('hash-sum')
const joinedHash = hash(
Array.from(chunk.modulesIterable, m => m.id).join('_')
)
webpack 内部维护了一份自增 id,每个 module 都有一个 id, joinedHash
是基于当前 chunk 引用的 module,获取 id 拼接后进行 hash 之后的结果。
由于id是自增的,为了防止新增或删除一个文件导致后续其他 moduleId
发生变化,cli
使用 HashedModuleIdsPlugin
进行处理,该插件会根据模块的相对路径生成一个四位数的 hash
作为模块 id
,如下:
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/prod.js#L8
// keep module.id stable when vendor modules does not change
webpackConfig
.plugin('hash-module-ids')
.use(require('webpack/lib/HashedModuleIdsPlugin'), [{
hashDigest: 'hex'
}])
当我们的改动并没有改变引用 module 的 hash-id 时,joinedHash
就不会改变,chunkname
也不会改变。
目前的解决方案是将 filenameHashing = true
,在 chunkname
后拼接 contenthash
来判断当前 chunk
内容是否更新,那么问题来了,contenthash
是什么,它和 hash
、chunkhash
有什么区别?
hash
the hash of the module identifier
hash
是基于整个 moduleId
序列计算得到的,整个项目共用一个 hash,当增加或删除一个模块,会对整个 moduleId
序列造成影响,从而改变 hash
的值,不利于项目文件缓存。
chunkhash
the hash of the chunk content
webpack
会将所有文件以 js 为入口聚合,如果当前聚合的 css 有变动,那么在计算 chunkhash
时,会对整个文件内容进行计算,即使使用 MiniCssExtractPlugin
等插件分隔,其 chunkhash
也是一致的。
contenthash
the hash of extracted content
针对当前文件内容计算 hash
,一般来说,css
文件必须使用 contenthash
参考资料: