JavaScript構建(編繹)系統大比拼:Grunt vs. Gulp vs. NPM

ourjs發表於2014-08-25

  Nicolas Bevacqua進行了一個比較JavaScript構建(編繹)系統的任務。他對三巨頭: Grunt, Gulp and NPM進行了比較,並討論了每種的優缺點。

  By Nicolas Bevacqua

  決定採用何種技術總是很難的。一旦遇到問題,你不想推翻你之前的選擇。但是你必須選一個,然後讓它按照著你的思路做。實施一套構建(編繹)系也是一樣的,你應該把它看作一個非常重要的選擇,讓我們以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所依辣的任務
});

  如果你有任何收穫,先看看這段話。 

你使用哪種工具並不重要,只要保證:流程構建(編繹)好用就行了,用起來不要太辛苦。

  原文地址: http://modernweb.com/2014/08/04/choose-grunt-gulp-npm

相關文章