01|如果说第一篇讲的是“前端为什么开始需要构建”,那第二篇要讲的就是:谁先把这件事做成了制度

第一篇讲到最后,其实已经把一个历史门槛立住了:

前端还没有成熟工程体系,但已经不可能长期只靠手工把开发态硬送上线。

这句话一旦成立,下一步几乎就是必然的。

因为只要你已经知道:

  • 文件要合并
  • 代码要压缩
  • 资源要处理
  • 上线前有一串固定动作

那团队迟早会问出同一个问题:

这些事,为什么还要每次都靠人手工做?

这就是第二篇真正要讲的起点。

GruntGulp 最重要的历史意义,不是“它们曾经很流行”。

更不是“它们是 webpack 之前的老古董”。

它们真正改变的,是另一件更基础的事:

前端第一次把那些重复、机械、容易出错的交付动作,正式收编成了可执行的流水线。


02|先别急着把它们理解成“落后工具”,它们最早解决的是一个非常硬的现实问题:重复劳动太多了

今天的人回头看 GruntGulp,很容易带着后见之明说:

  • 它们不懂依赖图
  • 它们不会 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|所以 GruntGulp 虽然路线不同,但它们真正共同完成的,是把前端交付从“经验活”变成了“项目制度”

这一步很容易被后来的 bundler 光环盖住。

但从工程史角度看,它非常重要。

因为在 Grunt / Gulp 之前,很多团队当然也会写 shell 脚本,也会手工拼文件,也会搞一些项目私有工具。

可这些东西往往还是偏经验活:

  • 某个人知道怎么发
  • 某个文档里写了一串命令
  • 某个老成员记得有哪些坑

这不是稳定制度。

Grunt / Gulp 最重要的作用,就是把这件事正式化了。

它们让前端第一次大规模拥有了这些共同现实:

  • 构建流程可以被提交进仓库
  • 团队成员可以跑同一套任务
  • 自动化不再只是个人习惯
  • “本地怎么做”和“线上怎么交付”之间开始有固定桥梁

所以第二篇如果要压一句最重要的判断,那就是:

它们先把前端交付动作制度化了。

这一步未必性感。

但它是后来一切更复杂工程化的真正地基。


08|当然,task runner 也很快撞到了自己的边界:它们能组织步骤,却还不真正理解应用本身

这也是为什么第二篇不能把 Grunt / Gulp 写成终局。

它们确实重要。

但它们的重要,不在于“它们已经解决了前端工程化”。

恰恰相反。

它们更像前端工程化第一次成形时的制度外壳。

因为 task runner 的强项是:

  • 自动化重复步骤
  • 串联处理流程
  • 固化团队约定

但它们的弱项也很明显:

  • 不真正理解模块依赖
  • 不真正理解哪些代码被用了
  • 不天然知道如何处理应用级分块
  • 更像在处理文件步骤,而不是在理解应用结构

这就是为什么后来 webpack 官方在回顾这段历史时,会把 GruntGulp 这类工具放在 “IIFE + concat + rebuild whole thing” 的上下文里看。

也就是说:

它们能把文件串起来。

但它们还没有真正进入“理解整个应用图”的阶段。

所以 task runner 的历史位置最准确的说法,不是“落后”。

而是:

它们先把前端交付做成了流水线,但还没有把前端应用本身做成可分析的图。


09|为什么 GruntGulp 先赢下来的,不是终局,而是脏活制度化

把前面这些线叠一起看,第二篇最重要的判断可以压成一句:

Grunt / Gulp 先赢,不是因为它们定义了终局,而是因为它们先把前端最痛的重复劳动收编成了制度。

这句话里面有三层意思。

第一,它们解决的问题非常现实,而且非常刚需。

第二,它们的共同贡献,不只是“自动化”,而是“项目级可复现的自动化”。

第三,它们虽然还没触到应用结构更深的那层,但已经把前端从手工交付推到了流水线交付。

所以理解 Grunt / Gulp 的历史意义,最不该只记住“后来它们被替代了”。

更该记住的是:

没有它们这一步,前端很难那么早形成“构建流程本身也是项目一部分”的共同意识。


10|从 GruntGulp 开始,发布流程被正式写进项目

前端工程化江湖的第二篇,最值得记住的,不是“GruntGulp 谁更先进”这种表层争论。

更值得记住的是:

它们第一次让前端团队大规模地接受了一件今天看起来理所当然、当时却很新的事:发布流程不是靠人记住,而是应该被写进项目里。

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 workflowcode over configurationcomposableefficient 等说明,以及 src() / .pipe() / dest() 的文档描述。关于 task runner 在更晚的 bundler 视角下所处的位置,主要依据 webpack 官方 Why webpackIntegrations 文档,其中明确区分了 task runner 与 bundler 的边界,并回顾了 Gulp / Grunt 在 concat 式前端构建时代的历史位置。正文将 Grunt / Gulp 概括为“把前端重复劳动收编成制度”,属于基于其自动化、项目共享与流程固定作用做出的历史总结。


关键人物速览

  • Ben AlmanGrunt 的发起者。理解前端为什么会先从“自动化重复劳动”开始形成工程制度,绕不开他。
  • Eric SchoffstallGulp 的早期发起者之一。理解前端流水线为什么会从“配置驱动”进一步走向“代码驱动”,绕不开这条线。
  • Tobias Kopperswebpack 的发起者。虽然本篇还没正式进入 bundler,但理解 task runner 的边界,下一篇就会接到他。

参考与延伸阅读

  1. Grunt: The JavaScript Task Runner
    https://gruntjs.com/

  2. Ben Alman: Why grunt? Why not something else?
    http://benalman.com/news/2012/08/why-grunt/

  3. Grunt: Getting started
    https://gruntjs.com/getting-started/

  4. gulp.js
    https://gulpjs.com/

  5. gulp npm package
    https://www.npmjs.com/package/gulp

  6. gulp: Working with Files
    https://gulpjs.com/docs/en/getting-started/working-with-files/

  7. Why webpack
    https://webpack.js.org/concepts/why-webpack/

  8. webpack: Integrations
    https://webpack.js.org/guides/integrations/


下篇预告:webpack 为什么会一度成王,因为前端很快发现,光把步骤串起来还不够,你还得真正理解整个应用。