给出一个为 grunt-contrib-uglify 增加自定义压缩选项的 PR 思路

解读

面试官并非想听你背诵“如何写 Grunt 插件”,而是考察三件事:

  1. 对 grunt-contrib-uglify 内部运行机制的熟悉度(任务初始化 → 文件映射 → UglifyJS 调用 → 结果回写)。
  2. 能否在 不破坏向后兼容 的前提下,把 UglifyJS 的新增/实验性压缩选项透传给最终用户。
  3. 是否具备 国内开源协作常识: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

答案

一、前期侦察

  1. fork 官方仓库,切到 next 分支(国内习惯称“dev”),通读 tasks/uglify.js 与 lib/uglify.js。
  2. 确认当前依赖的 UglifyJS 版本号,去其 Release Notes 找到 compress.drop_consolecompress.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”卡片

拓展思考

  1. 如果 UglifyJS 4 把 API 改成异步,你如何保证 Grunt 任务同步执行?
    答案:在任务里使用 grunt.util.async 把异步结果转成同步,或推动官方使用 Grunt 2 的 Promise 任务模型。

  2. 国内有企业项目仍在 UglifyJS 2,而社区已默认 3,PR 里如何做到双版本共存?
    答案:在 peerDependencies 里写 "uglify-js": "^2.8.0 || ^3.0.0",并在代码里做版本嗅探,分别调用 minifyuglify.gen_code

  3. 当自定义选项与 Grunt 官方推荐的“环保模式”(即默认去掉所有 console)冲突时,优先级策略如何设计?
    答案:在文档中明确“目标级 > 任务级 > 插件默认”,并在初始化时打印一行警告:grunt.log.warn('uglifyOptions 将覆盖默认压缩行为'),让开发者知情。