將 Blazor WebAssembly 整合進 Tauri 應用程式

Tauri 內建了很多前端的專案範本,但可惜的是沒有 Blazor WebAssembly 的範本,但 Tauri 架構本身已經考量得很完善了,只要最終產出是純前端的網站,任何應用程式都可以包裝到 Tauri 內,今天這篇文章就來說明一下如何將 Blazor WebAssembly 加到 Tauri 應用程式內。

基本原理說明

Tauri 本身就是一個桌面應用程式包裝一個網站在裡面的架構而已,至於要包裝什麼網站?如何包裝?其實 Tauri 的設定都已經很明確了!

當我們用 Tauri 建立一個新的應用程式時,會看到 Tauri 會建立一個 src-tauri 的資料夾,裡面會有一個 tauri.conf.json 的設定檔,這個設定檔就是告訴 Tauri 要包裝哪個網站,以及要如何包裝,內容範例如下。

{
  "build": {
    "beforeDevCommand": "npm run serve",
    "beforeBuildCommand": "npm run build",
    "devPath": "http://localhost:8080",
    "distDir": "../dist"
  }
}

從設定可以很明顯看到,在開始開發前,會執行 beforeDevCommand 指定的指令;開發時期,會使用 devPath 的位址塞到瀏覽器內。

因此在開發階段,只要確定 beforeDevCommand 指定的指令可以正常執行,並且可以在 devPath 的位址看到網站,就可以開始開發了。

開發完後,要打包成桌面應用程式時,則是在打包前會執行 beforeBuildCommand 指定的指令,並且將 distDir 的資料夾內容打包成桌面應用程式。

只要能掌握這個規則,就可以將任何網站整合進 Tauri 應用程式內。

有了這樣的概念後,我們就可以開始將 Blazor WebAssembly 加入 Tauri 了。

將 Blazor WebAssembly 加入 Tauri

建立 Blazor WebAssembly 專案

首先,我們建立 Tauri 專案時,可以選擇 vanilla 的範本,這是最基本的範本,不會套用任何的前端框架,就是單純的 HTML/JavaScript/CSS,也因此幾乎不會產生出什麼額外的前端相關檔案,所有前端程式碼都會在 src 裡面,當然這裡面的內容不是我們要的,只要把它移除就好。

之後我們再重新將 Blazor WebAssembly 專案建立在 src 目錄內,可以執行以下指令

dotnet new blazorwasm --name TauriWebDemo --output src

這樣就會在 src 目錄內建立一個 Blazor WebAssembly 專案了。

修改 Tauri 設定檔

接著我們要修改 tauri.conf.json 設定檔,讓 Tauri 可以正確的執行 Blazor WebAssembly 專案。

beforeDevCommand

首先是 beforeDevCommand,這裡可以直接換成以下指令

dotnet watch run --project src/TauriWebDemo.csproj --non-interactive

使用 dotnet watch run,可以做到變更程式時即時重新編譯,節省手動的時間,而 --non-interactive 是 .NET 7 的新參數,在 hot reload 需要重新 build 時就不需要手動回覆了,會直接重新 build。

devPath

devPath 是開發階段時要包裝到 Tauri 提供的是桌面應用程式的網站網址,因此我們要將 Blazor WebAssembly 的網站網址填入這裡。

dotnet new 時會自動建立一個 launchSettings.json 檔案,裡面會有 Blazor WebAssembly 的網站啟動時的位置,直接將裡面的內容填到 devPath 設定即可。

也可以依照團隊習慣,修改 launchSettings.json,只要能確定最終位置都是一致的就好!

這兩個步驟完成後,只要執行 npm run tauri dev 指令,就會自動啟動 Blazor WebAssembly 的網站,並可以在 Tauri 應用程式看到結果了。

beforeBuildCommand

接著是最終建置成桌面應用程式的設定,在 beforeBuildCommand 這裡,我們可以設定如何將 Blazor WebAssembly 專案輸出成一個純前端的應用程式,例如:

dotnet publish src/TauriWebDemo.csproj -c release -o dist

這樣就會將 Blazor WebAssembly 專案輸出到 dist 目錄,裡面就是純前端的應用程式了。

distDir

