JavaScript構建(編繹)系統大比拼:Grunt vs. Gulp vs. NPM
Nicolas Bevacqua進行了一個比較JavaScript構建(編繹)系統的任務。他對三巨頭: Grunt, Gulp and NPM進行了比較,並討論了每種的優缺點。
決定採用何種技術總是很難的。一旦遇到問題,你不想推翻你之前的選擇。但是你必須選一個,然後讓它按照著你的思路做。實施一套構建(編繹)系也是一樣的,你應該把它看作一個非常重要的選擇,讓我們以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所依辣的任務 });
如果你有任何收穫,先看看這段話。
你使用哪種工具並不重要,只要保證:流程構建(編繹)好用就行了,用起來不要太辛苦。
相關文章
- JavaScript運算子:== VS. ===JavaScript
- Airflow vs. Luigi vs. Argo vs. MLFlow vs. KubeFlowAIUIGo
- javascript requestAnimationFrame vs. setTimeoutJavaScriptrequestAnimationFrame
- MVC vs. MVP vs. MVVMMVCMVPMVVM
- 後端程式設計師一定要看的語言大比拼:Java vs. Go vs. Rus後端程式設計師JavaGo
- 移動端效能大比拼:CSS Sprites vs. Data URICSS
- 模板 vs. 硬編碼 HTMLHTML
- git revert .vs. git reset .vs. git rebaseGit
- String.format VS. StrSubstitutor VS. NamedParameterJdbcTemplateORMJDBC
- 我為何放棄Gulp與Grunt,轉投npm scripts(上)NPM
- 基於Grunt構建一個JavaScript庫JavaScript
- 搞懂:資料科學vs.機器學習vs.資料分析vs.商業分析資料科學機器學習
- Gulp, 比Grunt更好用的前端構建工具前端
- HTMLCollection vs. NodeListHTML
- PostgreSQL vs. MySQLMySql
- AutoIt vs. QTPQT
- 在.csproj檔案中增加npm, bower, gulp, webpack的構建NPMWeb
- 傳統量化策略 VS. AI量化策略AI
- 【譯】GraphQL vs. RESTREST
- Accoona VS. GoogleGo
- Ansible vs. TerraformORM
- 大資料檔案格式比較:AVRO vs. PARQUET vs. ORC大資料VR
- Java 下的 JSON庫效能比較:JSON.simple vs. GSON vs. Jackson vs. JSONPJavaJSON
- Gulp.js----比Grunt更易用的前端構建工具JS前端
- Epic vs. 蘋果 | App Store是否構成壟斷?蘋果APP
- Git branching: master vs. origin/master vs. remotes/origin/masterGitASTREM
- 柏拉圖洞穴寓言 vs. 表徵假說 vs. 表觀遺傳
- Spring WebClient vs. RestTemplateSpringWebclientREST
- Redis vs. MongoDB比較RedisMongoDB
- 引用計數 vs. GCGC
- HTML 5 Canvas vs. SVGHTMLCanvasSVG
- PostgreSQL vs. Oracle 測試SQLOracle
- WebForms VS. MVC(翻譯)WebORMMVC
- PostgreSQL vs. MS SQL ServerSQLServer
- Angular vs. React - the tie breakerAngularReact
- 使用gulp編寫常用自動化構建任務
- Quarkus vs. SpringBoot - RedditSpring Boot
- Ruby on Rails Mountable vs. Full EngineAI