使用 grunt-bundle-analyzer 生成可视化依赖图谱

解读

在国内前端面试中,面试官提出“用 grunt-bundle-analyzer 生成可视化依赖图谱”并不是单纯考察你会不会装插件,而是想确认三件事:

  1. 你是否理解 Grunt 生态与 Webpack 生态的边界——grunt-bundle-analyzer 只是 Webpack Bundle Analyzer 的 Grunt 封装,底层仍依赖 Webpack Stats 文件
  2. 你是否能在 老项目(Grunt 驱动) 里低成本地接入现代构建分析能力,而不推翻原有 Gruntfile;
  3. 你是否知道 如何解读可视化图谱并给出优化动作,把“会跑任务”升级为“能降包体”。
    因此,回答时要先讲“让 Grunt 输出 stats.json”,再讲“让 grunt-bundle-analyzer 消费 stats.json”,最后讲“看图说话、落地优化”,形成完整闭环。

知识点

  • Grunt 任务三件套:loadNpmTasks / registerMultiTask / this.options()
  • Webpack Stats 文件:webpack --json > stats.json 的产出,包含 chunks、modules、reasons、size、issuer 等字段
  • grunt-bundle-analyzer 本质:对 webpack-bundle-analyzer 的 Grunt 封装,不执行打包,仅起静态 HTTP 服务
  • analyzerMode:server / static / disabled,国内 CI 场景常用 static,生成 report.html 丢到 nginx 目录供团队查看
  • 默认端口 8888:云主机需检查安全组;本地若被占用可在 options 里改 port
  • Gzip 体积 vs Parse 体积:图谱中 红色条为 parse size,灰色条为 gzip size,面试时要明确“以 gzip 为准评估网络传输,以 parse 为准评估运行时内存”
  • SideEffects 与 UsedExports:图谱中 “[harmony side effect evaluation]” 提示可配 sideEffects: false 做 tree-shaking
  • 拆分策略:若 moment/lodash 单体过大,可借 grunt-replace + babel-plugin-import 做按需加载,再回炉 grunt-webpack 重新生成 stats,对比两次图谱

答案

  1. 安装依赖
    npm i -D webpack webpack-cli webpack-bundle-analyzer grunt-bundle-analyzer
    注意:grunt-bundle-analyzer 4.x 要求 Node ≥ 14,老项目若卡在 Node 12 需锁 3.x 版本

  2. 改造 Gruntfile.js
    在现有 grunt.initConfig 中追加:

    'bundle-analyzer': {
      prod: {
        options: {
          statsFile: './dist/stats.json',
          analyzerMode: 'static',
          reportFilename: './bundle-report.html',
          openAnalyzer: false,          // CI 环境无浏览器
          logLevel: 'info'
        }
      }
    }
    

    同时确保前置任务(如 webpack)已生成 stats.json:

    grunt.registerTask('webpack-stats', function () {
      const done = this.async();
      const { exec } = require('child_process');
      exec('npx webpack --config webpack.prod.js --json > dist/stats.json', done);
    });
    
  3. 串联任务
    grunt.registerTask('analyze', ['webpack-stats', 'bundle-analyzer']);

  4. 本地运行
    grunt analyze
    成功后终端输出
    Bundle Analyzer 静态报告已生成:bundle-report.html,总体积 1.34 MB(gzip 428 kB)

  5. 解读图谱

    • 发现 echarts 占 368 kB(gzip 124 kB),且整包被打入;
    • 查看 reasons 发现 import echarts from 'echarts' 未使用按需语法;
    • 给出优化:改用 import * as echarts from 'echarts/core' 并手动引入 LineChart、CanvasRenderer,重新跑 grunt analyze,echarts 体积降至 92 kB(gzip 34 kB),整体主包减少 232 kB
  6. 集成到 CI
    在 GitLab CI 中增加:

    analyze:
      stage: check
      script:
        - npm ci
        - grunt analyze
      artifacts:
        paths:
          - bundle-report.html
        expire_in: 7 days
    

    通过 artifacts 把报告挂在 MR 页面,MR 评论机器人自动读取 bundle-report.html 的 meta 标签 total-size,若增长 > 5% 则打回

拓展思考

  1. 无 Webpack 的老 Grunt 项目怎么办?
    若项目仍用 grunt-contrib-uglify + concat 无模块化打包,则无法生成 stats.json。此时可:

    • 先用 grunt-rollupgrunt-webpack 把源码做一次 “伪打包”(仅走模块解析,不真发线上),拿到 stats 后再分析;
    • 或者换 source-map-explorer,直接分析 concat 后生成的 .js.map,但粒度只能到文件级,无法看到 npm 包内部细节
  2. 如何横向对比两次迭代?
    在 CI 里把 stats.json 当产物保存,下次构建用 webpack-bundle-diff 做二次开发,输出 “新增/删除/体积变化” 三列 Markdown 表格,贴到 MR 评论,让产品同学也能看懂

  3. 安全与权限
    国内金融、政务项目禁止把源码 stats 上传到第三方平台。自建 analyzer 服务时,务必在 nginx 层加 basic-auth 并只开放 VPN 网段;report.html 里含完整路径名,上线前用 grunt-string-replace 把绝对路径脱敏为相对路径

  4. 性能红线
    可视化报告本身也是静态资源,超过 5 MB 的 stats.json 会导致 analyzer 页面卡顿。此时可在 options 里加

    generateStatsFile: false,
    statsOptions: { all: false, assets: true, chunks: true, modules: true }
    

    只保留关键字段,把 stats.json 压到 800 kB 以内,确保在低端办公本上也能秒开