为什么我放弃 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: 依赖插件作者
当你使用新技术或者不流行的技术时,可能根本没有相关的插件。即使有插件,它可能已经过时了。例如,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的潜在原因将更多:
是不是基础工具出错了?
是不是 Grunt / Gulp 插件出错了?
是不是我的配置错了?
是不是我用了不兼容的版本?
使用 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 变得这么流行:
人们认为使用 npm Scripts 需要很高的命令行编写能力
人们认为 npm Scripts 不够强大
人们认为 Gulp 的流处理对快速构建来说是必不可少的
人们认为 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 代码:
{
"name": "npm-scripts-example",
"version": "1.0.0",
"description": "npm scripts example",
"scripts": {
"prebuild": "echo I run before the build script",
"build": "cross-env NODE_ENV=production webpack",
"postbuild": "echo I run after the build script"
}
}
你所需要做的就是遵循约定。上面的脚本会根据前缀按顺序执行。prebuild 脚本会在 build 脚本之前执行,它对比 build 脚本有前缀 ”pre”,而 post 脚本会在 build 脚本后面执行因为有前缀 ”post” 。所以如果我创建了 Scripts : prebuild
, build
和 postbuild
,当我输入 npm run build
时,它们将会自动按顺序执行。
你也可以通过调用另一个脚本,把一个大任务拆分:
package.json 代码:
{
"name": "npm-scripts-example",
"version": "1.0.0",
"description": "npm scripts example",
"scripts": {
"clean": "rimraf ./dist && mkdir dist",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production webpack"
}
}
上面的例子中,prebuild
任务调用了clean
任务。这允许你把你的脚本拆分成更小、命名更恰当、单一职责、一行内完成。
你可以使用 &&
操作符将多个 Scripts 串联成一行。 在上述的 clean
步骤将会顺序执行各个 Scripts 。如果你曾经为了让任务列表能在 Gulp 中按顺序执行而苦苦挣扎过,那么这种简洁真的会让你笑出声来。
并且如果一个命令实在太复杂了,你也可以调用其他的文件:
package.json 代码:
{
"name": "npm-scripts-example",
"version": "1.0.0",
"description": "npm scripts example",
"scripts": {
"build": "node build.js"
}
}
上面的例子中,我在 build
任务中调用了一个独立的脚本。这个脚本将会通过Node执行, 而且因此我可以应用任何我需要的 npm 包, 并且应用所有 JavaScript 中的功能。
我就不继续说了,主要的特性都在这里。还有,这里还有一个 npm 作为构建工具的简短介绍, 或者查看 React Slingshot 项目并将其当作所有这些行为的一个例子。
误解#3: Gulp 的流处理对快速构建来说是必不可少的
Gulp可以从Grunt上快速取得市场主导权,一个原因就是 Gulp 基于内存操作的stream操作要比Grunt的文件操作要快。但是但其实要使用stream的力量你完全可以不用 Gulp。 事实上,流功能早就已经内置到了 Unix 和 Windows 的命令行中。 管道(|
)操作符可以将一个命令以流的形式输出作为另一个命令的输入。 而重定向(>
)操作符可以将定向输出到一个文件中。
所以, 举个例子, 在Unix中我可以使用 grep
读取一个文件的内容,并将其输出输出到一个新文件中:
CommandLine 代码:
grep 'Cory House' bigFile.txt > linesThatHaveMyName.txt
上面所做的就是流,没有中间的文件被写。(想知道上面的命令怎么跨平台使用吗?那继续读下去…)
你同样可以使用’&’操作符在Unix上同时执行两个命令:
CommandLine 代码:
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 代码:
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
里添加注释。有几个方法可以用来处理这个限制:
简短、命名良好、目的单一的 Scripts
单独为 Scripts 提供文档(例如写在一个 `README.md` 文件中)
调用单独的`.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与Grunt,转投npm scripts(上)
我为何放弃Gulp与Grunt,转投npm scripts(上)
我为何放弃Gulp与Grunt,转投npm scripts(上)