Blazor WebAssembly アプリの GitHub Pages への発行を、より楽にする

# Blazor WebAssembly アプリは、GitHub Pages 上にも配置できる

Blazor は C# で SPA を実装するフレームワークやランタイムでありますが、とりわけ Blazor の WebAssembly 版は、JavaScript 製の SPA フレームワークと同じくブラウザ上で動作します。

そのため、静的コンテンツを配置・配信可能な Web サーバーであれば何であっても、そのサーバー上に Blazor WebAssembly アプリの発行成果物を配置すれば、それをブラウザで開いて動作させることができます。

実際、Blazor WebAssembly アプリは、GitHub Pages 上に配置することもできます。

自分が GitHub Pages 上に配置・公開している Blazor WebAssembly アプリを以下に2つほど例示しておきます。

## [Awesome Blazor Browser](https://jsakamoto.github.io/awesome-blazor-browser/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/39002c37-f9be-b87b-82dd-60c560cd5582.png)

## [https://jsakamoto.github.io/ (私個人のポートフォリオサイト)](https://jsakamoto.github.io/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/5e7c3bf2-cd66-fb32-3150-d2d9b5b9391b.png)

# だがしかし GitHub Pages 用に調整するのがダルい...

さて、Blazor WebAssembly (に限らず、JavaScript 製の各種 SPA フレームワークでも同じだと思いますが)  を GitHub Pages 向けに "発行" するのは、難しくはないのですが、いくつか "調整" 作業が必要です。

- (GitHub Pages が備える Jekyll 変換は使わないので) **`.nojekyll` ファイルを追加**
- Git による迂闊な変換がされないよう **`.gitattribute` ファイルを作成**
- `index.html` 中の `<base href="..."/>` が指す**ベース URL** を、**GitHub Pages に配置後の URL** に合せて書き換え ( (a) 発行後に `index.html` を書き換えるか、(b) 事前に (`index.html` はもちろん) `launchSettings.json` も含めてプロジェクトそのもののベース URL を変更しておくか、のいずれか)
- (`index.html` をコピーして) **`404.html` ファイルを追加**

ひとつひとつは大した作業ではないです。
っていうか、機械的な単純作業の部類かと思います。

ですが、`index.html` の変更も含むようなアプリの改訂後に再発行、など、発行作業を繰り返し行なうとき、**こんな単純作業を手作業で**行なうのもなんだかなぁ、と、常々思っていました。

# GitHub Pages 向け発行作業を自動化 & NuGet パッケージ化

そこで、ある日ついに一念発起して、Blazor WebAssembly アプリを **GitHub Pages 向けに発行する作業を自動化**し、**NuGet パッケージとして公開**しました。

その NuGet パッケージがこちらです。

- [NuGet Gallery | PublishSPAforGitHubPages.Build](https://www.nuget.org/packages/PublishSPAforGitHubPages.Build/)

ということで、この NuGet パッケージを使って、Blazor WebAssembly アプリを GitHub Pages 向けに発行する作業を説明します。

## 1. NuGet パッケージ参照をプロジェクトに追加

まずは、なにはともあれ、対象の Blazor WebAssembly プロジェクトに、この NuGet パッケージ参照を追加します。

Windows OS 上で Visual Studio を使って開発中であれば、Visual Studio の NuGet パッケージマネージャウィンドウから "PublishSPAforGitHubPages.Build" を検索して追加したり、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/e49bc6a5-e67f-9968-e577-93d792c3e767.png)
あるいは Visual Studio の NuGet パッケージマネージャコンソールウィンドウにて、追加することもできます。

```shell:VisualStudioのNuGetパッケージマネージャコンソールウィンドウにて
PM> Install-Package PublishSPAforGitHubPages.Build 
```

Visual Studio Code など任意のエディタで作業中でしたら、対象の Blazor WebAssembly のプロジェクトファイル (.csproj) を直接編集し、適当な `<ItemGroup>` ノードの中に (なければ `<ItemGroup>` ノードの追加も含めて) 以下のように NuGet パッケージ参照の記述を追記すればよいでしょう。

