你的位置:首页 > Java教程

[Java教程][译]为什么我要离开gulp和grunt转投npm脚本的怀抱


https://medium.freecodecamp.com/why-i-left-gulp-and-grunt-for-npm-scripts-3d6853dd22b8#.n7m1855uf

原文链接:https://medium.freecodecamp.com/why-i-left-gulp-and-grunt-for-npm-scripts-3d6853dd22b8#.n7m1855uf

作者:Cory House

------------------------------------------------------------------------------------------------------------------------------

我知道你在想什么。 什么?!Gulp不是才刚把Grunt干掉吗? 为什么我们不能在Javascript的地盘上消停一会儿? 我听说过, 但是。。。

 

我发现Gulp和Grunt都是些不必要的抽象。npm脚本已经足够强大而且往往更容易忍受。

 

让我们从一个例子开始

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

 

这些都是可以解决的问题,但是当我直接使用这些工具时从不会发生这些问题。

 

最近我注意到很多开源项目都仅仅使用npm脚本。我决定退一步重新审视自己。 我真的需要Gulp吗? 原来并不是。

 

我决定在我的新开源项目中尝试只使用npm脚本。我创建了一个富开发环境,并仅用npm脚本为React应用创建了一个进程。想知道这看起来像什么吗? 你可以查看React弹弓项目。 在Pluralsight平台(一家美国软件开发在线教育平台)上我解决了用npm脚本为“用ES6编写React和Redux”这个目标创建进程的问题。

 

让我惊喜的是,相比Gulp,现在我更乐于使用npm脚本。这就是原因。

 

Gulp和Grunt做错什么了?

随着时间的推移,我注意到像Gulp和Grunt这样的任务执行器都具有的三个核心问题:

1. 依赖插件制作者

2. 调试困难

3. 滞后的文档

让我们依次思考这三个问题。

 

问题#1: 依赖插件制作者

当你使用一个新的或不流行的技术时,可能根本没有对应的插件。 当有插件出现时,这个技术可能已经过时了。 举个例子, Babel6刚刚发布不久。 它的API变化很大,很多Gulp插件不兼容这个最新版本。当使用Gulp时,我曾因为我需要的Gulp插件还没更新而被卡住了。

 

使用Gulp或者Grunt的话,你必须要等插件维护者提供更新,或者你自己来修复这些问题。这会延缓你应用最新的现代化工具的能力。相对的,当我使用npm脚本时,我直接使用工具,而不需要一个额外的抽象层。这表示当Mocha、Istanbul、Babel、Webpack、Browserify等工具的最新版本发布时,我有办法马上应用这些最新的版本。

 

在选择方面,没什么可以打败npm:

当你使用npm脚本时,你不需要搜索任何Grunt或Gulp插件。你直接在超过227,000个npm包中选择即可。

 

公平起见,如果你需要的Grunt或Gulp插件变得不可用了,你当然可以直接应用npm包。但从此你就不再需要为了那些特殊的任务而借助Gulp或Grunt了。

 

问题#2: 调试困难

在集成失败时,在Grunt和Gulp中进行调试会让人很泄气。 当你在一个额外的抽象层下工作的时候,可能引发bug的潜在原因将更多:

1. 是不是基础工具坏了?

2. 是不是Grunt/Gulp组件坏了?

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

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

使用npm脚本可以根除第二点. 我觉得第三点很少见,因为我通常是直接调用工具的命令行接口。 最后,自从我直接使用npm代替任务执行器的抽象概念后,我项目中包的数量减少了,第四点也很少见了。

 

问题#3:滞后的文档

我需要的核心工具的文档几乎总是比相应的Grunt和Gulp组件的要好。例如, 如果我使用gulp-eslint,到头来我需要将时间分别花在gulp-eslint文档和ESLint网站上。 我不得不在抽象的组件和工具之间来回切换上下文环境。 Gulp和Grunt中的核心矛盾是这个:

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

 

大多数的工具提供清晰、强大和具备良好文档的命令行接口。看看ESLint的CLI文档就是一个很好的例子。我发现在npm脚本中读取和实现一个简短的命令行调用会更清晰、低冲突且更易于调试(在把那些抽象层移除后)。

 

现在我认为我已经找到了痛点, 问题是, 为什么我们觉得我们需要像Gulp和Grunt那样的任务执行器?

 

为什么我们忽略了npm也可以编译?

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

1. 人们认为使用npm脚本需要很高的命令行水平

2. 人们认为npm脚本不够强大

3. 人们认为Gulp的流对快速构建来说是必须的

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

让我们一个个解决这些误解。

 

误解#1:npm scripts需要很高的命令行水平

你不需要知道你的操作系统的很多命令行知识也可以享用到npm脚本的力量。当然,grep、sed、awk和pipes命令是值得终身学习的技能,但你不必为了使用npm脚本而成为一个Unix或windows的命令行大师。 你可以改为在npm中写上千行书写良好的脚本来完成你的工作。

 

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

 

误解#2: npm scripts不够强大

npm基本仅靠自己已经强大得令人惊讶。这些是常规的pre和post钩子:

{ "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" }}

 Github上的package.json链接

 

所有你要做的就是遵循约定。 以上脚本会基于他们的前缀按顺序执行。 prebuild脚本会在build脚本之前执行,因为它有相同的名字,但是用了”pre“前缀。 postbuild脚本会在build脚本之后执行,因为它有”post“前缀。 所以, 如果我创建的脚本命名为prebuild、build和postbuild, 当我敲入‘npm run build’命令时,它们会自动按顺序执行。

 

