为什么我放弃 Gulp 和 Grunt 而使用 npm Scripts

Posted WEB前端开发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我放弃 Gulp 和 Grunt 而使用 npm Scripts相关的知识,希望对你有一定的参考价值。

我知道你在想什么。 什么?!Gulp不是刚刚干掉了Grunt吗? 为什么我们不能在javascript的地盘上消停一会儿呢?我知道,但是…

我发现Gulp和Grunt是没有必要的抽象,npm Script 已经足够强大,并且通常更好用。

让我们从一个例子开始

我原来是一个Gulp的超级粉丝。但是在我的最近一个项目中, 最终我的 gulpfile 中有几百行代码和一大堆 Gulp 插件。我曾经为了用 Gulp 整合 Webpack、Browsersync、hot reloading、Mocha等等东西而需要花费很多尽力。为什么? 好吧,有些插件的文档没覆盖到我的使用场景。有些插件只公开了部分我需要的API。有个插件有一个奇怪的bug,它能监视的文件数很小。另一个插件则在命令行中输出信息时把颜色都给去掉了。

这些都是可以解决的问题,但是我在直接调用这些工具的时候,一个问题也没有出现。

后来我注意到很多开源的项目都直接使用了 npm Scripts 。我决定退一步重新评估,我真的需要Gulp吗?结果证明我不需要。

我决定在我新的开源项目上尝试使用 npm Scripts。我创建了一个富开发环境,并只用 npm Scripts 为 React 应用创建了一个进程。好奇是什么样子? 请查看 React Slingshot 。 我将在 Pluralsight (愚人码头注:一家美国软件开发在线教育网站) 的 “构建JavaScript开发环境” 中介绍如何使用 npm Scripts 来创建构建过程。

令人惊讶的是,相比Gulp,我现在更喜欢使用 npm Scripts 来 Gulp。 这是为什么。

Gulp和Grunt有什么问题?

用了一段时间后,我发现类似 Gulp 和 Grunt 的任务执行工具有三个主要的问题:

  1. 依赖插件作者

  2. 难以调试

  3. 脱节的文档

我们先来思考一下这些问题。

问题#1: 依赖插件作者

当你使用新技术或者不流行的技术时,可能根本没有相关的插件。即使有插件,它可能已经过时了。例如,Babel 6 最近发布了。API改变了不少,所以很多 Gulp 插件不兼容最新的版本。使用 Gulp 的时候,我经常束手无策,因为我需要的 Gulp 插件还没更新。

Gulp 和 Grunt 遇到问题你通常都要等作者提供更新,或者自己修复。这会延迟您使用新版本的现代工具。这限制了你使用很多流行工具的最近版本。相比之下,当我使用 npm Scripts 时,我直接使用工具,而没有额外的抽象层。这意味着,当Mocha,Istanbul,Babel,Webpack,Browserify等工具新版本发布时,我可以立即使用新版本。

在插件数量方面,跟 npm 没有相比性:

Gulp 大约有 2,100个插件。 Grunt 大约有 5,400 个。 npm提供超过227,000个包,而且每天以400+的速度增长。

当您使用 npm Scripts 时,您不需要搜索 Grunt 或 Gulp 插件。您可以选择 227,000+ 的 npm 包。

公平起见,如果你需要的 Grunt 或 Gulp 插件不可用,你可以直接使用 npm 包。但是,你不再需要利用 Gulp 或 Grunt 来完成这个特定的任务。

问题#2: 难以调试

使用 Grunt 和 Gulp 构建失败时,调试是比较麻烦的。因为你是多了一层额外的抽象层,所以引发bug的潜在原因将更多:

  1. 是不是基础工具出错了?

  2. 是不是 Grunt / Gulp 插件出错了?

  3. 是不是我的配置错了?

  4. 是不是我用了不兼容的版本?

使用 npm Scripts 不会出现问题2,并且我发现问题3也很少出现,因为我一般直接调用工具的命令行接口。最重要的是,我直接使用 npm 代替任务运行器的抽象后,我项目中包的数量减少了,因此问题4也很少出现。

问题#3:脱节的文档

我需要的核心工具的文档几乎总是比相应的 Grunt 和 Gulp 插件文档的要好。例如,如果我使用 gulp-eslint 时,我需要将时间分别花在 gulp-eslint 文档和 ESLint 网站上。 我必须在插件和它抽象的工具之间来回切换上下文。 Gulp 和 Grunt 中的核心的痛点是:

只了解工具是远远不够的。Gulp 和 Grunt 还要求你理解插件的抽象概念。

大多数构建相关的工具提供了清晰,强大和具有完善文档的命令行接口。看看 ESLint 的 CLI 文档就是一个很好的例子。我发现在 npm Scripts 中阅读并且实现一个简短的命令行调用会更清晰,低冲突,并且更容易调试(因为没有了一层抽象).

现在我已经列出了所有痛点,问题是,为什么我们还会认为我们需要像 Gulp 和 Grunt 之类的任务执行器?