```xml:*.csproj
...
<ItemGroup>
  ...
  <PackageReference Include="PublishSPAforGitHubPages.Build" Version="1.1.0.1" />
  ...
</ItemGroup>
...
```

もちろん、dotnet CLI で NuGet パッケージ参照を追加することもできます。

```shell:プロジェクトファイルがあるフォルダをカレントフォルダとしたターミナルで
$ dotnet add package PublishSPAforGitHubPages.Build
```

## 2. `GHPages` MSBuild プロパティを指定して発行すれば OK!

ひとたび `PublishSPAforGitHubPages.Build` をプロジェクトに追加した以降は、そのプロジェクトを、**`GHPages` MSBuild プロパティに `true` を指定して発行するだけ**です。

例えば dotnet CLI を使って発行する場合は以下のように "-p:GHPages=true" を付けて実行すればよいです。

```shell:プロジェクトファイルがあるフォルダをカレントフォルダとしたターミナルで
$ dotnet publish -c:Release -p:GHPages=true
```

Visual Studio の GUI からファイルへ発行する場合は、発行プロファイルファイル (.pubxml) か、あるいは、プロジェクトファイル (.csproj) を事前に編集しておいて、MSBuild スクリプト内に `GHPages` MSBuild プロパティに `true` を指定しておくとよいでしょう。

```xml:*.csprojまたは*.pubxml
<PropertyGroup>
  <GHPages>true</GHPages>
</PropertyGroup>
```

以上、このように **GHPages=true の MSBuild プロパティを指定して発行するだけ**で、発行先のフォルダには、本投稿の冒頭で説明したとおりの、**GitHub Pages 向けの調整がすべて施された発行成果物ファイルが生成**されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/47fa3278-cdd2-555a-e7b7-6861e6f1d5dd.png)

# オフライン対応 PWA でも大丈夫!

さてところで、GitHub Pages に配置する Blazor WebAssembly アプリを、オフライン対応の PWA にする場合、`index.html` を変更するだけ（例：内部のベース URL を変更）で、それ以上は何もしないでいると、その Blazor PWA はエラーとなって動作しません。

通常の Blazor PWA プロジェクトでは、その発行時に各コンテンツファイルのハッシュを計算して `service-worker-assets.js` ファイルにそのハッシュ値を格納します。そして、Web ブラウザはその Blazor PWA を読み込む際に `service-worker-assets.js` ファイルと付き合わせて整合性チェックを行います。

```js
// 📜 service-worker-assets.js
self.assetsManifest = {
  "assets": [
    {
      "hash": "sha256-lqehqsKUaAnnNozj4bnXdM6UlKdzXsxJnVsMN+SjhAY=",
      "url": "index.html"
    },
    ...
```

そのため、`index.html` だけ変更して `service-worker-assets.js` を何も更新しないと、整合性チェックに失敗し、その Blazor PWA は動作しなくなります。

幸い、この `PublishSPAforGitHubPages.Build` パッケージ（2021年12月リリースの v.1.3.5 ないしはそれ以降）は、発行時に **`service-worker-assets.js` ファイル内のハッシュ値を適切に自動更新**します。そのため、最新版の`PublishSPAforGitHubPages.Build` パッケージを使用して Blazor PWA を GitHub Pages にデプロイする場合は、上記の**整合性チェックについて開発者が気にする必要がありません**。`PublishSPAforGitHubPages.Build` を使っていれば、それらの心配は無用です。

# Brotli 圧縮も有効に!

さらに、この NuGet パッケージ `PublishSPAforGitHubPages.Build` (2021年12月リリースの v.1.2.0 ないしはそれ以降)には、**さらなる特典**があります。

なんと、GitHub Pages に配置した Blazor WebAssembly アプリにおいて、**事前 Brotli 圧縮されたアセンブリ読み込みが有効**になるのです!

