Gulp:Web 開發人員最大化網站速度的秘密武器
已發表: 2022-03-11我們中的許多人必須處理用於生產的基於 Web 的項目,這些項目向公眾提供各種服務。 在處理此類項目時,能夠快速構建和部署我們的代碼非常重要。 快速做某事通常會導致錯誤,特別是如果一個過程是重複的,因此盡可能自動化這樣一個過程是一個好習慣。
在這篇文章中,我們將著眼於一個工具,它可以成為我們實現這種自動化的一部分。 這個工具是一個名為 Gulp.js 的 npm 包。 為了熟悉本文中使用的基本 Gulp.js 術語,請參閱我們的 Toptal 開發人員之一 Antonios Minas 之前在博客上發布的“使用 Gulp 進行 JavaScript 自動化簡介”。 我們將假設您對 npm 環境有基本的了解,因為它在本文中廣泛用於安裝包。
服務前端資產
在繼續之前,讓我們退後幾步,大致了解一下 Gulp.js 可以為我們解決的問題。 許多基於 Web 的項目具有前端 JavaScript 文件,這些文件提供給客戶端,以便為網頁提供各種功能。 通常還有一組 CSS 樣式表也提供給客戶端。 有時在查看網站或 Web 應用程序的源代碼時,我們可以看到如下代碼:
<link href="css/main.css" rel="stylesheet"> <link href="css/custom.css" rel="stylesheet"> <script src="js/jquery.min.js"></script> <script src="js/site.js"></script> <script src="js/module1.js"></script> <script src="js/module2.js"></script>
這段代碼有一些問題。 它引用了兩個獨立的 CSS 樣式表和四個獨立的 JavaScript 文件。 這意味著服務器總共要向服務器發出六個請求,並且每個請求都必須在頁面準備好之前單獨加載一個資源。 這對於 HTTP/2 來說不是什麼問題,因為 HTTP/2 引入了並行性和標頭壓縮,但這仍然是一個問題。 它增加了加載此頁面所需的總流量並降低了用戶體驗的質量,因為加載文件需要更長的時間。 在 HTTP 1.1 的情況下,它還會佔用網絡並減少可用請求通道的數量。 將 CSS 和 JavaScript 文件合併到一個單獨的包中會更好。 這樣一來,總共只有兩個請求。 提供這些文件的縮小版本也很好,這些文件通常比原始文件小得多。 如果任何資產被緩存,我們的 Web 應用程序也可能會中斷,並且客戶端會收到過時的版本。
解決其中一些問題的一種原始方法是使用文本編輯器手動將每種類型的資產組合成一個包,然後通過壓縮器服務運行結果,例如 http://jscompress.com/。 事實證明,在開發過程中不斷地這樣做是非常乏味的。 一個輕微但值得懷疑的改進是使用 GitHub 上可用的軟件包之一託管我們自己的 minifier 服務器。 然後我們可以做一些看起來有點類似於以下的事情:
<script src="min/f=js/site.js,js/module1.js"></script>
這將向我們的客戶端提供縮小文件,但它不會解決緩存問題。 它還會導致服務器上的額外負載,因為我們的服務器本質上必須在每次請求時重複地連接和縮小所有源文件。
使用 Gulp.js 實現自動化
當然,我們可以比這兩種方法中的任何一種做得更好。 我們真正想要的是自動化捆綁並將其包含在我們項目的構建階段。 我們希望最終得到已經縮小並準備好提供服務的預構建資產包。 我們還希望強制客戶端在每次請求時接收我們捆綁資產的最新版本,但我們仍然希望盡可能利用緩存。 對我們來說幸運的是,Gulp.js 可以處理這個問題。 在本文的其餘部分,我們將構建一個利用 Gulp.js 的強大功能來連接和縮小文件的解決方案。 當有更新時,我們還將使用插件來破壞緩存。
我們將在示例中創建以下目錄和文件結構:
public/ |- build/ |- js/ |- bundle-{hash}.js |- css/ |- stylesheet-{hash}.css assets/ |- js/ |- vendor/ |- jquery.js |- site.js |- module1.js |- module2.js |- css/ |- main.css |- custom.css gulpfile.js package.json
gulpfile.js 文件是我們定義 Gulp 將為我們執行的任務的地方。 npm 使用package.json
來定義我們應用程序的包並跟踪我們將安裝的依賴項。 公共目錄應該配置為面向網絡。 assets 目錄是我們將存儲源文件的地方。 要在項目中使用 Gulp,我們需要通過 npm 安裝它並將其保存為項目的開發人員依賴項。 我們還想從 Gulp 的concat
插件開始,它允許我們將多個文件連接成一個。
要安裝這兩個項目,我們將運行以下命令:
npm install --save-dev gulp gulp-concat
接下來,我們將要開始編寫 gulpfile.js 的內容。
var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);
在這裡,我們正在加載 gulp 庫及其 concat 插件。 然後我們定義三個任務。
第一個任務 ( pack-js
) 定義了將多個 JavaScript 源文件壓縮到一個包中的過程。 我們列出了源文件,它們將按照指定的順序進行全局、讀取和連接。 我們通過管道將其輸入到 concat 插件中,以獲得一個名為bundle.js
的最終文件。 最後,我們告訴 gulp 將文件寫入public/build/js
。
第二個任務 ( pack-css
) 與上面做同樣的事情,但針對 CSS 樣式表。 它告訴 Gulp 將連接的輸出作為stylesheet.css
存儲在public/build/css
中。
第三個任務( default
)是 Gulp 在我們不帶參數調用它時運行的任務。 在第二個參數中,我們傳遞了默認任務運行時要執行的其他任務的列表。
讓我們使用我們通常使用的任何源代碼編輯器將此代碼粘貼到 gulpfile.js 中,然後將文件保存到應用程序根目錄。
接下來,我們將打開命令行並運行:
gulp
如果我們在運行這個命令後查看我們的文件,我們會發現兩個新文件: public/build/js/bundle.js
和public/build/css/stylesheet.css
。 它們是我們源文件的串聯,解決了部分原始問題。 但是,它們沒有被縮小,並且還沒有緩存破壞。 讓我們添加自動縮小。
優化內置資產
我們將需要兩個新插件。 要添加它們,我們將運行以下命令:
npm install --save-dev gulp-clean-css gulp-minify
第一個插件用於縮小 CSS,第二個插件用於縮小 JavaScript。 第一個使用 clean-css 包,第二個使用 UglifyJS2 包。 我們將首先在 gulpfile.js 中加載這兩個包:
var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');
然後,在將輸出寫入磁盤之前,我們需要在任務中使用它們:
.pipe(minify()) .pipe(cleanCss())
gulpfile.js 現在應該如下所示:
var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify()) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);
讓我們再次運行 gulp。 我們將看到文件stylesheet.css
以縮小格式保存,而文件bundle.js
保存。 我們會注意到我們現在還有 bundle-min.js,它被縮小了。 我們只需要縮小文件,並且希望將其保存為bundle.js
,因此我們將使用附加參數修改我們的代碼:

