[TOC]
*andua*
# JavaScript構建(編繹)系統大比拼:Grunt vs. Gulp vs. NPM
> 英文原文:[A JavaScript Build System Shootout: Grunt vs. Gulp vs. NPM ](http://modernweb.com/2014/08/04/choose-grunt-gulp-npm/?utm_source=ourjs.com)
>譯者:kris
決定采用何種技術總是很難的。一旦遇到問題,你不想推翻你之前的選擇。但是你必須選一個,然后讓它按照著你的思路做。實施一套構建(編繹)系也是一樣的,你應該把它看作一個非常重要的選擇,讓我們以 Grunt 為例。
Grunt 有一個完善的社區,即使是在 Windows 上,它不僅僅應用在 Node 社區,它簡單易學,你可以隨便安裝插件并配置它們
你不需要多先進的理念,也不需要任何經驗。
這些都是用 Grunt 構建編繹工具的充分理由,但我想澄清一點,我不認為 Grunt 不是唯一最好的選擇。還有一些同樣流行的選擇擺在那里,有些方面可能比 Grunt 做得更好。
我寫這篇文章,以幫助您了解 Grunt,Gulp 和 npm 之間的差異,這是我在前端開發工作中使用最多的三種構建工具。
## 我們先來討論 Grunt 擅長的方面
Grunt:好的部分
Grunt 最好的一個方面是它的易用性。它能使程序員使用 JavaScript 構建編繹工具時,幾乎不費吹灰之力。你只需要尋找合適的插件,閱讀它們的文檔,然后安裝和配置它。這種易用性意味著大型開發團隊,那些不同技能水平的成員,也可以沒有任何麻煩的調整編繹流程,以滿足項目的最新需求。而且團隊并不需要精通 Node,他們僅需要配置對象,將不同的任務添加到不同的序列構建編繹流程。
這里有基礎足夠大的插件庫,你會發現自己幾乎不需要開發自己的編譯任務,這能使您和您的團隊能夠快速構建開發工具,如果你要快速完成編繹過程這是至關重要的,你也可以采取小步走,逐步完善編譯流程的策略。
通過 Grunt 管理部署也是可行的,因為有許多包已經可以完成這些任務,如 grunt-git, grunt-rsync, 或 grunt-ec2 等等。
那么,Grunt 有什么缺陷嗎?如果你有一個明顯復雜的編繹過程,它可能會變得過于冗長。當開發一段時間以后,它往往很難將編繹過程作為一個整體。一旦你編繹流程任務到達兩位數,幾乎可以保證,你會發現自己不得不在多個目標(Targets)中跑同一個 Task,以便你能夠正確地執行任務流。由于任務是需要聲明配置的,你也很難弄清楚任務真正的執行次序。
除此之外,你的團隊應該致力于編寫可維護的代碼,當涉及到你的編繹,比如在使用 Grunt 的情況下,這意味著你需要為每個任務(或者每個編繹流)編寫一份獨立的配置文件,供你的團隊使用。
現在,我們已經了解了 Grunt 好和不好的方面,以及在何種情況下,比較適合作為你項目的編繹工具。我們再來談談 npm,它如何被用作構建工具,以及與 Grunt 有何不同。
## 將 npm 視為構建工具
為了將 NPM 用作構建工具,你需要一個 package.json 和 npm。制定 NPM 任務就像在腳本中添加屬性一樣簡單。該屬性的名稱將用作任務名和將要執行的命令。下面的這個 build 任務將預先檢查我們的 JavaScript 代碼中有沒有語法錯誤,例子使用 JSHint 命令行接口來。在命令行中你可以運行任何你需要的 shell。
~~~
{
"scripts": {
"test": "jshint . --exclude node_modules"
},
"devDependencies": {
"jshint": "^2.5.1"
}
}
~~~
一旦定義完成,就可以通過下面的命令來運行
`npm run test`
需要注意的是 npm 提供了運行特定任務的快捷方式。比如要運行 test,你可以簡單地使用 npm test 并省略動詞 run。您可以通過一個命令鏈來將一系列 npm run 的任務連在一起,構成你的編繹流程:
~~~
{
"scripts": {
"lint": "jshint . --exclude node_modules",
"unit": "tape test/*",
"test": "npm run lint && npm run unit"
},
"devDependencies": {
"jshint": "^2.5.1",
"tape": "~2.10.2"
}
}
~~~
您也可以安排一些后臺完成的任務,然后讓他們同步。假設我們有以下的包文件,我們將復制出一個目錄用來放 JavaScript 文件,以及將我們用 Stylus 寫的樣式表文件編繹成 CSS。在這種情況下,多個任務一起運行是比較理想的。也可以實現,使用&分隔符即可。
~~~
{
"scripts": {
"build-js": "cp -r src/js/vendor bin/js",
"build-css": "stylus src/css/all.styl -o bin/css",
"build": "npm run build-js & npm run build-css"
},
"devDependencies": {
"stylus": "^0.45.0"
}
}
~~~
要了解關于將 npm 用作構建工具的更多內容,你應該先學學寫一些 Bash 命令。
### 安裝 NPM 的任務依賴
JSHint CLI 并不一定要包含在你的系統中,這里有兩種安裝它的方式。如果你正在尋找直接從命令行中運行的工具,那么你應該在全局范圍內安裝,使用g標志,如下所示。
`npm install -g jshint`
不過,如果您使用的是包在 npm run 中使用的,那么你就應該把它加到 devDependency 中,如下所示。這將讓 npm 自動在系統中尋找它所依賴的 JSHint 安裝在了哪里。這方法適用于任何命令行工具中和所有操作系統。
`npm install --save-dev jshint`
你其實不僅局限使用 CLI 工具。事實上,npm 能夠運行任何 shell 腳本。讓我們來挖一挖!
在 npm 中使用 shell 腳本
下面是一個運行在 node 的腳本,并顯示一個隨機的繪文字符串(emoji-random)。第一行指定運行環境,該腳本基于 Node。
~~~
#!/usr/bin/env node
var emoji = require ('emoji-random');
var emo = emoji.random ();
console.log (emo);
~~~
如果你將一個名為 emoji 的腳本文件放到你項目的根目錄中,你必須將 emoji-random 申報為依賴關系,并將以下腳本命令添加到包文件中。
~~~
{
"scripts": {
"emoji": "./emoji"
},
"devDependencies": {
"emoji-random": "^0.1.2"
}
}
~~~
一旦寫成這樣,你只需要在命令行運行` npm run emoji` 即可。
### 好和壞的方面
使用 NPM 作為構建工具比 Grunt 有幾大優勢。你不會被 Grunt 的插件束縛,你可以利用 NPM 的所有優勢,它有數以萬計的模塊可以選擇。除了 NPM,你不需要任何額外的命令行工具(CLI)或文件,你只需要在 package.json 添加依賴關系。由于 NPM 運行命令行工具(CLI 工具)和 Bash 命令,這比 Grunt 執行的方式更好。
**Grunt 的最大缺點之一就是它的I/O限制。**這意味著大多數 Grunt 的任務將從磁盤中讀取,再寫入到磁盤。如果你的多個任務需要操作同一個文件,那么該文件很有可能被從磁盤中多次讀取。在 bash 中,命令通過管道直接傳遞給下一個任務,避免 Grunt 額外的I/O開銷。
也許 NPM 的最大的缺點是,在 Windows 環境中的應用可能沒那么好。這意味著使用 NPM 運行的開源項目可能遇到問題。這也意味著 Windows 開發人員嘗試使用 npm 的替代品。這缺點幾乎將 NPM 從 Windows 上排除。
Gulp,另一個構建工具,提出了與 Grunt 和 npm 相似的功能,一會你就會發現。
## Gulp 的流式構建工具
與 Grunt 類似,它依賴插件,并且是跨平臺的,它也支持 Windows。Gulp 是一個代碼驅動的構建工具,與 Grunt 的聲明式定義任務相反,它的任務定義更容易閱讀一點。Gulp 也有類似于 npm run 的東西,因為它使用 Node Stream 來轉化輸入輸出。這意味著,Gulp 沒有 Grunt 那種磁盤密集型I/O操作的問題。它也是它比 Grunp 更快的原因,更少的時間花在I/O上面。
**在使用 Gulp 的主要缺點是,它在很大程度上依賴于流,管道和異步代碼。**不要誤解我的意思:如果你用在 Node 中,這絕對是一個優勢。但是,除非你和你的團隊非常精通 Node,你很有可能會遇到處理流的問題,特別是如果你要建立你自己的 Gulp 任務插件。
在團隊工作的時候,Gulp 不是望而卻步的 npm,因為大多數前端團隊可能都懂 JavaScript,但是他們可能對 Bash 腳本不那么熟練,其中一些可能是使用 Windows 的!這就是為什么我通常建議你在個人項目中運行 NPM 的原因。如果你的團隊很熟悉 Node,你可以使用 Gulp。當然,這是我個人的建議,你應該找到最適合你和你團隊的工具。此外,你應該不會把自己限制在 Grunt,Gulp,或者 npm run 中,對你我來說這些都只是工具。嘗試做一些小小的研究,也許你會發現,你喜歡的甚至比這三個更好的工具。
讓我們通過一些例子來看看 Gulp 中的任務看起來是什么樣子的。
### 在 Gulp 中運行測試
有一些約定 Gulp 與 Grunt 極為相似。在 Grunt 中有一個定義 Task 的文件 Gruntfile.js,在 Gulp 中叫 Gulpfile.js。另一種微小的差別是,在 Gulp 中,CLI 已經包含在同一個 Gulp 包中,你需要通過 npm 從本地和全局同時安裝。
~~~
touch Gulpfile.js
npm install -g gulp
npm install --save-dev gulp
~~~
在開始之前,我將創建一個 Grulp 任務處理一個 JavaScript 文件,就像你已經在 Grunt 和 NPM 中看到的那樣使用 JSHint,你需要先安裝 gulp-jshint,Gulp 的 JSHint 插件。
`npm install --save-dev gulp-jshint`
現在你已經同時在全局和本地中裝好 CLI 了,本地已經安裝了 gulp 和 gulp-jshint 插件,你可以將這些構建任務合成一個。你可以在 Gulpfile.js 文件中寫出來。
首先,您將使用 gulp.task 定義一個任務和功能。該功能包含了所有必要的代碼來運行這項測試。在這里,你應該使用 gulp.src 創建一個讀取你源文件的流,這個數據流會被管道輸送進 JSHint 插件。然后,所有你需要做的就是管道中的 JSHint 任務打印到終端。下面是 Gulpfile 中展示的結果。
~~~
var gulp = require ('gulp');
var jshint = require ('gulp-jshint');
gulp.task ('test', function () {
return gulp
.src ('./sample.js')
.pipe (jshint ())
.pipe (jshint.reporter ('default'));
});
~~~
點需要提一下,Grulp 流會在一個任務完全結束之后再轉到下一個任務。你可以使用一個 JSHint Reporter 使輸出更加簡潔,從而更易于閱讀。 JSHint Reporter 并不需要 Grulp 插件,例如 jshint-stylish,讓我們在本地直接安裝。
`npm install --save-dev jshint-stylish`
更新后的 Gulpfile 應如下所示。它會加載 jshint-stylish 模塊,按報表格式輸出。
~~~
var gulp = require ('gulp');
var jshint = require ('gulp-jshint');
gulp.task ('test', function () {
return gulp
.src ('./sample.js')
.pipe (jshint ())
.pipe (jshint.reporter ('jshint-stylish'));
});
~~~
大功告成!這是所有一個命名為 test 的 Gulp 的任務。它可以使用下面的命令運行,只要你安裝了全局的 CLI。
`gulp test`
這是一個相當簡單的例子。你也可以通過使用 gulp.dest,創建了一個寫數據流到磁盤中。讓我們看看另外一個構建任務。
### 在 Grulp 中創建一個庫
在開始之前,讓我們明確任務:從磁盤 gulp.src 讀取源文件并通過磁盤管道寫回內容到 gulp.dest,你可以理解成只是將文件復制到另一個目錄。
~~~
var gulp = require ('gulp');
gulp.task ('build', function () {
return gulp
.src ('./sample.js')
.pipe (gulp.dest ('./build'));
});
~~~
復制文件完成了,但是它沒有壓縮這個 JS 文件。要做到這一點,你必須使用一個 Gulp 插件。在這種情況下,你可以使用 gulp-uglify,流行的 UglifyJS 壓縮編繹插件。
~~~
var gulp = require ('gulp');
var uglify = require ('gulp-uglify');
gulp.task ('build', function () {
return gulp
.src ('./sample.js')
.pipe (uglify ())
.pipe (gulp.dest ('./build'));
});
~~~
正如你可能意識到的那樣,流使可以讓您添加更多的插件,而只需要讀取和寫入磁盤一次。你也可以指定緩沖器中內容的大小。需要注意的是,如果你在壓縮之前添加它,那么你得到的大小是 unminified。
~~~
var gulp = require ('gulp');
var uglify = require ('gulp-uglify');
var size = require ('gulp-size');
gulp.task ('build', function () {
return gulp
.src ('./sample.js')
.pipe (uglify ())
.pipe (size ())
.pipe (gulp.dest ('./build'));
});
~~~
為了增強這種組合,滿足添加或刪除管道的需要,讓我們添加最后一個插件。這一次,我會用 gulp-header 在頭文件添加一段版權信息的代碼,如名稱,版本和許可證類型。
~~~
var gulp = require ('gulp');
var uglify = require ('gulp-uglify');
var size = require ('gulp-size');
var header = require ('gulp-header');
var pkg = require ('./package.json');
var info = '// <%= pkg.name %>@v<%= pkg.version %>, <%= pkg.license %>\n';
gulp.task ('build', function () {
return gulp
.src ('./sample.js')
.pipe (uglify ())
.pipe (header (info, { pkg : pkg }))
.pipe (size ())
.pipe (gulp.dest ('./build'));
});
~~~
就像 Grunt 一樣,在 Grulp 中你可以通過傳遞一組任務到 gulp.task 來定義流程。**在這方面,Grunt 和 Grulp 之間的主要區別在于,Grunt 是同步的,而 Grulp 是異步的**。
`gulp.task ('build', ['build-js', 'build-css']);`
在 Gulp,如果你要讓任務同步運行,你必須聲明一個任務。你的任務開始之前執行。
~~~
gulp.task ('build', ['dep'], function () {
// 執行 dep 所依辣的任務
});
~~~
* * * * *
如果你有任何收獲,先看看這段話:
**你使用哪種工具并不重要,只要保證:流程構建(編繹)好用就行了,用起來不要太辛苦。**
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd