前端構建工具------Webpack vs Gulp

理想的前端開發流程

在說構建工具之前得先說說咱期望的前端開發流程是怎樣的?

  • 寫業務邏輯代碼(例如 es6,scss,pug 等)
  • 處理成瀏覽器認識的(js,css,html)
  • 瀏覽器自動刷新看到效果

前端開發就是在不斷的 123..123..123.... 循環中進行的,上面的後兩步(也就是 2 和 3)應該是 自動化 的,前端開發者理應只需關注第 1 步——寫業務邏輯代碼。

自動化的事情應該交由構建工具來做,時下流行的前端構建工具有 gulp 和 webpack(有人說 webpack 不算是構建工具,我覺得這沒什麼好爭的。橫看成嶺側成峯,我覺得從當前 webpack 所能做的事情來看,說它是構建工具絲毫不爲過)。本文不會對 gulp 和 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 只是我們解決問題的工具,不要被工具束縛了手腳不能前進。

扯了這麼多,到底誰會被拍死在沙灘上
可以看出來,這兩個工具其實各有優缺,都有用武之地。合理地配合使用,取長補短,才能發揮最大的威力,所以這倆基友並不是互斥的,而是互補的,誰也不會被拍死在沙灘上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章