解释在 grunt 中实现静态资源永久缓存
解读
面试官抛出“永久缓存”话题,本质想验证两点:
- 你是否理解浏览器缓存策略(Expires/Cache-Control/ETag/Last-Modified)以及“指纹文件名”才是彻底避免 304 请求、实现“永久”缓存的核心;
- 你是否能在 Grunt 体系里把“文件名加哈希 → 替换引用路径 → 配合长缓存头”三步跑通,并兼顾国内常见 CDN、Nginx、OSS 等部署场景。
回答时务必先点破“指纹 + 长缓存头”缺一不可,再给出 Grunt 插件组合与配置细节,最后补一句“上线回滚策略”,即可体现工程化深度。
知识点
- 浏览器缓存:Cache-Control: max-age=31536000, immutable 与 304 对比
- 指纹策略:文件内容 → MD5 → 8~10 位哈希 → 嵌入文件名(app.3ab4c6.js)
- Grunt 插件:
- grunt-rev / grunt-filerev:生成带哈希的新文件
- grunt-usemin:扫描 html/css/js 中的 build:js / build:css 注释块,自动替换引用
- grunt-contrib-copy / clean:保证 dist 干净
- grunt-asset-manifest(可选):输出 json 映射表,供后端模板引擎动态读取
- 国内 CDN:阿里云 OSS、腾讯云 COS、七牛均支持“忽略参数缓存”,必须关闭“缓存参数过滤”,否则
app.js?v=3ab4c6仍会被当作不同文件 - 回滚:保留最近两版带哈希文件,Nginx 做
try_files $uri $uri/ /index.html,避免 404
答案
步骤一:安装核心插件
npm i -D grunt-filerev grunt-usemin grunt-contrib-concat grunt-contrib-uglify grunt-contrib-cssmin grunt-contrib-clean
步骤二:Gruntfile.js 关键片段
module.exports = function(grunt) {
grunt.initConfig({
clean: { dist: 'dist' },
// 1. 先合并、压缩
concat: {
js: { src: ['src/js/*.js'], dest: 'dist/tmp/app.js' },
css: { src: ['src/css/*.css'], dest: 'dist/tmp/app.css' }
},
uglify: { js: { files: { 'dist/tmp/app.min.js': 'dist/tmp/app.js' } } },
cssmin: { css: { files: { 'dist/tmp/app.min.css': 'dist/tmp/app.css' } } },
// 2. 加哈希 → 永久缓存文件名
filerev: {
options: { encoding: 'utf8', algorithm: 'md5', length: 8 },
assets: {
src: ['dist/tmp/app.min.*'],
dest: 'dist/static/'
}
},
// 3. 替换 html 里的引用
usemin: {
html: 'dist/*.html',
options: {
assetsDirs: ['dist/static'],
patterns: {
js: [[/app\.min\.js/, 'Replacing app.min.js']]
}
}
}
});
grunt.registerTask('cache', [
'clean', 'concat', 'uglify', 'cssmin',
'filerev', 'usemin'
]);
};
步骤三:Nginx 配置(国内云主机最常见)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
root /var/www/dist;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
gzip_static on;
}
步骤四:上线验证
- 运行
grunt cache后,dist 目录出现app.3ab4c6.min.js - 打开 Chrome Network,Status=200(from disk cache),无 304
- 修改业务代码再次构建,哈希变为
app.9e5d2a.min.js,旧文件保留,刷新页面加载新文件,实现平滑版本切换与永久缓存并存
拓展思考
- 若团队使用服务端模板(如 JSP、Thymeleaf),可让 grunt-asset-manifest 生成
manifest.json,Java 启动时读入内存,模板里动态输出<script src="${assets.appJs}">,避免全量替换 html - 对于微前端或多页面应用,可将 filerev 的
length调到 6 位减少 url 长度,同时开启grunt-filerev-summary输出映射表,供子应用运行时异步加载 - 国内灰度发布场景,可结合 Node 中间层:把
manifest.json写入 Redis,按用户维度返回不同哈希,实现“用户级缓存版本”,回滚只需改 Redis 键值,无需重新构建