导读
Vladimir[1] 发现自己一直讨厌 bash 编写的自动化流程脚本,并且在机缘巧合下发现同事们都有类似的想法,因此他分享了他认为 JavaScript 编写自动化脚本的优势,看看能不能说服大家去共建更好的生态。
与之相关的是,谷歌的 zx[2] 项目正是为此而生,并且在去年的 JavaScript 工具流行趋势调查中获得了第一名。
今年最受欢迎的项目是谷歌的 zx,可在 JavaScript 或 TypeScript 中编写简单的命令行脚本。
zx 支持在代码中嵌入任何 bash 表达式(ls、cat、git 等等),并借助 JavaScript 模板字面量获得结果。
zx 涵盖了多个软件包提供的功能:
node-fetch:使用与浏览器中相同的 API 发出 HTTP 请求;
fs-extra:运行文件系统;
Globby:匹配给定用户友好模式的文件名;
接下来是他所分享的一些看法:
我在日常的工作中也体会到,大家仿佛有共识一般默认写自动化构建脚本时要去用 bash,希望这篇文章可以带给大伙一些不一样的思考,也许 JavaScript 来写会更好?
先看看几个可能的优点:
- 你的团队可能对 JS 最熟悉
- dev 和 CI 机器上很可能默认安装了 Node
- 直接可以访问其他 JS 工具
- Node 是跨平台的运行时
- 进程间通信是异步的,而且相当方便
如果你时间不多的话,不妨看看快速比较表格:
这是你团队的主要语言
相比于 bash,大多数前端团队都更熟悉 JS。Node 是具有特殊的 API,但总的来说它有函数一等公民,循环和 promise 等熟悉特性。bash?我搞了几年下来还是不确定它是咋工作的 —— 语法很熟悉,但在意想不到的地方又不一样,大多数变量是字符串,到底存在模块不?如果我错了,也不要纠正我,我不关心了。我一直只是用的时候去谷歌……
每个体面的程序员都需要学习 bash?这是病态的!如果你的后端同事需要在你的项目中做一些紧急改动,那他应该学习一些 JS。C 语言风格的语法让任何人都能大概了解代码的意图。当然从这个角度来看 bash 也差不多,但 JS 在这里起码并不比它差。
在 JS 优先的团队中使用 JS 进行自动化脚本的编写,是最合乎逻辑的选择。
runtime 大概率已经安装了
你的 bash 脚本即使成功运行了,麻烦也没有结束,因为它通常会在另一台机器上失败(说你呢,Alpine Docker 容器……)。各种 shells[3](SH,ASH,BASH,ZSH)都略有不同,在不同的 Linux 发行版上也不完全通用。你当然可以手动挑选必要的包,或者重新手写逻辑,但是真的很浪费时间。
用 Node 的话,丢失的 runtimes 的问题非常少见 - CI 机器无论如何都可以运行 npm / yarn,这些和 node 绑在一起。此外,一旦 node 程序编写完成,通常每台计算机上都可以运行。
开箱即用的跨平台特性
这就引出了下一点 —— node 是一个跨平台的运行时,在 linux、mac 和 windows 上运行良好。对,MacOS 是兼容 POSIX 的,但是许多命令在选项和输出格式上仍然有细微的差异。现在,你需要 Windows 支持吗?虽然大多数前端开发人员都使用 Mac,而且存在 Win 的 bash 端口。但是,免费支持开箱即用总是很好的:
- 降低了开源项目的贡献障碍。
- 一旦我需要匆忙在 Windows 服务器上启动 dev 服务器的时候,一般都很不愉快。
- 经理想玩玩你的项目,但他用的是 Win 电脑。
Node 团队花了大量时间抽象出操作系统之间的差异。忽视这一点,而去坚持使用 bash,会适得其反。
直接访问其他 JS 工具
前端工作流(webpack/parcel/babel/PostSS)中的大多数工具都开放了 node APIs。甚至像 esbuild 和 swc 这样的非 JS 工具也提供 node bindings。如果你的自动化编排在 node 上运行,那么访问这些 API 就很简单:只需导入包并调用函数。
在 bash 中,有两个麻烦的选项可以与基于 node 的工具集成:
- 通过奇怪的选项格式调用 CLI。
- 编写一个最小的 JS 包装器来调用 node API,从 bash 调用它。
另外一个好处是,由于许多工具的 CLI 位于单独的软件包中(如 @babel/CLI),如果直接使用 node API,可以跳过安装,从而节省一点 npm i时间。
体面的进程间通信
node 作为自动化运行时的一个很棒的方面是它的 IPC 能力。有时候你更喜欢通过 CLI 而不是 node API 使用其他工具。也可以 —— 在 node 中,这可以通过 child_process[4] 异步且跨平台地完成!你甚至可以在不同的进程之间使用管道输出,就像 shell 的管道操作符 |。虽然内置的Stream和child_process API 可能不太符合人体工程学,但你可以根据自己的口味使用包装器——我比较喜欢execa。
bash 也擅长于流程管理,但对我来说,有太多的可能性了——参考这个 stackoverflow 问题:里面提到有五种不同的并行运行命令的方式[5],如果你不知道自己在做什么,这就很容易让你搬起石头砸自己的脚。
庞大的生态系统
npm 为各种各样的问题提供了很好的解决方案。我最喜欢的是管理子进程的 execa[6]、处理 CLI 选项的yargs[7]和输出样式的chalk[8]。
是的,也存在类似的许多命令行工具,但必须使用特定于操作系统的软件包管理器(apt?brew?apk?)安装它们。大伙真的不想处理这种问题。此外,您安装的任何 CLI 软件包也可以通过 spawn/exec 在 node 中使用。
因此,以下是我选择 JS/node 来管理复杂自动化工作流的主要原因:
- JS 是你们团队的主要语言!
- 节点运行时通常安装在本地和 CI 中,因为您处理的是 npm/Spread。
- node 跨平台运行,与 bash 和 make 不同。
- node 可以直接访问其他 JS 工具。
- node IPC(用于编排 CLI 工具)非常合适,尤其是使用 execa 时。
- 在 node 中编写 CLI 工具,有很多好用的软件包。
当然也有理由避免使用 node(比如缺少关于自动化用例的教程,对于不熟悉 node 的人来说,异步的复杂性),但我仍然相信它是 JS 项目中构建自动化流程最可靠的选择。
Vladimir:https://thoughtspile.github.io/2022/02/14/js-automation
参考资料
[1]Vladimir: https://twitter.com/thoughtspile
[2]zx: https://github.com/google/zx
[3]各种 shells: https://en.wikipedia.org/wiki/Shell_script
[4]child_process: https://nodejs.org/api/child_process.html
[5]里面提到有五种不同的并行运行命令的方式: https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script
[6]execa: https://github.com/sindresorhus/execa
[7]yargs: https://github.com/yargs/yargs
[8]chalk: https://github.com/chalk/chalk