你也可以通过用一个脚本调用另一个脚本的方法来将大问题分解:

{ "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" }}

Github上的package.json链接

 

在这个例子中,prebuild任务调用clean任务。这允许你将你的脚本分解得更小、命名更恰当、单一职责、一行内完成。

 

你可以用&&操作符在一行命令中同步调用多个脚本。 在上述的clean步骤中的脚本会一个接一个执行。这种简洁真的会让你笑出声,如果你曾为了让任务列表能在Gulp中按顺序执行而苦苦挣扎过。

 

而且如果一个命令实在太复杂,你总是可以调用一个单独的文件:

{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": {  "build": "node build.js" }}

Github上的package.json链接

 

以上代码中,我正在build任务中调用一个单独的脚本。这个脚本将会通过Node执行, 而且因此我可以应用任何我需要的npm包, 和应用所有javascript中的功能。

 

我可以继续了, 但核心功能都记录在这里。还有,这里也有一个短小的关于将npm作为一个构建工具的Pluralsight课程. 或者,查看React弹弓项目并将其当作所有这些行为的一个例子。

 

误解#3: Gulp的流是快速构建所必需的

Gulp可以从Grunt上快速取得市场主导权,是因为Gulp的内存流比Grunt的基于文件的做法快得多。但是你并不需要Gulp来享用流的强大。 事实上, 流功能早就已经集成到Unix和Windows的命令行中。 管道(|)操作符可以将一个命令的输出以流的形式作为另一个命令的输入。 而重定向(>)操作符可以将输出重定向到一个文件中。

 

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

grep ‘Cory House’ bigFile.txt > linesThatHaveMyName.txt

 

上面的做法就是流。没有任何中间文件被创建。

(想知道上述命令在跨平台的情况下该怎么做? 请读下去。。。)

 

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

npm run script1.js & npm run script2.js

 

以上这两个脚本会在同时执行。为了跨平台的同时执行脚本,可以使用npm-run-all。这就导向了下一个误解。。。

 

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

很多项目都依赖于特定操作系统,所以跨平台的顾虑并不算重要。但如果你需要跨平台运行,npm脚本依然可以工作得很好。数不清的开源项目就是证据。接下来就是怎么做了。

 

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

 

方法1: 使用跨平台的命令。存在数量惊人的跨平台命令。 这里只是一小部分:

&& chain tasks (Run one task after another)< input file contents to a command> redirect command output to a file| redirect command output to another command

 

方法2: 使用node包。 你可以用node包代替shell命令。例如, 使用rimraf代替'rm -rf‘. 使用cross-env在跨平台方式下设置环境变量。 在google、npm或libraries.io中搜索你想要做什么,几乎肯定会有一个node包可以实现你的需求而且还是跨平台的。而且如果你的命令行调用已经太长,你可以在单独脚本中调用Node包,就像这样:

node scriptName.js

 

上述脚本是普通的古老的javascript文件,通过Node执行。 并且因为你只是在命令行上调用一个脚本, 你不止可以调用js文件。你可以运行任何你的操作系统能执行的脚本, 例如Bash、Python、Ruby或Powershell等等。

 

方法3: 使用ShellJS。 ShellJS是一个npm包, 它可以通过Node运行Unix命令。 这就给了你在任何平台上执行Unix命令的能力, 包括Windows。

 

我在React弹弓项目中同时使用了方法#1和#2。

 

痛点

诚然,npm脚本也有一些缺点: JSON规范并不支持注释, 所以你无法在package.json文件中添加注释。 这里有几个方法可以围绕这个限制开展工作:

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

2. 单独对脚本提供文档(例如在一个README.md文件中)

3. 调用单独的js文件

 

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

 

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

 

最后,在创建非常长且很复杂命令行参数时,这依然是有风险的,因为这些命令非常难理解。 代码审查和勤于重构是一个很好的方法,可以保证npm脚本都被分解成简短、命名良好且目的单一的函数,这能让所有人都能理解这些脚本。 如果脚本复杂到需要注释,你应该可以很容易的将单个脚本重构成多个命名良好的脚本,或直接将其提取到一个单独的文件中。

 

增加抽象概念必须理由充分

Gulp和Grunt都是将我使用的工具进行再次抽象。 抽象概念是有用的,但是抽象概念需要成本。 它们可能造成内存泄漏。 它们让我们依赖于插件维护者和他们的文档。而且他们通过增加依赖项的数量来增加了复杂度。 我已经决定像Gulp和Grunt这样的任务执行器的抽象概念我再也不需要了。

 

想要更多细节? 在Pluralsight平台(一家美国软件开发在线教育平台)上我解决了用npm scripts为“用ES6编写React和Redux”这个目标创建进程的问题。

 

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

 

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

· 用npm run完成任务自动化 -- James Holliday

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

· 如何将npm用成一个构建工具 -- Kieth Cirkel

· npm作为构建工具介绍 -- Marcus Hammarberg

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

· NPM脚本之于构建工具 -- Andrew Burgess

        …………

 

 

Cory House是《用React和Flux创建应用》、《ES6下的React和Redux》、《简洁代码:编写人能看懂的代码》和其他多个Pluralsight上的课程的作者。 他是VinSolutions 的软件架构师,并且通过像前端开发和简洁代码这些软件实践来训练国际化的软件工程师。Cory是一位微软的MVP、Telerik开发专家同时还是outlierdeveloper.com网站的创始人。

 

---------------------

谢谢观看。 翻译的不对欢迎指出。

2016.08.23