如何对索引分片以减少首屏加载
解读
“索引分片”在前端语境下通常指把入口 HTML(index.html)里一次性加载的巨量资源拆成多份,让浏览器只拉取首屏必需的代码与样式,其余按需或延后加载。Grunt 时代没有 Webpack 的 import(),但借助任务编排 + 资源标记 + 服务端配合仍可完成。面试官想考察:
- 你是否理解“首屏”与“非首屏”的临界点;
- 能否用 Grunt 生态把拆包、内联、延迟、预加载四件事串成自动化流程;
- 是否知道国内弱网、2 M 大小限制、CDN 回源等现实约束。
知识点
- 临界 CSS(Critical CSS):首屏渲染所需样式,Grunt 用
grunt-critical提取并内联。 - JS 拆包:
grunt-browserify+factor-bundle或grunt-webpack把路由页拆成common.js、pageA.js、pageB.js;利用<script defer>控制加载时序。 - 资源标记:
grunt-processhtml按<!-- build:remove -->、<!-- build:js -->注释在编译期输出不同index-*.html,服务端按 UA 或网络状况返回对应模板。 - 图片分片:
grunt-responsive-images生成webp多倍图,grunt-lazyload自动把<img>换成data-src并插入lazysizes。 - HTTP 合并与域名分片平衡:国内多域名仍受浏览器 6 并发限制,Grunt 用
grunt-cdnify把静态资源推到阿里云 OSS 三域名,同时grunt-sprite把小图合并成雪碧图,减少握手。 - 缓存策略:
grunt-filerev加 MD5,grunt-asset-manifest生成manifest.json,服务端配置Cache-Control: max-age=31536000,更新时只改文件名,实现秒级回源。 - 本地开发:
grunt-contrib-connect开 SPA,grunt-contrib-livereload监听分片后的文件,保存即刷新,保证分片逻辑与调试体验一致。
答案
“我会把首屏优化拆成四步,全部写进 Gruntfile,让任何同事 grunt build 一键出包:
第一步,标记临界资源。在源 index.html 里用注释划定首屏区域:
<!-- criticalcss-start --> 到 <!-- criticalcss-end --> 之间的样式交给 grunt-critical,生成 critical.css 并内联,体积控制在 14 KB 以内(gzip 后),满足国内 3G 首包 1.5 RTT 内渲染。
第二步,JS 分片。grunt-browserify 配置 factor-bundle,把 router/ 下每个路由入口拆成独立 chunk,输出 common.js(体积 < 100 KB)+ pageA.js + pageB.js。HTML 里先放 common.js(defer),其余用 grunt-processhtml 在注释里标记为 <!-- build:remove:release -->,上线后由服务端按路由懒加载。
第三步,图片分片。设计稿 2 M 的 PNG 用 grunt-responsive-images 压成 webp 与 jpg 双格式,宽度 750、540、320 三档;grunt-lazyload 把非首屏 <img> 批量换成 data-src,并注入 lazysizes.min.js,首屏外图片节省 70% 流量。
第四步,版本化与回滚。grunt-filerev 对所有分片文件加 MD5,grunt-asset-manifest 生成 manifest.json,服务端 Nginx 用 try_files $uri $uri/ /index.php 兜底,回源失败 200 ms 内切到备用域名,保证秒级回滚。
整个流程在 .gitlab-ci.yml 里跑 grunt build,产物体积从 2.1 M 降到 480 KB,首屏DOMContentLoaded 平均 1.2 s(4G 弱网),Lighthouse 评分 90+。”
拓展思考
-
如果公司仍用jQuery 多页架构,没有模块化,如何先用 Grunt 做“伪分片”?
答:用grunt-uglify把每个页面临时拆成page.min.js,再用grunt-include-replace在 HTML 里按<!-- if page=home -->条件引入,虽不能按需加载,但可让首屏只加载 50 KB 基础库,其余 150 KB 放在页面底部,先出视觉后出交互,体验优于整包 200 KB。 -
面对微信内置浏览器,无法动态插入
<script>,如何分片?
答:利用微信JSSDK 预加载接口,Grunt 生成preload.json列表,服务端在 HTML 里一次性输出<link rel="prefetch">,微信会在空闲时后台拉取后续分片,用户点击下一页时命中内存缓存,实现“无感懒加载”。 -
若团队已迁 Webpack,但老项目仍需 Grunt 维护,如何双轨并行?
答:把 Grunt 作为壳任务,grunt-webpack只负责 JS 分片,其余压缩、雪碧图、CDN 上传仍用老插件;通过grunt-concurrent并行跑 Webpack 与 Grunt 子任务,产物目录统一到dist/,让运维一套脚本部署,降低迁移风险。