为什么我们忽略了npm的构建功能?

我认为有四个关键的误解导致 Gulp 和 Grunt 变得这么流行:

  1. 人们认为使用 npm Scripts 需要很高的命令行编写能力

  2. 人们认为 npm Scripts 不够强大

  3. 人们认为 Gulp 的流处理对快速构建来说是必不可少的

  4. 人们认为 npm Scripts 不能跨平台运行

让我们一个一个消除这些误解。

误解#1:npm Scripts 需要很高的命令行编写能力

要享受使用 npm Scripts 的力量,你不需要掌握很多有关操作系统的命令行。当然,grep,sed,awk 和 pipe 是值得学习终身受用的技能,但要使用 npm Scripts 你也不用成为 Unix 或者 Windows 命令行大师。你也可以使用 npm 中的数千个包来完成工作。

例如,你可能不知道在 Unix 中强制删除的命令行是:rm -rf。不过没关系。 你可以使用 rimraf 来完成相同的工作(并且它可以跨平台使用)。大多数 npm 包是在假设你对操作系统命令行知识知之甚少的前提下提供接口。当你要用某个功能的时候,只需要在 npm 上搜索你需要的包,阅读文档,学习即可。我之前都是搜索Gulp插件,现在我搜索 npm 包。顺便提供一个不错的资源:libraries.io

误解#2: npm Scripts 不够强大

npm Scripts 其实是很强大的。这些是常规的pre和post钩子:

package.json 代码:
  1. {

  2.  "name": "npm-scripts-example",

  3.  "version": "1.0.0",

  4.  "description": "npm scripts example",

  5.  "scripts": {

  6.    "prebuild": "echo I run before the build script",

  7.    "build": "cross-env NODE_ENV=production webpack",

  8.    "postbuild": "echo I run after the build script"

  9.  }

  10. }

你所需要做的就是遵循约定。上面的脚本会根据前缀按顺序执行。prebuild 脚本会在 build 脚本之前执行,它对比 build 脚本有前缀 ”pre”,而 post 脚本会在 build 脚本后面执行因为有前缀 ”post” 。所以如果我创建了 Scripts : prebuild , build 和 postbuild ,当我输入 npm run build 时,它们将会自动按顺序执行。

你也可以通过调用另一个脚本,把一个大任务拆分:

package.json 代码:
  1. {

  2.  "name": "npm-scripts-example",

  3.  "version": "1.0.0",

  4.  "description": "npm scripts example",

  5.  "scripts": {

  6.    "clean": "rimraf ./dist && mkdir dist",

  7.    "prebuild": "npm run clean",

  8.    "build": "cross-env NODE_ENV=production webpack"

  9.  }

  10. }

上面的例子中,prebuild任务调用了clean任务。这允许你把你的脚本拆分成更小、命名更恰当、单一职责、一行内完成。

你可以使用 && 操作符将多个 Scripts 串联成一行。 在上述的 clean 步骤将会顺序执行各个 Scripts 。如果你曾经为了让任务列表能在 Gulp 中按顺序执行而苦苦挣扎过,那么这种简洁真的会让你笑出声来。

并且如果一个命令实在太复杂了,你也可以调用其他的文件:

package.json 代码:
  1. {

  2.  "name": "npm-scripts-example",

  3.  "version": "1.0.0",

  4.  "description": "npm scripts example",

  5.  "scripts": {

  6.    "build": "node build.js"

  7.  }

  8. }

上面的例子中,我在 build 任务中调用了一个独立的脚本。这个脚本将会通过Node执行, 而且因此我可以应用任何我需要的 npm 包, 并且应用所有 JavaScript 中的功能。

我就不继续说了,主要的特性都在这里。还有,这里还有一个 npm 作为构建工具的简短介绍, 或者查看 React Slingshot 项目并将其当作所有这些行为的一个例子。

误解#3: Gulp 的流处理对快速构建来说是必不可少的

Gulp可以从Grunt上快速取得市场主导权,一个原因就是 Gulp 基于内存操作的stream操作要比Grunt的文件操作要快。但是但其实要使用stream的力量你完全可以不用 Gulp。 事实上,流功能早就已经内置到了 Unix 和 Windows 的命令行中。 管道(|)操作符可以将一个命令以流的形式输出作为另一个命令的输入。 而重定向(>)操作符可以将定向输出到一个文件中。

所以, 举个例子, 在Unix中我可以使用 grep 读取一个文件的内容,并将其输出输出到一个新文件中:

CommandLine 代码:
  1. grep 'Cory House' bigFile.txt > linesThatHaveMyName.txt

上面所做的就是流,没有中间的文件被写。(想知道上面的命令怎么跨平台使用吗?那继续读下去…)

你同样可以使用’&’操作符在Unix上同时执行两个命令:

CommandLine 代码:
  1. npm run script1.js & npm run script2.js

上面两个脚本将同时执行,想要跨平台的同时执行脚本,使用 npm-run-all 。这引出了我们的下一个误解…

