描述在 grunt 中实现 Tree-Shaking 兼容
解读
面试官抛出“Tree-Shaking 兼容”并不是让你现场手写 Rollup,而是考察三点:
- 是否理解 Tree-Shaking 的前置条件(ES Module 静态结构、副作用标记、DCE 原理);
- 是否知道 Grunt 本身只做任务调度,不具备模块依赖分析能力,必须嫁接具备 Tree-Shaking 能力的工具链;
- 能否给出可落地的 Gruntfile 集成方案,并解释如何验证效果、规避国产组件库常见副作用陷阱。
在国内实际项目中,Webpack 与 Rollup 并存,很多老系统仍用 Grunt 做“壳”,因此“用 Grunt 驱动 Webpack5 或 Rollup2+ 完成 Tree-Shaking”是高频改造场景,答出这一思路即可命中面试官痛点。
知识点
- ESM 静态依赖图:只有
import/export才能被静态分析,require/exports无法摇树。 - sideEffects 字段:
package.json中显式标记“无副作用”文件,给打包器白名单;国内组件库(如 AntD 3.x)常因import './style'导致整包引入。 - DCE(Dead Code Elimination)与 Tree-Shaking 区别:DCE 基于代码可达性,Tree-Shaking 基于模块依赖图,两者互补。
- Grunt 生态关键插件:
–grunt-webpack:将 Webpack5 嵌入 Grunt 任务,支持optimization.usedExports与sideEffects。
–grunt-rollup:直接调用 Rollup,天然 Tree-Shaking,适合库构建。
–grunt-contrib-uglify:仅做压缩,无摇树能力,不能单独完成 Tree-Shaking。 - 国产兼容坑:
– Babel 转码后默认转成 CommonJS,需用@babel/preset-env加{ modules: false }保持 ESM。
– 老项目存在jquery全局依赖,需用externals剥离,否则会被误打包。 - 验证方法:
–webpack-bundle-analyzer生成可视化报告,对比摇树前后模块体积。
– 在产物中全局搜索/* unused harmony export */注释,确认标记未被清除。
– 使用ag -c "function.*unused"统计未引用函数残留数,国内 CI 常用此脚本做门禁。
答案
在 Grunt 中实现 Tree-Shaking 兼容的核心思路是**“任务外包”**:让 Grunt 做生命周期管理,把真正的模块分析与摇树交给 Webpack5 或 Rollup。下面给出国内团队落地最多的 Grunt + Webpack5 方案,三步即可跑通:
-
安装依赖
npm i -D grunt-webpack webpack webpack-cli webpack-bundle-analyzer \ @babel/core @babel/preset-env babel-loader -
配置 Gruntfile.js
module.exports = function(grunt) { const webpackConfig = { mode: 'production', devtool: false, entry: './src/index.js', output: { path: __dirname + '/dist', filename: 'app.[contenthash].js' }, module: { rules: [{ test: /\.m?js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { modules: false }] // **关键:保持 ESM** ] } } }] }, optimization: { usedExports: true, // **标记未使用导出** sideEffects: false, // **强制识别 package.json 中的 sideEffects 字段** providedExports: true, concatenateModules: true }, externals: { jquery: 'jQuery' }, // **国产老项目常见全局变量** plugins: [ new (require('webpack-bundle-analyzer')).BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false, reportFilename: '../reports/webpack.html' }) ] }; grunt.initConfig({ webpack: { prod: webpackConfig }, clean: { dist: ['dist'] } }); grunt.loadNpmTasks('grunt-webpack'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.registerTask('build', ['clean', 'webpack']); }; -
在业务库中声明无副作用
项目根目录package.json加入:"sideEffects": ["*.css", "*.less", "src/polyfill.js"]若依赖的国产组件库未标记,可在
webpackConfig.module.rules追加side-effects-loader或手动alias到 ESM 版本。
执行 npx grunt build 后,查看 reports/webpack.html 即可看到被灰色标记的未引用模块,体积下降 30%~60% 即证明 Tree-Shaking 生效。若需库构建,可再建一个 grunt-rollup 子任务,把同样源码打出 ESM + UMD 双格式,供外部按需加载。
拓展思考
- 双构建管线:国内大型中台普遍要求“Grunt 老任务线不动,新增 Tree-Shaking 产物线”,可通过
grunt-concurrent并行跑传统grunt-contrib-uglify与新grunt-webpack,灰度切换,零回归风险。 - 微前端场景:子应用独立构建,主应用用
import()动态加载。若子应用仍用 Grunt,可在 CDN 上传前增加 “二次摇树” 步骤:用 Rollup 把子应用 ESM 入口再跑一遍,剔除主应用未用到的 reducer,常再省 15% 体积。 - 合规审计:国内金融项目要求可溯源构建,需在 Gruntfile 中把
webpackConfig.stats设为'verbose',并把生成的webpack-stats.json一并归档,审计人员可用webpack-bundle-diff工具对比版本差异,确保没有“暗桩”代码被摇掉后又被重新引入。