.pipe(minify({ ext:{ min:'.js' }, noSource: true }))
根據 gulp-minify 插件文檔(https://www.npmjs.com/package/gulp-minify),這將為縮小版本設置所需的名稱,並告訴插件不要創建包含原始源的版本。 如果我們刪除構建目錄的內容並再次從命令行運行 gulp,我們最終會得到兩個縮小的文件。 我們剛剛完成了構建過程的縮小階段。
緩存破壞
接下來,我們將要添加緩存破壞,我們需要為此安裝一個插件:
npm install --save-dev gulp-rev
並在我們的 gulp 文件中要求它:
var rev = require('gulp-rev');
使用插件有點棘手。 我們必須首先通過插件管道縮小輸出。 然後,我們必須在將結果寫入磁盤後再次調用插件。 該插件重命名文件,以便用唯一的哈希標記它們,並且它還創建一個清單文件。 清單文件是一個映射,我們的應用程序可以使用它來確定我們應該在 HTML 代碼中引用的最新文件名。 在我們修改 gulp 文件後,它最終應該是這樣的:
var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('default', ['pack-js', 'pack-css']);
讓我們刪除構建目錄的內容並再次運行 gulp。 我們會發現我們現在有兩個文件,每個文件名都附加了標籤,還有一個 manifest.json 保存到public/build
。 如果我們打開清單文件,我們會看到它只引用了我們的縮小和標記文件之一。 發生的情況是每個任務都編寫一個單獨的清單文件,其中一個最終覆蓋另一個。 我們將需要使用其他參數修改任務,這些參數將告訴他們查找現有清單文件並將新數據合併到其中(如果存在)。 它的語法有點複雜,所以讓我們看看代碼應該是什麼樣子,然後再過一遍:
var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('default', ['pack-js', 'pack-css']);
我們首先將輸出傳遞給rev.manifest()
。 這將創建標記文件,而不是我們之前擁有的文件。 我們提供rev-manifest.json
的所需路徑,並告訴rev.manifest()
合併到現有文件(如果存在)。 然後我們告訴 gulp 將清單寫入當前目錄,此時該目錄將是 public/build。 路徑問題是由 GitHub 上更詳細討論的錯誤引起的。
我們現在有自動縮小、標記文件和清單文件。 所有這些將使我們能夠更快地將文件交付給用戶,並在我們進行修改時破壞他們的緩存。 不過只剩下兩個問題了。
第一個問題是,如果我們對源文件進行任何修改,我們將獲得新標記的文件,但舊的文件也會保留在那裡。 我們需要一些方法來自動刪除舊的縮小文件。 讓我們使用一個允許我們刪除文件的插件來解決這個問題:
npm install --save-dev del
我們將在我們的代碼中需要它並定義兩個新任務,一個用於每種類型的源文件:
var del = require('del'); gulp.task('clean-js', function () { return del([ 'public/build/js/*.js' ]); }); gulp.task('clean-css', function () { return del([ 'public/build/css/*.css' ]); });
然後,我們將確保新任務在我們的兩個主要任務之前完成運行:
gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {
如果我們在修改後再次運行gulp
,我們將只有最新的縮小文件。
第二個問題是我們不想在每次進行更改時都繼續運行 gulp。 為了解決這個問題,我們需要定義一個觀察者任務:
gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });
我們還將更改默認任務的定義:
gulp.task('default', ['watch']);
如果我們現在從命令行運行 gulp,我們會發現它在調用時不再構建任何東西。 這是因為它現在調用 watcher 任務,該任務將監視我們的源文件是否有任何更改,並且僅在它檢測到更改時才構建。 如果我們嘗試更改任何源文件,然後再次查看控制台,我們將看到pack-js
和pack-css
任務及其依賴項自動運行。
現在,我們所要做的就是在我們的應用程序中加載 manifest.json 文件並從中獲取標記的文件名。 我們如何做到這一點取決於我們特定的後端語言和技術堆棧,並且實現起來非常簡單,所以我們不會詳細討論它。 但是,一般的想法是我們可以將清單加載到數組或對像中,然後定義一個幫助函數,允許我們以類似於以下方式從模板調用版本化資產:
gulp('bundle.js')
一旦我們這樣做了,我們就不必再擔心文件名中的標籤更改了,我們將能夠專注於編寫高質量的代碼。
本文的最終源代碼以及一些示例資產可以在這個 GitHub 存儲庫中找到。
結論
在本文中,我們討論瞭如何為我們的構建過程實現基於 Gulp 的自動化。 我希望這對您有所幫助,並允許您在自己的應用程序中開發更複雜的構建過程。
請記住,Gulp 只是可用於此目的的工具之一,還有許多其他工具,例如 Grunt、Browserify 和 Webpack。 它們的目的和可以解決的問題範圍各不相同。 有些可以解決 Gulp 無法解決的問題,例如將 JavaScript 模塊與可以按需加載的依賴項捆綁在一起。 這被稱為“代碼拆分”,它是對在每個頁面上提供一個大文件的想法的改進。 這些工具非常複雜,但將來可能會被覆蓋。 在接下來的文章中,我們將討論如何自動部署我們的應用程序。