最後就是 distDir 設定了,在 Tauri 最終要建立成桌面應用程式時,會將 distDir 設定的目錄內容複製到 Tauri 應用程式內。

在 Blazor WebAssembly 專案中使用 dotnet publish 後,完整的純前端應用程式會再指定目錄的 wwwroot 目錄下,例如發佈到 dist 目錄,則實際上的網站內容會在 dist/wwwroot 目錄下,因此我們要將 distDir 設定為 dist/wwwroot

最後我們就可以執行 npm run tauri build 指令,將整個 Blazor WebAssembly 應用程式與 Tauri 整合在一起啦!!

完整的設定物件看起來如下:

{
  "build": {
    "beforeDevCommand": "dotnet watch run --project src/TauriWebDemo.csproj --non-interactive",
    "beforeBuildCommand": "dotnet publish src/TauriWebDemo.csproj -c release -o dist",
    "devPath": "https://localhost:5001",
    "distDir": "../dist/wwwroot",
  }
}

Invoke 及 JavaScript/TypeScript API

最後一個議題,是在 Blazor WebAssembly 嵌入 Tauri 應用程式後,如何使用 Tauri 提供的 Invoke 及其他 JavaScript/TypeScript API。

tauri.conf.json 中,我們可以設定 build.withGlobalTauritrue,這樣就可以單純的在 JavaScript 中使用 window.__TAURI__ 來呼叫 Tauri 提供的相關 API。

{
  "build": {
    "withGlobalTauri": true
  }
}

而在 Blazor WebAssembly 中,我們直接使用注入 IJSRuntime 的方式處理,例如:

@inject IJSRuntime JSRuntime

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    async Task IncrementCount()
    {
        var message = await JSRuntime.InvokeAsync<string>(
            "window.__TAURI__.tauri.invoke", 
            "greet",
            new { name = "Mike" });
        Console.WriteLine(message);
    }
}

如果要呼叫作業系統相關的 API,一樣可以使用 Tauri 提供的 JavaScript/TypeScript API,例如:

// 用 eval 取得 Tauri JavaScript API 內建的常數
var desktopBasePath = await JSRuntime.InvokeAsync<int>(
    "eval",
    "window.__TAURI__.fs.BaseDirectory.Desktop");

// 呼叫 Tauri 的 JavaScript API
await JSRuntime.InvokeVoidAsync(
    "window.__TAURI__.fs.writeTextFile", 
    "tauri-greet.txt", 
    "Hello World"", 
    new { dir = desktopBasePath });

在這邊比較特別的是,我們需要使用 eval 來取得 Tauri JavaScript API 內建的常數,例如 window.__TAURI__.fs.BaseDirectory.Desktop,因為這個常數是在 Tauri 應用程式內建的,也不是個可以被呼叫的方法,因此這裡改成呼叫 eval 來取得指定的常數。

比較理想的作法還是些一個 JavaScript 的 function 來取得這個常數,然後再從 JSRuntime 呼叫這個 function 來取得資料,會比較好一點,不過這邊就偷懶一下,直接用 eval 來取得。

更理想一點,是將這些呼叫通通都包裝成 C# 的 API,把這些醜醜的字串都隱藏起來,不過要花費的時間就比較多了,如果你有實作出來,或看到其他人的實作,歡迎分享給我。

之後的 window.__TAURI__.fs.writeTextFile 則是呼叫 Tauri 提供的 JavaScript API,跟前面都差不多,這邊就不多做介紹了。

結論

今天的文章我們介紹了如何使用 Tauri 將 Blazor WebAssembly 應用程式打包成桌面應用程式,雖然 create-tauri-app 沒有內建 Blazor WebAssembly 的範本,不過 Tauri 的架構非常好理解,因此我們可以很容易的將 Blazor WebAssembly 應用程式整合進 Tauri 中。並且理論上任何純前端的架構都能依照這個方法進行整合!

而透過 Blazor WebAssembly 的 IJSRuntime,我們也能輕易的呼叫 Tauri 提供的 JavaScript/TypeScript API,在評估一個非 JavaScript 的純前端框架時,也記得要把這個框架是否能呼叫 JavaScript 當做一個重要的衡量依據喔!

如果您覺得我的文章有幫助,歡迎免費成為 LikeCoin 會員,幫我的文章拍手 5 次表示支持!