利用 gulp 壓縮合併 js 檔案

透過 gulp 將 javascript 檔案壓縮合併,減少前端 request 連線數量
本文參考自gulp 给静态资源文件添加 hash(md5)后缀 防止缓存

gulpfile.js 設定檔

參照下面設定範例,任務minifyJs從原始來源讀取後先合併更名,接著做最小化並計算 hash 之後,存檔,並且另外再產生 json 檔案給後續替換 html 引用

任務revHtmlJs則是將指定頁面及 hash json 資料做查找替換,透過調整第三方依賴套件的原始碼,可以做到替換檔名後方 queryString 的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let { src, dest } = require("gulp");
let gulp = require("gulp");
let rename = require("gulp-rename");
let concat = require("gulp-concat");
let uglify = require("gulp-uglify-es").default;
let rev = require("gulp-rev");
let revCollector = require("gulp-rev-collector");

function minifyJs() {
return src("Scripts/Page/Cart/Component/**/*.js")
.pipe(concat("cartComponent.js"))
.pipe(
rename(function(path) {
path.basename += ".min";
path.extname = ".js";
})
)
.pipe(uglify(/* options */))
.pipe(rev())
.pipe(dest("Scripts/Page/Cart"))
.pipe(rev.manifest())
.pipe(dest("Scripts/Page/Cart"));
}

function revHtmlJs() {
return src(["Scripts/Page/Cart/*.json", "Views/OrderHistory/Index.cshtml"])
.pipe(revCollector())
.pipe(dest("Views/OrderHistory"));
}

const def = gulp.series(minifyJs, revHtmlJs);

exports.default = def;

比較重要的就是需要調整相關的依賴套件,實際上調整的部分如下

調整 gulp 第三方依賴套件

gulp-rev

1
2
3
// 將檔案替換為後方加上queryString的形式,而不是直接將檔案名稱改掉
// manifest[originalFile] = revisionedFile;
manifest[originalFile] = originalFile + "?v=" + file.revHash;

rev-path

1
2
3
// 不修改檔案名稱,維持原本的檔名
// return modifyFilename(pth, (filename, ext) => `${filename}-${hash}${ext}`);
return modifyFilename(pth, (filename, ext) => `${filename}${ext}`);

gulp-rev-collector

1
2
3
4
5
6
7
8
// 原本透過 regex 的部分,在 pattern 後方加上 ?v=xxxxxxxxxx 的pattern
let versionPattern = "(\\?v=(\\w){10})?";
let regPatt = prefixDelim + pattern + versionPattern;
changes.push({
regexp: new RegExp(regPatt, "g"),
patternLength: pattern.length,
replacement: "$1" + manifest[key]
});

整合 visual studio build event

於專案右鍵選內容,點選建置事件頁籤,在建置前事件命令列輸入指令

1
npm run build --prefix $(ProjectDir)

透過指令在 vs build 之前先執行 npm run build 的指令,參數 prefix 表示指定執行時的目錄路徑,這邊用 vs 的預設專案目錄變數代入
而在 package.json 檔案之中,直接宣告 build 指令即可

1
2
3
4
5
// package.json
...
"scripts": {
"build": "gulp"
}

其他做法

上述是透過前端的方式來處理,但若後端採用 scriptBundle 也可以做到相同的事情,唯獨在 ES6 語法的部分可能會因為轉譯錯誤而造成網站 cshtml 無法瀏覽,只需要透過 babelBundle 取代掉 jsBundle 即可

先透過 nuget 安裝套件,相關的依賴套件會一併安裝

1
Install-Package React.Web.Mvc4 -Version 4.1.1

在使用上與 ScriptBundle 相同,因此可以很好的與專案整合在一起
若要直接在開發時

在 bundle 的部分原則上還是建議利用群組的方式來管理相關的 js 檔案,例如第三方套件一組;自己寫的 js 一組,然後在頁面上就是引用兩組

範例就像下面這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void RegisterBundles(BundleCollection bundles)
{
// 壓縮合併
BundleTable.EnableOptimizations = true;

bundles.Add(new ScriptBundle("~/bundles/jquery")
.Include(JsBundleCollection.jqery)
.Include(JsBundleCollection.jqueryValidate)
);
...略
}

internal class JsBundleCollection
{
internal static string[] jqery =
{
"~/Scripts/Plugins/Jquery/jquery-{version}.js"
};

internal static string[] jqueryValidate =
{
"~/Scripts/Plugins/JqueryValidate/jquery.validate.js",
"~/Scripts/Plugins/JqueryValidate/jquery.validate.unobtrusive.js"
};
}

這樣做的好處是使用者的瀏覽器如果已經有讀取過,那麼就不需要再重新下載,對於使用者體驗是有幫助的;而較常調整的檔案,修改後會有不同的 hash 值,瀏覽器會重新取得,對於 client 端來說,也可以確保每次讀取的都是最新的資料

1
2
3
4
5
// page.cshtml
@section scripts{
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/page")
}

總結

如果專案是 csharp mvc,直接 nuget 安裝套件較為方便;逼不得已的情況下再考慮用 gulp 硬幹,畢竟是要改第三方套件,不是很好