使用 grunt-replace 注入 Web Vitals 监控代码
解读
在国内前端面试中,面试官提出“用 grunt-replace 注入 Web Vitals 监控代码”,并不是单纯考察“会不会用插件”,而是验证候选人能否在遗留 Grunt 体系内优雅地引入现代化性能监控能力。
核心考点有三层:
- 对 Grunt 任务流与插件机制的熟悉度——能否在不破坏原有构建顺序的前提下插入新任务;
- 对 Web Vitals 官方脚本注入最佳实践的理解——既要保证采集代码轻量、无阻塞、可配置,又要避免与业务代码冲突;
- 对国内网络与合规场景的落地经验——例如如何把数据上报到自研网关或阿里云 SLS、腾讯云 RUM,并做采样率、域名白名单、用户隐私脱敏等处理。
如果候选人只回答“装 grunt-replace → 写字符串替换”,会被认为“能跑但不够生产级”;只有给出**“任务分级、占位符设计、sourcemap 友好、灰度采样、一键回滚”** 的完整思路,才能拿到高分。
知识点
- grunt-replace 的三种模式:
plain字符串直接替换,最快但易误伤;regexp正则捕获,适合精准定位;function动态函数,可运行时读取版本号、Git commit、灰度配置等。
- Web Vitals 库加载方式:
- 官方
web-vitals模块仅 1.7 kB(gz),需type="module"; - 若需兼容旧浏览器,可改用
web-vitals-base.js并加nomodule兜底; - 采集后通过
navigator.sendBeacon发送到/metrics接口,失败时降级为 1×1 gif 以绕过企业网关拦截。
- 官方
- Grunt 任务编排:
grunt.registerTask('build', ['clean', 'replace:injectVitals', 'uglify', 'rev']);必须保证注入在压缩与加 hash 之前,否则替换标记会被破坏;- 使用
grunt-contrib-watch开启livereload时,需排除监控插入后的临时文件,防止无限循环。
- 国内合规细节:
- 采集指标只能包含性能条目,严禁携带用户身份信息;
- 上报域名需加入工信部备案号与隐私政策链接,否则企业安全审计会被 block;
- 支持采样率配置化,如
window.VITALS_SAMPLE = Math.random() < 0.1,减少带宽费用。
答案
- 安装与初始化
npm i -D grunt-replace web-vitals@3
- Gruntfile.js 关键片段
module.exports = function(grunt) {
grunt.initConfig({
replace: {
injectVitals: {
options: {
patterns: [{
match: /<!--\s*vitals\s*-->/, // 1. 占位符必须唯一且与业务注释区分
replacement: function() {
const fs = require('fs');
const code = fs.readFileSync('./build-inject/vitals.js', 'utf-8')
.replace('{{SAMPLING}}', grunt.option('sampling') || 0.1)
.replace('{{ENDPOINT}}', process.env.VITALS_ENDPOINT || '/metrics');
return `<script>${code}</script>`;
}
}]
},
files: [{
expand: true,
cwd: 'dist/',
src: '**/*.html',
dest: 'dist/'
}]
}
}
});
grunt.loadNpmTasks('grunt-replace');
grunt.registerTask('build', ['clean', 'replace:injectVitals', 'uglify', 'rev']);
};
- build-inject/vitals.js(精简后约 1 kB)
(function(s){
if(Math.random()>s)return;
import('https://cdn.jsdelivr.net/npm/web-vitals@3/dist/web-vitals.base.iife.js').then(function(m){
function send({name,value,id}){
var data={n:name,v:value,i:id,url:location.href,ts:Date.now()};
navigator.sendBeacon('{{ENDPOINT}}', JSON.stringify(data)) ||
new Image().src='{{ENDPOINT}}?gif=1&d='+encodeURIComponent(JSON.stringify(data));
}
m.onCLS(send);m.onFID(send);m.onLCP(send);m.onFCP(send);m.onTTFB(send);
});
})({{SAMPLING}});
- 灰度与回滚
# 全量
grunt build --sampling=1
# 10% 灰度
grunt build --sampling=0.1
# 紧急回滚:重新打包,replace 任务把占位符替换为空串即可
- 验证
本地起 nginx,访问页面,DevTools Network 面板应看到/metrics请求带gif=1兜底,且响应 204;Lighthouse 中 Performance 分数不应因注入下降超过 3 分。
拓展思考
- 如果公司已将构建迁移到 Vite,但老项目仍需 Grunt 维护,可共用同一套 vitals.js 注入脚本,通过
vite-plugin-html与grunt-replace同时引用,保证指标口径一致。 - 对于小程序内嵌 WebView 场景,Web Vitals 部分指标(如 CLS)会失真,需在注入脚本里加
userAgent判断,关闭不适用的指标,避免误导运营。 - 当业务采用多页应用 + 服务端拼接模板(如 Java Velocity)时,占位符应改为服务器注释
#*vitals*#,Grunt 任务仅负责预编译静态片段,最终注入由后端灰度开关控制,实现“前后端联合灰度”。