前端打包文件的缓存机制
# 强缓存和协商缓存
一般习惯将缓存分为强缓存和协商缓存两种。两者的主要区别是使用本地缓存的时候,是否需要向服务器验证本地缓存是否依旧有效。顾名思义,协商缓存,就是需要和服务器进行协商,最终确定是否使用本地缓存。
# 强缓存
我们知道,强缓存主要是通过 http 请求头中的 Cache-Control 和 Expire 两个字段控制。Expire 是 HTTP1.0 标准下的字段,在这里我们可以忽略。我们重点来讨论的 Cache-Control 这个字段。
一般,我们会设置 Cache-Control 的值为“public, max-age=xxx”,表示在 xxx 秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。
显而易见,如果在 xxx 秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果说你不着急,可以接受这样的,那是不是完美?然而,很多时候不是你想的那么简单的,如果发布新版本的时候,后台接口也同步更新了,那就 gg 了。有缓存的用户还在使用旧接口,而那个接口已经被后台干掉了。怎么办?
# 协商缓存
协商缓存最大的问题就是每次都要向服务器验证一下缓存的有效性,似乎看起来很省事,不管那么多,你都要问一下我是否有效。但是,对于一个有追求的码农,这是不能接受的。每次都去请求服务器,那要缓存还有什么意义。。
# 前端缓存方案
缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效。
所以问题是在强缓存前提下,更新版本之后,如何让用户第一时间使用最新的资源文件呢?
前端页面的入口 index.html 不使用缓存或者使用协商缓存。, 静态资源使用强缓存,更新版本的时候,把静态资源的路径都改了,这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。
下图为生产环境的网络请求
我们定下一个大概方案
HTML:使用协商缓存。
CSS&JS&图片:使用强缓存,文件命名带上 hash 值。
# 静态资源的 hash 配置
上图可以看到每个静态资源文件名中间都加上了 hash 值,如果每次更新版本都要手动一个一个去修改文件名,那也太让人难受了
幸运的是,webpack 可以让我们在打包的时候,在文件的命名自动加上 hash 值。
# webpack 三种哈希计算方式
webpack 给我们提供了三种哈希值计算方式,分别是 hash、chunkhash 和 contenthash。那么这三者有什么区别呢?
hash:跟整个项目的构建相关,构建生成的文件 hash 值都是一样的,只要项目里有文件更改,整个项目构建的 hash 值都会更改。
chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的 hash 值。
contenthash:由文件内容产生的 hash 值,内容不同产生的 contenthash 值也不一样。
vue-cli 默认选择 chunkhash, vue-cli 制作的 Webpack 配置。它们已经被很好地优化了,如果你对 webpack 不太熟悉,不建议对它做修改
// vue-cli默认配置
module.exports = {
configureWebpack: (config) => {
return {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
显然,我们一般不会使用第一种。改了一个文件,打包之后,其他文件的 hash 都变了,缓存自然都失效了。
chunkhash 按块计算 hash,只有模块变化了 才会重新计算 hash 值,强缓存命中高,在工作中的运用最广。
但是这样又有一个问题,因为我们是将样式作为模块 import 到 JS 文件中的,所以它们的 chunkhash 是一致的,如 test.js 和 var.css。
这样就会有个问题,只要对应 css 或则 js 改变,与其关联的文件 hash 值也会改变,但其内容并没有改变呢,所以没有达到缓存意义。
所以 contenthash 的用途随之而来
contenthash 是针对文件内容级别的,只有你自己模块的内容变了,那么 hash 值才改变,所以我们可以通过 contenthash 解决上诉问题
# node 后端设置
上文说明了前端是如何进行打包,后端也需要配合做一些设置
浏览器是根据响应头的相关字段来决定缓存的方案的。所以,后端的关键就在于,根据不同的请求返回对应的缓存字段。
以 nodejs 为例,如果需要浏览器强缓存,我们可以这样设置:
静态资源采用强缓存,可以这样设置:
res.setHeader('Cache-Control', 'public, max-age=xxx')
index.html需要协商缓存,则可以这样设置:
res.setHeader('Cache-Control', 'public, max-age=0')
res.setHeader('Last-Modified', xxx)
2
其它语言类似 设置对应返回头就可以了。