.net 專案靜態檔案自動壓縮打包

2023-06-15 12:05:59

打包工具選型

在 ASP.NET MVC 時代,我們常使用 BundleCollection 設定需要打包壓縮的 js 和 css 檔案,執行時框架會自動處理打包壓縮過程並將最終結果傳入響應。

ASP.NET Core 開始,不再提供內建的打包壓縮元件,官方推薦 WebOptimizer 替代使用。

上述兩者都是在執行時實時處理,應該至少在系統初始化時會佔用一定的資源。

時間來到 .NET 大一統時代,截止目前(.NET7),我們仍然只能尋求外部元件的幫助。有一個編譯期生成預期檔案的元件 BuildBundlerMinifier,nuget 安裝後進行簡單的 JSON 設定即可使用,但遺憾的是,它不支援壓縮包含 ES6 語法的 js,會報未將物件參照設定到物件的範例錯誤。

於是我們開始考慮獨立於 .NET 平臺的,較為成熟的方案,比如 gruntgulp

  • grunt: 使用 JSON 描述構建步驟
  • gulp:基於流傳遞(管道機制)

這兩者都擁有豐富的外掛,以應對各種需求。下面以更年輕更簡單的 gulp 為例,說明如何使之與 Visual Studio 配合使用。

範例

假設有如下 .NET 專案,其中按約定靜態檔案存放在 wwwroot 目錄下

安裝 gulp

# 全域性安裝 gulp 或 gulp-cli,提供可在命令列執行的 gulp 指令
npm install -g gulp-cli

需要注意的是,我們還需要在待處理的目錄下安裝 gulp,否則後續執行命令時會提示 Local gulp not found. 錯誤。其實這是Gulp故意設計的,原因是為了版本和依賴的控制,也就是當別人 fork 你的程式碼,或者你過段時間拷貝到別的電腦上再 gulp 的時候,能控制該專案中 gulp 的版本和其他外掛的版本始終保持不變。(這裡的 gulp 其實也可看作外掛,只是它是官方提供的。博主不懷疑 gulp 團隊前向相容的能力,推測是考慮第三方外掛前向相容的能力參差不齊的緣故。)

# 進入待處理的目錄,此是 .net 專案下的一個子目錄
cd wwwroot/
# 將此子目錄初始化為 npm 專案
npm init -y
# 安裝 gulp 外掛,提供各種 gulp.xxx()
npm install gulp -D

此時在該目錄下檢視 gulp 版本

gulp --version
# 輸出:
CLI version: 2.3.0  # 全域性 cli 版本
Local version: 4.0.2  # 當前專案下 gulp 外掛版本

安裝需要的第三方外掛

# 以下各外掛功能請查閱相關資料 
npm i -D gulp-cssmin gulp-autoprefixer gulp-uglify
# es6 語法轉 es5,竊以為目前瀏覽器對 es6 的支援程度,大可不必
npm i -D gulp-babel @babel/core @babel/preset-env
# 提供合併若干檔案為單一檔案的功能
npm i -D gulp-concat

編寫 gulpfile.js

在專案根目錄下(wwwroot/)新建 gulpfile.js,編寫任務指令碼

// 載入 專案gulp 依賴包
const gulp = require('gulp');

// 載入 css相關依賴包
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');

// 載入 js相關依賴包
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');

// 載入檔案合併處理包
const concat = require('gulp-concat');

// 打包 css
const cssHandler = function () {
    return gulp.src('./css/main.css')
        .pipe(autoprefixer())
        .pipe(cssmin())
        .pipe(concat('main.min.css'))
        .pipe(gulp.dest('./css'))
}

// 打包 js
const jsHandler = function () {
    return gulp.src(['./js/instruction/*.js', './js/repos/*.js', './js/app/*.js'])
        .pipe(babel({ presets: ['@babel/env'] }))
        .pipe(uglify())
        .pipe(concat('main.min.js'))
        .pipe(gulp.dest('./js'))
}

// 3-3,定義預設執行程式
module.exports.default = gulp.parallel(cssHandler, jsHandler)

注意,gulp.dest() 參數列示的是目標目錄而非目標檔名(輸出檔名預設同原始檔名),如果要自定義輸出檔名可使用 gulp-rename 外掛;或如上使用 gulp-concat,它的目的是合併若干檔案為單一檔案,自然能指定生成的檔名咯。

完成上述步驟後,我們就可以在命令列執行 gulp 檢查輸出結果。

編譯 .net 專案時自動打包

在 .net 專案根目錄下(注意非 wwwroot/)新建一個批次檔 package-statics.bat:

cd wwwroot
gulp

然後編輯專案檔案(.csproj),在 節點下加入如下設定:

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
  <Exec Command="call package-statics.bat" />
</Target>

意思很簡單:在編譯前先執行批次檔,也就是先執行之前我們設定好的 gulp 流程。

如果你的專案採用了 CI/CD 自動構建,可能伺服器上並沒有安裝 gulp,那麼可以在本地先執行打包,將生成後的檔案提交,然後在伺服器上編譯時跳過打包步驟,直接使用提交的檔案。實現這個場景也很簡單,只要在上述 節點內設定執行條件即可:<Target ... Condition="'$(Configuration)'=='DEBUG'">。參看演練:使用 MSBuild

ps:在頁面中,我們希望開發時依然參照的是原檔案,上線後才參照打包後檔案,那麼可以使用 <environment> 標記,通過判斷環境變數達到這一目的:

<environment include="Development">
  <link ... href="~/css/main.css" />
</environment>
<environment include="Production">
  <link ... href="~/css/main.min.css" />
</environment>

參考資料

gulp打包詳解