使用 grunt 生成组件级别缓存键并注入 HTML
解读
在国内前端工程化面试中,“组件级缓存键”通常指为每个可独立发布的业务组件(如 header、banner、商品卡片)生成带内容哈希的文件名(如 card.3e2f1a.js),并在最终 HTML 中自动替换引用路径,实现**“发版即刷新缓存、未改动即复用缓存”的极致性能策略。
Grunt 作为老牌任务运行器,本身不内置 webpack 式的 chunk 拆分**,但借助 grunt-hash、grunt-filerev、grunt-usemin、grunt-processhtml 等插件组合,可完全覆盖“哈希→映射→注入”三步流程。面试官重点考察:
- 能否在 Grunt 生态里闭环解决“文件名带哈希”与“HTML 自动替换”两大痛点;
- 是否理解组件级粒度(非整包)与多入口场景(如 SSR、微前端、多页应用)下的缓存策略差异;
- 对映射文件持久化、CDN 路径拼接、SourceMap 同步、回滚方案等生产细节是否有落地经验。
知识点
- grunt-filerev:基于文件内容生成 MD5 戳并重命名,输出
filerev.summary映射表。 - grunt-usemin 或 grunt-processhtml:在 HTML 里标记
<!-- build:js scripts/card.js -->区块,自动把映射后的哈希文件名写回。 - 组件级入口:通过
expand:true动态遍历components/*/index.js,保证每个组件独立生成哈希。 - 映射持久化:把
filerev.summary写入rev-manifest.json,供后端模板或 Node 中间件二次读取,实现非覆盖式灰度发布。 - 缓存控制:配合
grunt-cachebuster在查询串加哈希,解决部分 CDN 对“同名文件”缓存时间过长的合规问题(国内阿里云、腾讯云默认 10 分钟)。 - 并行优化:使用
grunt-concurrent把filerev、uglify、imagemin并行,降低大项目 40% 构建耗时,满足国内“下班前必须出包”的强节奏。 - 回滚策略:在
filerev后追加grunt-copy保留上一版本文件,结合grunt-ssh-deploy一键回滚,避免“哈希式发布”后找不到旧文件导致 404。
答案
- 安装核心插件
npm i -D grunt-filerev grunt-usemin grunt-contrib-copy grunt-contrib-clean
- 目录约定(符合国内团队习惯)
├─ components/
│ ├─ card/
│ │ ├─ index.js
│ │ └─ index.less
│ └─ search/
│ ├─ index.js
│ └─ index.less
├─ dist/ # 构建产出
├─ rev/ # 映射表
└─ Gruntfile.js
- Gruntfile.js 关键配置
module.exports = function(grunt) {
grunt.initConfig({
clean: ['dist', 'rev'],
// 1. 先合并、压缩每个组件
uglify: {
components: {
files: [{
expand: true,
cwd: 'components',
src: '*/index.js',
dest: 'dist/js',
rename: function(dest, src) {
// 把 card/index.js 变成 dest/js/card.js
return dest + '/' + src.replace(/\/index\.js$/, '.js');
}
}]
}
},
// 2. 生成带哈希的文件名
filerev: {
options: {
algorithm: 'md5',
length: 8
},
comp: { src: 'dist/js/*.js' }
},
// 3. 把哈希结果写回 HTML
usemin: {
html: 'src/*.html',
options: {
assetsDirs: ['dist'],
// 读取 filerev 生成的映射
revmap: 'rev/filerev.json'
}
},
// 4. 持久化映射,供后端使用
filerev_assets: {
dist: {
options: {
dest: 'rev/filerev.json',
pretty: true
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-filerev');
grunt.loadNpmTasks('grunt-usemin');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.registerTask('default', [
'clean',
'uglify',
'filerev',
'filerev_assets',
'usemin'
]);
};
- HTML 模板示例(src/index.html)
<!-- build:js js/card.js -->
<script src="components/card/index.js"></script>
<!-- endbuild -->
- 构建后效果
- 磁盘文件:
dist/js/card.3e2f1a8c.js - HTML 输出:
<script src="js/card.3e2f1a8c.js"></script> - 映射文件:
rev/filerev.json内含{"js/card.js":"js/card.3e2f1a8c.js"},供后端 Java、Node 模板引擎动态读取,实现**“组件级缓存键”**闭环。
拓展思考
- 微前端场景:若使用 qiankun、micro-app,每个子应用独立仓库,Grunt 可在 CI 阶段把
rev-manifest.json推送到统一版本中心,主应用通过 HTTP 拉取最新哈希,实现**“子应用热更新而主应用无感”**。 - 多环境路径差异:国内生产环境普遍走 CDN,测试环境走本地 Nginx,需在
usemin前加grunt-replace把src="/js/替换为src="https://cdn.example.com/js/,避免硬编码域名。 - SourceMap 同步:
filerev后记得把*.map文件也同步重命名,否则 Sentry 上报无法还原行列号;可写自定义 task 读取filerev.summary批量重命名 map。 - 灰度回滚:在
filerev后追加grunt-ssh-deploy,把旧版本文件保留为card.3e2f1a8c.js.bak,一旦监控告警,可在 30 秒内执行mv *.bak *.js回滚,满足国内“晚间上线、凌晨回滚”的严苛 SLA。