误解#4: npm Scripts 不能跨平台运行

很多项目都是在特定的操作系统使用,所以没有跨平台的忧虑。但如果你需要跨平台运行,npm Scripts 也能够很好的工作。无数的开源项目就是证明。接下来就是怎么做了。

你的操作系统命令行执行 npm Scripts ,所以在Linux和OSX,你的 npm Scripts 通过Unix命令行执行。而在 Windows 上,npm Scripts 通过 Windows 命令行执行。因此,如果你要你的构建脚本可以在所有平台上执行,你需要同时让Unix和Windows开心。 这里有3个方法:

方法1: 使用跨平台的命令。很幸运的是竟然有如此多的跨平台命令,以下是其中一小部分:

  • `&&` 串联任务 (运行一个任务后,再运行另一个任务)

  • `<` 输入文件内容到命令

  • `>` 重定向命令输出到一个文件

  • `|` 重定向命令输出到另一个命令

方法2: 使用 node packages 。你可以使用 node packages 来取代 shell 命令。例如,使用 rimraf 取代rm -rf。使用 cross-env 来跨平台地设置环境变量。在 Google , npm 或者 lirbraries.io 上搜索你所需要的 node package ,一般都能找到一个可以跨平台的。另外,如果你的命令行调用过长,你可以调用单独脚本中的 node packages,类似这样:

CommandLine 代码:
  1. node scriptName.js

上面脚本是一个普通的老的 javascript 文件,通过 Node 执行。 如果你仅仅是想用命令行调用一个脚本,你可以不使用.js文件。你可以运行任何你的操作系统能执行的脚本, 例如 Bash 、 Python 、 Ruby 或 Powershell 等等。

方法3: 使用 ShellJS 。ShellJS是一个通过 Node 执行 Unix 命令行的 npm package。所以这可以让你在任何平台上面执行 Unix 命令,包括 Windows。

我在React Slingshot 项目中使用了方法1和方法2。

痛点

不可否认地是, npm Scripts 也存在一些缺点:因为 JSON 规范不支持添加注释,所以你不能在 package.json 里添加注释。有几个方法可以用来处理这个限制:

  1. 简短、命名良好、目的单一的 Scripts

  2. 单独为 Scripts 提供文档(例如写在一个 `README.md` 文件中)

  3. 调用单独的`.js`文件

我更倾向于第1种方法。如果你将每个脚本都分解成只有单一职责, 将很少再需要注释。脚本的名字可以完全描述其意图,就好像所有简短且命名良好的函数一样。就像我在《简洁代码:编写人能看懂的代码》中的讨论一样,短小且单一职责的函数很少需要注释。当我觉得注释是必要的时,我使用方法3,并将脚本移到单独的文件中。这让我在需要时可以使用javascript的所有能力。

package.json也不支持变量。这听起来像是一个大问题,但由于以下两个原因,它不再是问题。 首先,最通常的需要变量的情况是要解决环境问题,但这你可以在命令行中设置。其次,如果你因为其他原因需要用到变量,你完全可以调用一个单独的js文件。 在React-starter-kit项目中你可以找到该做法的一个优雅的例子。

总之,仍然会有可能创建让人很难看懂的又长又臭的命令行参数。而确保 npm Scripts 分离成简洁、单一职责并且命名规范容易理解的小功能,代码审查和不断的重构是一个不错的方法。而如果 Scripts 复杂到真的需要注释,你应该把单一的脚本分离成多个命名规范的脚本,或者抽离到分离的文件。

抽象要恰当

Gulp 和 Grunt 都是我使用过的抽象工具。抽象是有用的,但是也有代价。它们让我们依赖于插件的维护者和文档,并且越来越多的依赖使他们更复杂。我已经觉得我不再需要 Gulp 和 Grunt 这样的任务执行器了。

想要更多细节?在 Pluralsight 网站上 “构建JavaScript开发环境”中从头开始学习如何使用npm脚本创建构建过程。

评论? 可以在文章底部、Reddit 或 Hacker News 上进行评论。

最后, 我离第一个建议这么做的人已经很遥远。 下面是一些非常棒的链接:

  • 用npm run完成任务自动化 — James Holliday

  • 使用npm脚本实现进阶前端自动化 — Kate Hudson

  • 如何将npm用成一个构建工具 — Kieth Cirkel

  • npm作为构建工具介绍 — Marcus Hammarberg

  • Gulp非常棒,但是我们真的需要它吗? — Gonto

  • NPM脚本之于构建工具 — Andrew Burgess


以上是关于为什么我放弃 Gulp 和 Grunt 而使用 npm Scripts的主要内容,如果未能解决你的问题,请参考以下文章

gulp (转)

我为何放弃Gulp与Grunt,转投npm scripts(上)

我为何放弃Gulp与Grunt,转投npm scripts(上)

我为何放弃Gulp与Grunt,转投npm scripts(上)

我为何放弃Gulp与Grunt,转投npm scripts(中)

我为何放弃Gulp与Grunt,转投npm scripts(下)