前端構建工具------Webpack vs Gulp
理想的前端開發流程
在說構建工具之前得先說說咱期望的前端開發流程是怎樣的?
- 寫業務邏輯程式碼(例如 es6,scss,pug 等)
- 處理成瀏覽器認識的(js,css,html)
- 瀏覽器自動重新整理看到效果
前端開發就是在不斷的 123..123..123.... 迴圈中進行的,上面的後兩步(也就是 2 和 3)應該是 自動化 的,前端開發者理應只需關注第 1 步——寫業務邏輯程式碼。
自動化的事情應該交由構建工具來做,時下流行的前端構建工具有 gulp 和 webpack(有人說 webpack 不算是構建工具,我覺得這沒什麼好爭的。橫看成嶺側成峰,我覺得從當前 webpack 所能做的事情來看,說它是構建工具絲毫不為過)。本文不會對
什麼是構建工具
構建工具是一段自動根據原始碼生成可使用檔案的程式,構建過程包括打包、編譯、壓縮、測試等一切需要對原始碼進行的相關處理。構建工具的目的是實現構建過程的自動化,使用它可以讓咱們避免機械重複的勞動(這怕是程式設計師最不能忍受的了),從而解放我們的雙手。
解放了雙手幹什麼
哇槽,愛幹什麼幹什麼。
Gulp 為何物
先來聽聽 Ta 的官網是怎麼說:
Gulp 致力於 自動化和優化 你的工作流,它是一個自動化你開發工作中 痛苦又耗時任務
想一想咱們日常的開發工作中痛苦又耗時任務有哪些呢?
- 用 es6,typescript 編寫的指令碼檔案需要編譯成瀏覽器認識的 javascript
- 用 scss,less 編寫的樣式檔案需要編譯成瀏覽器認識的 css
- 檢查程式碼是否符合書寫規範,跑單元測試和整合測試
- 開發環境如果有 sourcemaps 的話除錯起來就方便多了,修改完程式碼瀏覽器能自動重新整理立即看到效果就更好了
- 生產環境部署程式碼需要壓縮合並靜態檔案,新增檔案指紋控制快取
- blabla...更多的你自己想吧
Gulp 聲稱要幫咱們實現 自動化,那他是怎樣幫助咱們實現自動化的呢?這就不得不先提一嘴牛逼哄哄的 NodeJS
Node 背景小知識
Node 使前端 Jser 有了脫離瀏覽器工作的能力,要擱以前的話咱們寫的 js 要麼嵌到 html 頁面裡,然後用瀏覽器開啟 html 頁面才能執行js,要麼就是在瀏覽器開發者工具的 Console 面板裡編寫執行程式碼片段。總之沒了瀏覽器這個宿主,咱們的 js 就 run 不起來。Node 這貨突發奇想,把開發者工具的 Console 給摳下來了,從此 js 可以脫離瀏覽器直接在 node 裡執行。相當於 js 現在有了兩個宿主環境,一個是瀏覽器,一個是 node。當然了,Node 可不是開發者工具裡的 Console,那只是打個比方。它是基於Chrome V8 引擎實現的一個 JavaScript 執行環境,功能其實類似 Console 面板,但提供了大量實用的 API,感興趣的同學可前往 Node官網 詳細瞭解,英文吃力的騷年 戳這裡。Node 可以算是前端革命式的創新,隨 node 一起釋出的 node 包管理器 npm(node package manager) 也已經是全球最大的開源庫生態系統。node/npm 這對組合一出,前端生態迎來了大爆發,一時間為解決各種問題的 node 包層出不窮,遍地開花。gulp 就是披荊斬棘,一路過五關斬六將闖出來的一個小 node 包。
扯談完畢,接下來就來看看 gulp 是不是在裝逼,他到底能不能幫我們實現自動化。
作為一個 node 包,標準開啟方式當然是:
npm i -g gulp
然後呢,這裡以編譯 less 為例,首先安裝編譯 less 需要用到的 node 包:
npm i --save-dev gulp gulp-less
前面已經全域性安裝過 gulp 了,怎麼又本地安裝了一遍
前面的 -g
是全域性安裝,是為了執行你所編寫的 gulp 任務,即 gulp yourTask。而後面的 --save-dev 是本地安裝,是為了咱們編寫任務時使用 gulp 提供的 api,例如 gulp.src()
、gulp.task()
、gulp.dest()
等等。當然也是可以直接使用全域性安裝的 gulp 的 api 的,但是強烈不推薦,因為這樣涉及到 gulp 版本控制的問題,而且使用全域性 gulp 的 api 的話就會產生環境依賴(你假設環境已經全域性安裝了gulp,萬一沒裝呢,程式不就出錯了)。
接著在專案的根目錄下新建一個 gulpfile.js 檔案,這是 gulp 的預設配置檔案。
gulpfile.js 必須放在專案根目錄?
當然也可放在其他目錄,但這樣的話就得在啟動 gulp 任務時手動指定 gulp 配置檔案 gulp yourTask --gulpfile yourGulpfilePath,可能還需要全域性安裝 gulp-cli,所以除非有特殊需要,否則就放在專案根目錄就行了,這樣最簡單。
配置檔案的名字必須是 gulpfile.js 嗎?
不區分大小寫,取成 gULPFile.js 的話 gulp 也能認識,只要 toLowerCase 之後是 gulpfile 就行了,如果取其它名字那你就又得使用 --gulpfile 選項去指定了。
現在工程目錄結構已經成了下面的樣子:
構建前 gulp 工程目錄結構
接下來就是在 gulpfile.js 裡編寫 gulp task(gulp 把為每個痛苦又耗時任務編寫的處理方法稱為一個 task):
const gulp = require('gulp');
const less = require('gulp-less');
gulp.task('build:less', function(){
return gulp.src('./src/*.less')
.pipe(less())
.pipe(gulp.dest('./dist'));
});
最後就是開啟一個終端,在終端裡執行 gulp build:less。好了,編譯後的檔案已經被輸出到了 dist 目錄:
構建後 gulp 工程目錄結構
至此你已經算是一個 gulp 磚家了,這基本上就是 gulp 的全部內容。怎麼樣,是不是夠簡單,夠絲滑。這也是 gulp 的突出特點——易於學習,易於使用,五分鐘成磚家。如果想要執行解決其他痛苦又耗時的任務,只需下載安裝對應的 gulp 外掛包,然後依次類推寫一個 gulp.task
出來就行了。
這些原始碼具體是怎樣被處理的
這通常不需要關心,因為 gulp 外掛包已為你做好了,並且封裝的非常漂亮,你只需要告訴 gulp 你要什麼,gulp 及其外掛會幫你打點好一切。這就好比你把一份電子文件傳進印表機,告訴它我要一份 A4 紙列印,呲呲呲~,印表機就吐出來一張 A4 紙,上面是你的文件內容。原始碼就是你的電子文件,gulp 外掛就是印表機,生成的可用檔案就是你手裡的那張 A4 紙,你不用關心印表機內部是怎樣工作的,因為它封裝的很好,或者你可以把印表機拆了一探究竟也行。
Gulp 是基於流的?
流(Stream)不是 gulp 創造的概念,而是從 unix 時代就開始使用的 I/O 機制,一直到現在仍在廣泛使用。Node 封裝了一個 stream 模組專門用來對流進行操作。gulp 所基於的流即是 Node 封裝起來的 stream。上面 gulp.task()
程式碼裡面的 pipe 方法並不是 gulp 提供的 api,而是 node 的 api,準確的說應該是 node 的 stream 模組提供的 api。具體是怎麼實現的呢:gulp.src()
的返回值是 node Stream 的一個例項,之後的 pipe
呼叫的其實是這個例項的 pipe
方法,而 pipe
方法的返回值依然是 node Stream 例項,以此實現前面的 .pipe().pipe().pipe()
這種串聯寫法。熟悉 jQuery 的同學應該很清楚這種技巧。
webpack 又是從哪冒出來的
gulp 似乎是完美的,對前端開發工作中每一項痛苦又耗時任務都能見招拆招,各個擊破。然而前端發展速度之快超乎想象,對頁面效能和使用者體驗更是追求極致,以至於 gulp 某些領域尤其大型 SPA(單頁應用)顯得有些不夠用了:
- 單頁應用的核心是模組化,ES6 之前 JavaScript 語言本身一直是沒有模組系統的,導致 AMD,CMD,UMD 各種輪子模組化方案都蹦出來。對這種模組化亂象,gulp 顯得無能為力,gulp 外掛對這一塊也沒有什麼想法。不過也可以理解,模組化解決方案可不是誰都能 hold 住的,需要考慮的問題太多了;
- 對前沿的 SPA 技術 gulp 處理起來顯得有些力不從心,例如 Vue 的單檔案元件,gulp 配合一些外掛可以勉強處理,但是很蹩腳。其實歸根結底,還是模組化處理方面的不足;
- 優化頁面載入速度的一條重要法則就是減少 http 請求。gulp 只是對靜態資源做流式處理,處理之後並未做有效的優化整合,也就是說 gulp 忽略了系統層面的處理,這一塊還有很大的優化空間,尤其是移動端,那才真的是一寸光陰一寸金啊,哪怕是幾百毫秒的優化所帶來的收益(使用者?流量?付費?)絕對超乎你的想象。別跟我說 gulp-concat,CSS Sprites,這倆玩意兒小打小鬧還行,遇上大型應用根本拿不上臺面。現在的頁面動輒上百個零碎資源(圖片,樣式表,指令碼),也就是上百個 http 請求,因此這個優化需求還是相當迫切的。關於為何減少 http 請求可以有效降低頁面載入時間戳這裡。
- blabla... 你自己想吧,主要就是大型單頁應用方面有短板;
時勢造英雄。webpack 一聲吼,大張旗鼓地挖起了gulp 的牆角。
老規矩,先看看webpack官網怎麼吹牛逼介紹自己的:
Webpack 是當下最熱門的前端資源模組化 管理和打包 工具。它可以將許多鬆散的模組按照依賴和規則打包成符合生產環境部署的前端資源。還可以將按需載入的模組進行程式碼分割,等到實際需要的時候再非同步載入。
是不是看完一臉懵逼,不明覺厲。其實翻譯過來就是 “在我眼裡,什麼都是模組”。webpack “萬物皆模組” 的理念和 SPA 配合起來簡直是金童玉女,天作之合。這也是 webpack 短時間內名聲大噪,直接撼動 gulp 地位的主要原因。
webpack 的理念比較前衛,它本身也帶來了很多新的概念和內容,諸如載入器(loader)、依賴圖(Dependency Graph)等等。和 gulp 兩小時成磚家的學習難度相比,webpack 或許你研究兩天仍然會暈頭轉向。
接下來簡單看一下 webpack 的主要工作方式。
webpack 和 gulp 一樣也是一個小 node 包,開啟方式自然是:
npm i -g webpack
npm i --save-dev webpack
和 gulp 一樣,全域性安裝是為了執行 webpack 任務,本地安裝是為了使用 webpack 提供的 api。
安裝完 webpack 之後在專案根目錄下新建一個 webpack.config.js,這是 webpack 的預設配置檔案,同 gulp 的 gulpfile.js 的功能類似。webpack.config.js 同樣是不區分大小寫的,取成 webPACk.CONfig.js 的話 webpack 也能認識,但是取成其他名字或放在別的目錄就需要使用 --config 選項去指定配置檔案了。
現在工程目錄結構如下:
構建前webpack工程目錄結構
接下來就是在 webpack.config.js 裡配置需要的選項,注意了,webpack 與 gulp 的重要不同就是使用方式 由程式設計式變成了配置式:
const path = require('path');
module.exports = {
entry: './src/index.js', // 告訴 webpack 你要編譯哪個檔案
output: { // 告訴 webpack 你要把編譯後生成的檔案放在哪
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
}
};
最後仍然和 gulp 類似,就是在終端裡執行 webpack(終端裡一般會出現一大坨編譯資訊)。好了,現在 webpack 已經把編譯好的檔案輸出到了 dist 目錄:
構建後webpack工程目錄結構
看到這是不是已經一頭霧水了,在你還沒明白髮生了什麼的時候 webpack 已經把事情幹完了。這也是 webpack 和 gulp 作業方式的重要不同:Gulp 是搭了個臺子,讓 gulp 外掛在上面唱戲,盡情表演,所有構建相關的具體事情都交由 gulp 外掛去做。而 Webpack 就牛逼了,webpack 先搭了個臺子,然後自己在上面唱嗨了,仔細一聽,他在上面唱的是《我們不一樣》,當然了他也是讓 webpack 外掛在上面唱戲的。
也就是說 webpack 把很多功能都封裝進了自己身體裡面,使得自己強大同時臃腫。現在你可以在 ./src/index.js
檔案裡直接寫 ES6 程式碼,因為 webpack 把編譯 ES6 的工作已經封裝到自己的實現裡了,使得 webpack 看起來原生支援 ES6 而不需要藉助第三方外掛,其實他內部也是用了第三方外掛的,所以你不用再專門去下一個 babel 之類的外掛去轉譯 ES6。這樣封裝的好處是使用起來很方便,不好的地方就是使用者完全不知道發生了什麼,構建完了還一臉懵逼。
上面僅是 webpack 使用的最最最簡單示例,簡直連 “hello world” 都算不上。具體怎樣打包各種資源(typescript,樣式表,圖片,字型等等)可前往 webpack官網 深入學習,想看中文的同學使勁 戳這裡。
webpack “一切皆模組” 的特點完美解決了上面 gulp 暴露的幾個短板,連 webpack 官網也說自己是因為看到現存的模組打包器都不太適合大型 SPA 應用,於是決定打造一個適合大型 SPA 應用的模組打包器,也就是說 webpack 其實就是為大型 SPA 而生的。
webpack 怎麼實現像 gulp 一樣對大量原始檔進行流式處理
人家 webpack 本來就沒打算做這事。webpack 不是以取代 gulp 為目的的,而是為了給大型 SPA 提供更好的構建方案。對大量原始檔進行流式處理是 gulp 擅長的事,webpack 不想搶,也沒必要搶。即使搶,也無非是再造一個蹩腳的 gulp 出來而已。
既然 webpack 模組化這麼強,那以後模組化就全用 webpack 好了
webpack 模組化是強,但是他胖啊,不是所有人都抱得動,主要是他為了提供更多的功能封裝進了太多東西,所以選擇上還是需要因地制宜。如果單純只是打包 js(多頁應用往往是這種需求),完全可以使用 rollup,browserify 這種小而美的實現,因為他們只做一件事——打包js。而如果需要將圖片,樣式,字型等所有靜態資源全部打包,webpack 毫無疑問是首選。這也是為什麼越來越多的流行庫和框架開始從 webpack 轉向使用 rollup 進行打包,因為他們只需要打包 js,webpack 好多強大功能根本用不到。連 rollup 官網也坦言如果你在構建一個庫,rollup 絕對是首選,但如果是構建一個應用,那麼請選 webpack。
結論
我看好多人說 gulp 和 webpack 不是一類東西,我不這麼覺得,雖然說兩者的出發點確實是不一樣的,gulp 走的是流式處理路線,webpack 走的是模組處理路線,但是兩者所要達成的目標卻是一樣的,那就是促進前端領域的自動化和工程化管理。webpack 發展到現在,已經非常強大了,強大到在構建方面 gulp 能做的事 webpack 基本上都可以勝任,gulp 做不了的 webpack 也能搞。同樣的那些開發工作中痛苦又耗時的任務,gulp 和 webpack 都能解決,只是解決思路有天壤之別。
下表是從各個角度對 gulp 和 webpack 做的對比:
Gulp | Webpack | |
定位 | 基於流的自動化構建工具 | 一個萬能模組打包器 |
目標 | 自動化和優化開發工作流,為通用 website 開發而生 | 通用模組打包載入器,為移動端大型 SPA 應用而生 |
學習難度 | 易於學習,易於使用,api總共只有5個方法 | 有大量新的概念和api,不過好在有詳盡的官方文件 |
適用場景 | 基於流的作業方式適合多頁面應用開發 | 一切皆模組的特點適合單頁面應用開發 |
作業方式 | 對輸入(gulp.src)的 js,ts,scss,less 等原始檔依次執行打包(bundle)、編譯(compile)、壓縮、重新命名等處理後輸出(gulp.dest)到指定目錄中去,為了構建而打包 | 對入口檔案(entry)遞迴解析生成依賴關係圖,然後將所有依賴打包在一起,在打包之前會將所有依賴轉譯成可打包的 js 模組,為了打包而構建 |
使用方式 | 常規 js 開發,編寫一系列構建任務(task)。 | 編輯各種 JSON 配置項 |
優點 | 適合多頁面開發,易於學習,易於使用,介面優雅。 | 可以打包一切資源,適配各種模組系統 |
缺點 | 在單頁面應用方面輸出乏力,而且對流行的單頁技術有些難以處理(比如 Vue 單檔案元件,使用 gulp 處理就會很困難,而 webpack 一個 loader 就能輕鬆搞定) | 不適合多頁應用開發,靈活度高但同時配置很繁瑣複雜。“打包一切” 這個優點對於 HTTP/1.1 尤其重要,因為所有資源打包在一起能明顯減少瀏覽器訪問頁面時的資源請求數量,從而減少應用程式必須等待的時間。但這個優點可能會隨著 HTTP/2 的流行而變得不那麼突出,因為 HTTP/2 的多路複用可以有效解決客戶端並行請求時的瓶頸問題。 |
結論 | 瀏覽器多頁應用(MPA)首選方案 | 瀏覽器單頁應用(SPA)首選方案 |
gulp 為何不吸取百家之長,把 webpack 的東西整合進來,反正都是開源的
騰訊那麼牛逼,你說他怎麼不把阿里巴巴整合進來。整合應該是沒可能,因為 gulp 和 webpack 的定位不一樣。所以,沒有放之天下而皆準的解決方案,只有具體問題具體分析選擇適合的解決方案才能正確地解決問題。gulp 和 webpack 只是我們解決問題的工具,不要被工具束縛了手腳不能前進。
扯了這麼多,到底誰會被拍死在沙灘上
可以看出來,這兩個工具其實各有優缺,都有用武之地。合理地配合使用,取長補短,才能發揮最大的威力,所以這倆基友並不是互斥的,而是互補的,誰也不會被拍死在沙灘上。