如何在不安装插件的前提下通过 grunt.task.run 串行执行多函数
解读
面试官抛出“不安装插件”这一限制,目的是考察候选人对 Grunt 任务队列机制与 task.registerMultiTask / registerTask 的底层理解,而非只会 grunt.loadNpmTasks。国内一线厂普遍要求“最小依赖、最大可控”,尤其在基建组或外包审计场景,禁止随意装包是红线。因此,必须利用 Grunt 原生 API 把若干普通函数封装成具名任务,再借助 grunt.task.run 的串行调度能力完成流水线。
知识点
- 任务即函数:
grunt.registerTask('foo', function () { ... })会把函数注册成任务,函数体内必须返回 true / false 或调用 this.async() 告知 Grunt 结束时机。 - 串行队列:
grunt.task.run(['a','b','c'])会把任务名依次压入当前任务上下文的内部队列,前一个任务“完成标记”打出后,后一个才会出队。 - 无插件原则:禁止
grunt.loadNpmTasks,但可手写普通函数并通过registerTask包装,不依赖任何 npm 包。 - 错误熔断:任务函数中
return false或grunt.fail.warn()可中断后续任务,符合国内 CI 质量门禁要求。 - this.async():当任务内部有异步 IO(如读文件、拉取远端配置)时必须调用,否则 Grunt 会提前认为任务成功。
答案
在 Gruntfile.js 中按以下步骤操作,全程零插件:
module.exports = function (grunt) {
// 1. 定义纯函数
function clean() {
grunt.log.writeln('>> 清理 dist 目录');
grunt.file.delete('dist', { force: true });
return true; // 同步任务直接返回 true
}
function buildJS() {
const done = this.async(); // 标记异步
setTimeout(() => {
grunt.file.write('dist/app.js', 'console.log("hello");');
grunt.log.ok('JS 构建完成');
done(); // 通知 Grunt 本任务结束
}, 300);
}
function copyHtml() {
grunt.file.copy('src/index.html', 'dist/index.html');
grunt.log.ok('HTML 拷贝完成');
return true;
}
// 2. 注册成具名任务
grunt.registerTask('clean', clean);
grunt.registerTask('buildJS', buildJS);
grunt.registerTask('copyHtml', copyHtml);
// 3. 默认任务里串行调度
grunt.registerTask('default', function () {
grunt.task.run(['clean', 'buildJS', 'copyHtml']);
grunt.log.writeln('>> 所有任务已按序进入队列');
});
};
运行 npx grunt 即可看到clean → buildJS → copyHtml 严格串行,任一环节返回 false 或未调用 done(),后续任务不会执行,满足国内企业对可中断、可追踪、零依赖的强制要求。
拓展思考
- 动态任务名:在
grunt.task.run之前,可根据命令行参数grunt.option('env')决定加入哪些任务,实现开发/生产两套流水线而无需额外插件。 - 并行优化:虽然题目要求串行,但可把无依赖的子任务拆成多目标(如
cssmin:prod、cssmin:lib),再用grunt.task.run(['concurrent:css'])做并行,并发目标仍通过原生注册实现,同样不引入第三方。 - 结果缓存:在函数内部用
grunt.file.exists('.cache/foo')做增量判断,跳过未变更任务,提升大型仓库二次构建速度,该技巧在国内 Monorepo 基建中可节省 30%+ 时间。