- ["Search result of “Brotli 圧縮” - Qiita"](https://qiita.com/search?q=Brotli+%E5%9C%A7%E7%B8%AE)

実は Blazor WebAssembly プロジェクトを発行すると、素の .NET アセンブリファイル (.dll, .wasm) に加えて、GZip 圧縮した同名のファイル (.dll.gz, .wasm.gz) および **Brotli 圧縮した同名のファイル (.dll.br, .wasm.br) が同時に生成**されています。

ただ、これら事前圧縮されたアセンブリファイルは、そのままでは採用されません。

Blazor WebAssembly アプリのローダー側で、明示的に .dll.br あるいは .wasm.br を Web サーバーから取得し、Brotli 圧縮を伸張する必要があるのです。

詳しくは下記公式ドキュメントに解説があります。

- ["ASP.NET Core Blazor WebAssembly のホストと展開 | Microsoft Docs"](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/webassembly)


最新版の `PublishSPAforGitHubPages.Build` を使って、GHPages=true を指定しての発行を行なうと、発行処理の一環として、発行後の `index.html` ファイルを書き換えて、**事前 Brotli 圧縮されたアセンブリ読み込みを行なう JavaScript コードの注入**が行なわれるようになる仕掛けです。

```html:発行後のindex.html
  ...
  <!-- 👇 autostart="false" 属性が書き加えられる -->
  <script src="_framework/blazor.webassembly.js" autostart="false"></script>

  <!-- 👇 以下 2行の script 要素が追加される -->
  <script src="decode.min.js"></script>
  <script src="brotliloader.min.js"></script>
</body>
```

Blazor WebAssembly は、どうしても .NET アセンブリファイル群 (.dll, .wasm) の総サイズが膨らみがちです。
もっとも、二回目以降のアクセスでは、初回アクセス時にブラウザの Cache Storage に保存されたデータを使うので、さくっと開きます。

ですが、そうは言っても、とにかく初回アクセス時には、どうしてもそれら多くの .NET アセンブリファイル群 (.dll, .wasm) を転送せざるを得ず、通信量がかさみ、初期ロード時間が長くなる懸念があります。

その点、事前 Brotli 圧縮されたアセンブリファイル (.dll.br, .wasm.br) を取得するようにすることで、**結構なサイズの通信量を削減**することができ、初回アクセス時の**初期ロード時間の短縮**が期待できます。

# おまけ: GitHub Actions を使った配置

以上、Blazor WebAssembly アプリを GitHub Pages 向けに発行するにあたり、つまらぬ調整作業を一手に引き受ける上に、事前 Brotli 圧縮されたアセンブリファイル読み込み機能も織り込んでしまう NuGet パッケージ `PublishSPAforGitHubPages.Build` の紹介でした。

なお、この `PublishSPAforGitHubPages.Build` パッケージと GitHub Actions を組み合わせると、さらに GitHub Pages への配置が簡単になります。

例えば以下のような GitHub Actions 用の YAML ファイルを、リポジトリの `.github/workflows` フォルダに収録しておくと、**GitHub に push するたびに自動で、`gh-pages` ブランチに Blazor WebAssembly アプリを配置**してくれるようになります。

```yaml:.github/workflows/gh-pages.yml
name: github pages

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # Checkout the code
      - uses: actions/checkout@v4

      # Install .NET SDK
      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 9.0.x
      - name: Install .NET WebAssembly Tools
        run: dotnet workload install wasm-tools

      # Publish the site
      - name: Publish
        run: dotnet publish (プロジェクトフォルダ)/(プロジェクト名).csproj -p:GHPages=true -c:Release -o:public

      # Deploy the site
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: public/wwwroot
          force_orphan: true
```

これでより手軽に、自作の Blazor WebAssembly アプリを、GitHub Pages 上で公開できるようになりますね!

_Learn, Practice, Share!_