给出一个为 grunt-contrib-uglify 增加自定义压缩选项的 PR 思路
解读
面试官并非想听你背诵“如何写 Grunt 插件”,而是考察三件事:
- 对 grunt-contrib-uglify 内部运行机制的熟悉度(任务初始化 → 文件映射 → UglifyJS 调用 → 结果回写)。
- 能否在 不破坏向后兼容 的前提下,把 UglifyJS 的新增/实验性压缩选项透传给最终用户。
- 是否具备 国内开源协作常识:Commit Message 中英文混排规范、CHANGELOG 中文补录、CI 钉钉群通知、npm 镜像同步等细节。
一句话:让考官相信“你不仅会用,还敢改,而且改得让社区愿意合并”。
知识点
- Grunt 多任务模型:this.options() / this.files 的解析顺序
- UglifyJS 3 的 API:minify(code, {compress:..., mangle:...}) 与 2.x 的差异
- Gruntfile 配置合并策略:任务级 options 与目标级 options 的优先级
- semver 规范:新增“非破坏性”功能只能发 minor 版本
- 国内镜像同步:npmmirror 同步延迟与 README 中“安装”章节需给出 cnpm 示例
- 测试覆盖:grunt-contrib-internal 的 test/tasks 目录必须新增“自定义选项”用例,且用 Travis & AppVeyor 双 CI
答案
一、前期侦察
- fork 官方仓库,切到 next 分支(国内习惯称“dev”),通读 tasks/uglify.js 与 lib/uglify.js。
- 确认当前依赖的 UglifyJS 版本号,去其 Release Notes 找到 compress.drop_console、compress.pure_funcs 等未透出选项。
二、设计接口
在 Gruntfile 中保持 零 Breaking Change:
uglify: {
myTarget: {
options: {
compress: { // 原已有
drop_console: true // 新增自定义字段
},
mangle: { // 原已有
reserved: ['$']
},
// 本次 PR 新增顶级字段,透传 UglifyJS 未来任意选项
uglifyOptions: { ... }
},
files: {'dist/a.min.js': 'src/a.js'}
}
}
核心改动:
- 若用户显式写了 uglifyOptions,则 深度合并 到默认 minify 参数;
- 若未写,则保持 100% 旧行为。
三、代码实现(关键 10 行)
// lib/uglify.js
var defaultOptions = {
compress: true,
mangle: true,
sourceMap: false
};
// 新增
if (opts.uglifyOptions) {
Object.assign(defaultOptions, opts.uglifyOptions);
}
var result = UglifyJS.minify(code, defaultOptions);
注意:用 lodash.merge 而非 Object.assign 做深度合并,防止数组被覆盖。
四、测试用例
在 test/tasks/uglify_test.js 新增:
'test uglifyOptions': function (test) {
var files = grunt.file.read('tmp/uglify_options.js');
test.ok(!files.includes('console.log'), 'drop_console 应生效');
test.done();
}
并在 Gruntfile.js 里加对应目标,确保 CI 通过。
五、文档与 CHANGELOG
- README.zh-CN.md 中给出 国内镜像安装 示例:
cnpm install grunt-contrib-uglify --save-dev - CHANGELOG 新增 “新增” 分类,用中文写“支持 uglifyOptions 透传 UglifyJS 任意字段”。
六、提交规范
feat(options): add uglifyOptions for custom compress flags
close #123
七、PR 自检清单
- 本地
npm run test通过 - 兼容 Node 12+(国内服务器常用)
- 未改
package.json主版本号,只升 minor - 已在 Travis 与 GitHub Actions 双 CI 跑绿
- 钉钉群机器人已推送“等待 Code Review”卡片
拓展思考
-
如果 UglifyJS 4 把 API 改成异步,你如何保证 Grunt 任务同步执行?
答案:在任务里使用grunt.util.async把异步结果转成同步,或推动官方使用 Grunt 2 的 Promise 任务模型。 -
国内有企业项目仍在 UglifyJS 2,而社区已默认 3,PR 里如何做到双版本共存?
答案:在peerDependencies里写"uglify-js": "^2.8.0 || ^3.0.0",并在代码里做版本嗅探,分别调用minify或uglify.gen_code。 -
当自定义选项与 Grunt 官方推荐的“环保模式”(即默认去掉所有 console)冲突时,优先级策略如何设计?
答案:在文档中明确“目标级 > 任务级 > 插件默认”,并在初始化时打印一行警告:grunt.log.warn('uglifyOptions 将覆盖默认压缩行为'),让开发者知情。