解释在 grunt 中实现按需数据预取
解读
“按需数据预取”在前端工程里通常指:
- 仅当某些入口或路由被真正访问时,才去拉取对应的数据包或模板;
- 构建阶段要把“哪些路由需要哪些数据”提前分析出来,并生成一份映射表;
- 运行时根据这份表做懒加载或 prefetch。
Grunt 本身只是一个任务编排器,不会替你跑在浏览器里,因此“实现”必须拆成两步:
- 构建时:用 Grunt 任务把路由依赖关系扫描出来,输出一份 manifest(JSON/JS);
- 运行时:业务代码读取 manifest,决定何时发请求或动态插入
<link rel="prefetch">。
面试时,考官想听的是“你怎么用 Grunt 把第一步做成可配置、可缓存、可增量”,而不是泛泛地说“我写个 task 就好了”。
知识点
- Grunt 多任务机制:
grunt.registerMultiTask可接收文件数组与自定义选项,天然适合做“扫描→输出”流水线。 - AST 静态分析:用
@babel/parser+traverse扫描import()或getInitialProps等约定式调用,提取路由与数据接口的映射。 - 缓存指纹:把上次解析结果序列化成
.cache.json,下次跑任务先比对文件mtime,无变动直接跳过,大幅提升 CI 增量构建速度。 - manifest 格式:推荐输出
{"/product/:id":["/api/product/detail","/api/stock"]},方便运行时按路由正则做最长匹配。 - Grunt 插件生态:官方
grunt-contrib-watch可监听源码变化,再触发自定义prefetch任务,实现本地开发阶段实时更新映射表;grunt-webpack亦可把映射表注入到 webpack 的DefinePlugin,供前端代码消费。 - 合规与性能:国内项目常需兼容低版本微信内核,prefetch 数量不宜超过 3 条,且要加
crossorigin="anonymous"避免 Cookie 冗余;Grunt 任务里可配阈值做自动裁剪。
答案
我曾在电商大屏项目中落地过一套“Grunt + 按需数据预取”方案,核心思路是“构建时扫描、运行时消费”。
第一步,写一个私有 Grunt 插件 grunt-route-manifest,注册为多任务:
grunt.initConfig({
route_manifest: {
options: {
// 只扫描 pages 目录下的 *.jsx
pattern: 'pages/**/*.jsx',
// 提取函数名约定
dataHook: ['getInitialProps', 'loader'],
// 输出路径
dest: 'dist/route-manifest.json'
},
src: ['src']
}
});
任务内部用 @babel/parser 把文件解析成 AST,再遍历 CallExpression,如果命中 getInitialProps,就把同文件导出的 route 字段与接口 URL 收集起来;同时把文件 mtime 与 .cache.json 比对,实现增量扫描。
第二步,在 grunt.registerTask('default', ['clean','route_manifest','webpack']) 里把该任务插到 webpack 打包之前;manifest 生成后,通过 grunt.file.write 直接落盘为 dist/route-manifest.json。
第三步,前端运行时封装 prefetch.js:
const map = __MANIFEST__; // 由 webpack DefinePlugin 注入
export function prefetchFor(path) {
const apis = matchPath(path, map);
apis.forEach(url => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
});
}
当路由切换时,先调用 prefetchFor(location.pathname),真正做到了“只预取当前路由可能用到的数据”。
上线后,首屏请求量下降 38%,白屏时间减少 220 ms,CDN 费用也省下一笔。整个流程对业务透明,只通过 Grunt 配置即可开关,符合国内团队“配置驱动”的交付习惯。
拓展思考
- 如果项目后续迁移到 Vite 或 esbuild,可以把同一套 AST 扫描逻辑抽成语言无关的 Rust 模块,通过 NAPI 供 Grunt 调用,保证老项目不改配置也能享受新解析器带来的 5× 速度提升。
- 对于千人千面的推荐接口,manifest 里会出现长尾 URL,此时可在 Grunt 任务里再做一层“接口归一化”:把
/api/recommend?userId=123聚合成/api/recommend,运行时再用占位符动态替换,避免映射表爆炸。 - 国内小程序容器不支持
<link prefetch>,可以把 Grunt 任务再扩展一条分支,输出prefetch.wxml,在小程序里用<wxs>做逻辑层预拉取,实现同一套构建产物多端适配。