如何对索引分片以减少首屏加载

解读

“索引分片”在前端语境下通常指把入口 HTML(index.html)里一次性加载的巨量资源拆成多份,让浏览器只拉取首屏必需的代码与样式,其余按需或延后加载。Grunt 时代没有 Webpack 的 import(),但借助任务编排 + 资源标记 + 服务端配合仍可完成。面试官想考察:

  1. 你是否理解“首屏”与“非首屏”的临界点
  2. 能否用 Grunt 生态把拆包、内联、延迟、预加载四件事串成自动化流程;
  3. 是否知道国内弱网、2 M 大小限制、CDN 回源等现实约束。

知识点

  1. 临界 CSS(Critical CSS):首屏渲染所需样式,Grunt 用 grunt-critical 提取并内联。
  2. JS 拆包grunt-browserify + factor-bundlegrunt-webpack 把路由页拆成 common.jspageA.jspageB.js;利用 <script defer> 控制加载时序。
  3. 资源标记grunt-processhtml<!-- build:remove --><!-- build:js --> 注释在编译期输出不同 index-*.html,服务端按 UA 或网络状况返回对应模板。
  4. 图片分片grunt-responsive-images 生成 webp 多倍图,grunt-lazyload 自动把 <img> 换成 data-src 并插入 lazysizes
  5. HTTP 合并与域名分片平衡:国内多域名仍受浏览器 6 并发限制,Grunt 用 grunt-cdnify 把静态资源推到阿里云 OSS 三域名,同时 grunt-sprite 把小图合并成雪碧图,减少握手。
  6. 缓存策略grunt-filerev 加 MD5,grunt-asset-manifest 生成 manifest.json,服务端配置 Cache-Control: max-age=31536000,更新时只改文件名,实现秒级回源
  7. 本地开发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.jsdefer),其余用 grunt-processhtml 在注释里标记为 <!-- build:remove:release -->,上线后由服务端按路由懒加载

第三步,图片分片。设计稿 2 M 的 PNG 用 grunt-responsive-images 压成 webpjpg 双格式,宽度 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+。”

拓展思考

  1. 如果公司仍用jQuery 多页架构,没有模块化,如何先用 Grunt 做“伪分片”?
    答:用 grunt-uglify 把每个页面临时拆成 page.min.js,再用 grunt-include-replace 在 HTML 里按 <!-- if page=home --> 条件引入,虽不能按需加载,但可让首屏只加载 50 KB 基础库,其余 150 KB 放在页面底部,先出视觉后出交互,体验优于整包 200 KB

  2. 面对微信内置浏览器,无法动态插入 <script>,如何分片?
    答:利用微信JSSDK 预加载接口,Grunt 生成 preload.json 列表,服务端在 HTML 里一次性输出 <link rel="prefetch">,微信会在空闲时后台拉取后续分片,用户点击下一页时命中内存缓存,实现“无感懒加载”。

  3. 若团队已迁 Webpack,但老项目仍需 Grunt 维护,如何双轨并行
    答:把 Grunt 作为壳任务grunt-webpack 只负责 JS 分片,其余压缩、雪碧图、CDN 上传仍用老插件;通过 grunt-concurrent 并行跑 Webpack 与 Grunt 子任务,产物目录统一dist/,让运维一套脚本部署,降低迁移风险。