01|如果说第一篇讲的是“前端为什么开始需要构建”,那第二篇要讲的就是:谁先把这件事做成了制度
第一篇讲到最后,其实已经把一个历史门槛立住了:
前端还没有成熟工程体系,但已经不可能长期只靠手工把开发态硬送上线。
这句话一旦成立,下一步几乎就是必然的。
因为只要你已经知道:
- 文件要合并
- 代码要压缩
- 资源要处理
- 上线前有一串固定动作
那团队迟早会问出同一个问题:
这些事,为什么还要每次都靠人手工做?
这就是第二篇真正要讲的起点。
Grunt 和 Gulp 最重要的历史意义,不是“它们曾经很流行”。
更不是“它们是 webpack 之前的老古董”。
它们真正改变的,是另一件更基础的事:
前端第一次把那些重复、机械、容易出错的交付动作,正式收编成了可执行的流水线。
02|先别急着把它们理解成“落后工具”,它们最早解决的是一个非常硬的现实问题:重复劳动太多了
今天的人回头看 Grunt、Gulp,很容易带着后见之明说:
- 它们不懂依赖图
- 它们不会 tree-shaking
- 它们没有现代 bundler 那套静态分析能力
这些都没错。
可问题是,它们出现的时候,前端最痛的地方还不是这些。
最痛的地方是:
每次发布前,都有一堆固定动作要重复做。
比如:
- 把多个脚本拼起来
- 压缩 CSS 和 JS
- 处理图片
- 跑 lint
- 跑测试
- 拷贝文件到发布目录
这些动作单个看都不复杂。
麻烦在于,它们几乎每次都要做,而且:
- 很无聊
- 很机械
- 很容易漏
- 很容易前后不一致
这就导致一个非常典型的前端早期工程问题:
大家并不是不知道该怎么上线,而是无法稳定地每次都用同样的方法上线。
而一旦问题被表述成这样,task runner 的历史位置就很清楚了。
它首先不是来发明“未来前端架构”的。
它是先来解决:
能不能让重复劳动别再继续靠记忆力和自觉。
03|所以 Grunt 先火,不是因为它最优雅,而是因为它特别直白地回答了这句:把这些步骤写进 Gruntfile,以后别再手工做了
Grunt 官方首页对自己的介绍非常直接:
The JavaScript Task Runner
而它解释“为什么要用 task runner”时,给出的答案也很朴素:
In one word: automation.
这套说法看起来甚至有点不够传奇。
但它恰恰说明 Grunt 当时踩得非常准。
因为那个阶段前端最急需的,不是新的理论语言。
而是一种非常直接的承诺:
把那些重复任务写进一个文件里,以后交给工具跑。
所以 Grunt 这套东西的历史意义,不在于它发明了多厉害的新范式。
而在于它把一件以前分散在经验、脚本、命令、口耳相传里的事,变成了正式制度:
- 项目根目录里有
Gruntfile - 任务配置写进去
- 发布流程能被团队共享
- 同样的命令可以重复执行
这一步非常关键。
因为它意味着前端第一次不再只是“知道应该做什么”。
而是开始把这些步骤固定成:
一个可重复运行的项目级合同。
04|而 Grunt 最像那个时代气质的地方,就在于它本质上是“配置先行”的流水线思维
理解 Grunt,不能只记住它能做 concat、uglify、lint。
更要看它代表的是哪种工程化直觉。
Grunt 的核心气质很明确:
你先把任务配置好,再让工具按照配置去执行。
Ben Alman 后来自己也讲过,Grunt 之所以偏向 configuration over scripting,
是因为很多常见任务本身就高度重复。
既然重复,就值得被抽象成一套声明式配置。
所以在 Grunt 的世界里,开发者面对的不是“随手写点脚本”。
而是:
- 定义任务
- 指定输入输出
- 设定参数
- 串成默认流程
这让前端第一次拥有了一种非常像正规 build system 的感觉。
虽然今天回头看,它当然还很笨重。
但在当时,它给出的制度感非常重要。
因为它把“上线前的一堆手工步骤”翻译成了:
一个可以被配置、保存、共享、复用的自动化过程。
这就是为什么 Grunt 会先赢得第一波广泛信任。
它未必最灵活。
但它非常像秩序。
05|可也正因为它太像秩序了,大家很快又开始嫌它笨:前端流水线不只是配置表,它也需要像写代码一样可组合
这也是第二篇必须讲清楚的一层。
Grunt 的成功,并不意味着问题已经结束。
恰恰相反。
它成功之后,大家很快又会意识到另一件事:
前端流水线本身,也越来越复杂了。
一旦任务变多,配置越来越长,很多人就会开始觉得:
- 写起来啰嗦
- 定制起来不顺手
- 复杂流程表达得不够自然
这时候,社区里的直觉就会转向另一个方向:
如果这些任务本来就发生在 JavaScript 世界里,
那为什么不干脆用更直接的 JavaScript 代码来组织它们?
这就把故事带到了 Gulp。
06|Gulp 最重要的那步,不是“它比 Grunt 快一点”,而是它把前端流水线从“声明配置”往“可编排代码”推了一大步
Gulp 官方后来给自己的描述也很直接:
A toolkit to automate & enhance your workflow
但它最有代表性的几句自我定位,其实是:
- code over configuration
- composable
- efficient
这几句合在一起,几乎已经把它和 Grunt 的差异说透了。
Gulp 并不是反对自动化。
它是反对把自动化过度僵硬地锁在配置表里。
所以 Gulp 提供的是另一种工程化直觉:
任务不是只能被声明,它也可以像程序一样被编排。
你可以:
- 用
src()读文件 - 用
.pipe()串联处理步骤 - 用
dest()写出结果 - 用
series()/parallel()组合任务
这套感觉对当时的前端非常有吸引力。
因为它让流水线第一次不只是“存在”。
而是开始显得:
更像开发者自己能够掌控和书写的东西。
换句话说,Gulp 历史上的关键贡献,是把前端工程化从“配置自动化”往“代码化流水线”推进了一步。
07|所以 Grunt 和 Gulp 虽然路线不同,但它们真正共同完成的,是把前端交付从“经验活”变成了“项目制度”
这一步很容易被后来的 bundler 光环盖住。
但从工程史角度看,它非常重要。
因为在 Grunt / Gulp 之前,很多团队当然也会写 shell 脚本,也会手工拼文件,也会搞一些项目私有工具。
可这些东西往往还是偏经验活:
- 某个人知道怎么发
- 某个文档里写了一串命令
- 某个老成员记得有哪些坑
这不是稳定制度。
而 Grunt / Gulp 最重要的作用,就是把这件事正式化了。
它们让前端第一次大规模拥有了这些共同现实:
- 构建流程可以被提交进仓库
- 团队成员可以跑同一套任务
- 自动化不再只是个人习惯
- “本地怎么做”和“线上怎么交付”之间开始有固定桥梁
所以第二篇如果要压一句最重要的判断,那就是:
它们先把前端交付动作制度化了。
这一步未必性感。
但它是后来一切更复杂工程化的真正地基。
08|当然,task runner 也很快撞到了自己的边界:它们能组织步骤,却还不真正理解应用本身
这也是为什么第二篇不能把 Grunt / Gulp 写成终局。
它们确实重要。
但它们的重要,不在于“它们已经解决了前端工程化”。
恰恰相反。
它们更像前端工程化第一次成形时的制度外壳。
因为 task runner 的强项是:
- 自动化重复步骤
- 串联处理流程
- 固化团队约定
但它们的弱项也很明显:
- 不真正理解模块依赖
- 不真正理解哪些代码被用了
- 不天然知道如何处理应用级分块
- 更像在处理文件步骤,而不是在理解应用结构
这就是为什么后来 webpack 官方在回顾这段历史时,会把 Grunt、Gulp 这类工具放在 “IIFE + concat + rebuild whole thing” 的上下文里看。
也就是说:
它们能把文件串起来。
但它们还没有真正进入“理解整个应用图”的阶段。
所以 task runner 的历史位置最准确的说法,不是“落后”。
而是:
它们先把前端交付做成了流水线,但还没有把前端应用本身做成可分析的图。
09|为什么 Grunt 和 Gulp 先赢下来的,不是终局,而是脏活制度化
把前面这些线叠一起看,第二篇最重要的判断可以压成一句:
Grunt / Gulp 先赢,不是因为它们定义了终局,而是因为它们先把前端最痛的重复劳动收编成了制度。
这句话里面有三层意思。
第一,它们解决的问题非常现实,而且非常刚需。
第二,它们的共同贡献,不只是“自动化”,而是“项目级可复现的自动化”。
第三,它们虽然还没触到应用结构更深的那层,但已经把前端从手工交付推到了流水线交付。
所以理解 Grunt / Gulp 的历史意义,最不该只记住“后来它们被替代了”。
更该记住的是:
没有它们这一步,前端很难那么早形成“构建流程本身也是项目一部分”的共同意识。
10|从 Grunt 和 Gulp 开始,发布流程被正式写进项目
前端工程化江湖的第二篇,最值得记住的,不是“Grunt 和 Gulp 谁更先进”这种表层争论。
更值得记住的是:
它们第一次让前端团队大规模地接受了一件今天看起来理所当然、当时却很新的事:发布流程不是靠人记住,而是应该被写进项目里。
Grunt 代表的是:
把重复任务声明出来,交给工具稳定执行。
Gulp 代表的是:
把流水线写成代码,让自动化更可组合、更像开发者自己的工作流。
所以第二篇如果只记一句,就记这句:
Grunt / Gulp 先火,不是因为它们已经理解了现代前端应用,而是因为它们第一次把前端那些最脏、最重复、最容易出错的交付动作,正式做成了团队可以共同依赖的流水线。
编者注(事实核对):文中关于 Grunt 的定位,主要依据其官方首页与文档中“The JavaScript Task Runner”“Why use a task runner? In one word: automation.”等表述,其中明确把 minification、compilation、linting、testing 等重复任务的自动化视为核心价值。关于 Grunt 偏向 configuration over scripting 的思路,主要依据 Ben Alman 在 Why grunt? Why not something else? 一文中的自述。关于 Gulp 的定位,主要依据官方首页与 npm 页面中 automate & enhance your workflow、code over configuration、composable、efficient 等说明,以及 src() / .pipe() / dest() 的文档描述。关于 task runner 在更晚的 bundler 视角下所处的位置,主要依据 webpack 官方 Why webpack 与 Integrations 文档,其中明确区分了 task runner 与 bundler 的边界,并回顾了 Gulp / Grunt 在 concat 式前端构建时代的历史位置。正文将 Grunt / Gulp 概括为“把前端重复劳动收编成制度”,属于基于其自动化、项目共享与流程固定作用做出的历史总结。
关键人物速览
- Ben Alman:
Grunt的发起者。理解前端为什么会先从“自动化重复劳动”开始形成工程制度,绕不开他。 - Eric Schoffstall:
Gulp的早期发起者之一。理解前端流水线为什么会从“配置驱动”进一步走向“代码驱动”,绕不开这条线。 - Tobias Koppers:
webpack的发起者。虽然本篇还没正式进入 bundler,但理解 task runner 的边界,下一篇就会接到他。
参考与延伸阅读
Grunt: The JavaScript Task Runner
https://gruntjs.com/Ben Alman: Why grunt? Why not something else?
http://benalman.com/news/2012/08/why-grunt/Grunt: Getting started
https://gruntjs.com/getting-started/gulp.js
https://gulpjs.com/gulp npm package
https://www.npmjs.com/package/gulpgulp: Working with Files
https://gulpjs.com/docs/en/getting-started/working-with-files/Why webpack
https://webpack.js.org/concepts/why-webpack/webpack: Integrations
https://webpack.js.org/guides/integrations/
下篇预告:webpack 为什么会一度成王,因为前端很快发现,光把步骤串起来还不够,你还得真正理解整个应用。