Vue.jsとBlazorでWebアプリを作ってみた比較

僕は1か月ちょっとの間Vue.jsとBlazor(Wasm)双方でMHWIルーレットを作っていました。
(モンスターハンターワールド:アイスボーン用に装備とかクエストを自動で決めるツール)

Vue.jsとBlazorの書き心地って似てるよねってことでそれぞれ作ってみてその比較を書き留めます。

# 実装
[Vue.js](http://yosgspec.starfree.jp/lret/mhwi-ve/)
[Blazor](http://yosgspec.starfree.jp/lret/mhwi-br/)
[ソースコード](https://github.com/yosgspec/mh-lret7)

# 環境
## Vue.js
* @vue/cli 4.1.2
	+ Babel
	+ TypeScript
	+ Router
	+ Vuex

## Blazor
* Blazor WebAssembly 3.2.0 preview1

# メリット・デメリット
◎>〇>△>× の順で良い。
## Vue.js
| 評価 | 内容
|:----:|:-----------------------------
| ◎   | ロードが高速。
| ○    | ビルドファイルの容量が小さめ([MHWIルーレットVE](http://yosgspec.starfree.jp/lret/mhwi-ve/) 1.43MB)(しかし、ネイティブよりは容量が大分多い)
| ◎   | style scopedの利用可。
| ◎   | TypeScriptを簡単に導入できる(vue-cliにて選択するだけ)。<br>vue-property-decoratorも個人的に結構好き。
| △   | Vue Routerの設定が少し煩雑。
| △   | 各種ファイル読み込みでimport文が多くなりがち。
| ○   | 状態管理ライブラリ(Vuex)が公式で提供されている。
| ◎   | 画面上の要素は適切に更新されるので気を使う必要がない。
| ○    | html上の改行や空白が削除される。

## Blazor
| 評価 | 内容
|:----:|:-----------------------------
| ×    | ロードが遅い。
| ×    | ビルドファイルの容量が大きい([MHWIルーレットBR](http://yosgspec.starfree.jp/lret/mhwi-br/) 4.91MB)
| ×    | フレームワークとしてコンポーネントへのCSSのインポート方法が(おそらく)用意されていない。<br>[html5の仕様としてbodyの中に埋め込むことを許可されたりされなかったり](https://wiki.developer.mozilla.org/ja/docs/Web/HTML/Element/style$compare?locale=ja&to=1602521&from=1528790)しているみたいだけど、おおよそのブラウザがbody内styleを認識できるので妥協した。お行儀良くないし、推奨もされないだろう。
| ◎    | (大体)C#のみで処理を書くことができる。
| ×     | DOM APIの操作にはJavaScriptに頼らなくてはならない。<br>また、JSRuntime.InvokeAsyncで扱えるのが関数/メソッドのみなので、プロパティやフィールドへのアクセスができない。推奨されないが必要とあればevalとかしてお茶を濁す。[一応綺麗にjsにアクセスする方法もある。](https://webcache.googleusercontent.com/search?q=cache:lOZxlJuLMOYJ:https://docs.microsoft.com/ja-jp/aspnet/core/blazor/javascript-interop%3Fview%3Daspnetcore-3.1+&cd=1&hl=ja&ct=clnk&gl=jp&lr=lang_ja#use-of-ijsruntime)個別にjsファイル用意してindex.htmlに埋め込むのも面倒だと思う。
| ◎    | ルーターの設定が対象ページの冒頭に1行追記するだけで良いので簡単。
| ○    | プロジェクト丸ごとインポートしてくれるので名前空間を分けない限りは呼び出し時の定義が不要。
| △    | 状態管理ライブラリが用意されていない。ただし、ライブラリの追加なしで似たような書き方は可能。
| △    | 画面上の要素を変更した時、StateHasChangedを呼び出して手動で画面を更新する必要がある。
| ○    | html上の改行や空白が削除されない。

機能的には全体的にVue.jsが勝っているなぁという印象でした。

# ディレクトリ構成
それぞれ初期化した時に作られるディレクトリ構成について。
## Vue.js
```
├─public
└─src
    ├─assets
    ├─components
    ├─router
    ├─store
    └─views
```

## Blazor
```
├─Pages
├─Shared
└─wwwroot
```

## ディレクトリの役割
| 項目                          | Vue.js          | Blazor              |
|-------------------------------|-----------------|---------------------|
| 静的ファイル(index.html,CSS)  | /public         | /wwwroot            |
| 動的に読み込む静的ファイル    | /src/assets     | /wwwroot            |
| ページファイル                | /src/views      | /Pages              |
| コンポーネントファイル        | /src/components | /Shares             |
| Vuex等状態管理                | /src/store      | 任意                |
| ルーターの設定                | /src/router     | なし                |

今回のMHWIルーレットでは一部ディレクトリ構成を下記のように追加しています。

| 項目                          | Vue.js          | Blazor              |
|-------------------------------|-----------------|---------------------|
| 動的に読み込む静的ファイル    |                 | /wwwroot/**Assets**   |
| Vuex等状態管理                |                 | /**Store**          |
| その他汎用スクリプト(.ts/.cs) | /src/**lib**    | /**Lib**            |

# 設定ファイル
## Vue
```
│  babel.config.js
│  package.json
│  tsconfig.json
│  vue.config.js
│  
└─src
    │  main.ts
    │  shims-tsx.d.ts
    │  shims-vue.d.ts
    │  
    └─router
            index.ts

```

### /babel.config.js
Babelの設定ファイル。

### /package.json
プロジェクトの設定ファイル。

### /tsconfig.json
TypeScriptの設定ファイル。

### /vue.config.js
ドメイン直下以外のパスに配置するとき以外は必要なファイルです。
記入したパス向けにビルドを行います。

### /src/main.ts
エントリーポイント。
App.vueとVue Routerを起動している様子。
テンプレート作成時にルーターを設定していれば改めて触る必要はないです。

#### /src/shims-tsx.d.ts, /src/shims-vue.d.ts
多分TypeScriptの型を定義しているファイル。

### /router/index.ts
ルーターで切り替えるページ数が増えるたびに追記する必要があります。
ページに割り当てたいコンポーネントをパスとともに記述します。

## Blazor
```
│  base.patch
│  mh-lret.csproj
│  Program.cs
│  _Imports.razor
```

#### /base.patch
ドメイン直下以外のパスに配置するとき以外は/wwwroot/index.htmlのbase要素のパスを変更する必要があります。
それを自動化するために作ったpatchファイルです。

#### /mh-lret.csproj
プロジェクトの設定ファイルです。

#### /Program.cs
エントリーポイント。
App.razorを起動しているみたいです。
[サービス](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.1)を追加する場合はここに追記します。  
今回は状態管理用のクラスを追加するため追記しています。

```cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped<AppState>(); //<-追加

await builder.Build().RunAsync();
```

[参考1](https://qiita.com/nr_ck/items/796413387d831b7f1761) [参考2](https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/)

#### /_Imports.razor
razorファイルで常に参照できる名前空間を追記します。

# index.html
アプリを反映するための基本htmlファイルです。
アプリ起動に失敗した場合の処理はデフォルトで大体ここに書かれています。
head要素はここにしか書けないので結構重要そう。
Blazor的にはC#から呼び出せるjsファイルを配置できる唯一のファイルです。
Blazorはロードが凄く長いのでスプラッシュ画面を用意したりしました。

##Vue.js
```vue:/public/index.html
<!DOCTYPE html>
<html lang=ja>

<head>
	<title>MHWIルーレットVE</title>
</head>

<body>
	<noscript>
	<strong>We're sorry but ve doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
	</noscript>
	<div id="app"></div>
</html>

```

## Blazor
```html:/wwwroot/index.html
<!DOCTYPE html>
<html lang=ja>

<head>
	<base href="/" />
	<title>MHWIルーレットBR</title>
</head>

<body>
	<app>Loading...</app>
	<div id="blazor-error-ui" style=display:none>
		An unhandled error has occurred.
		<a href="" class="reload">Reload</a>
		<a class="dismiss">🗙</a>
	</div>
	<script src="_framework/blazor.webassembly.js"></script>
</body>

</html>
```

# App Component/ルーター
基準となるコンポーネントファイルとルーターについて。
## Vue.js
最初に呼び出されるコンポーネントファイルはApp.vueとなります。
ルーターを使用する場合はルーター用のリンク(router-link)とページコンポーネントが表示される場所(router-view)を記載します。
ルーターのパス構成は/src/router/index.tsから行う必要があります。

```vue:App.vue
<template>
<main v-cloak>
	<router-view/>
	<div id=nav>
		<router-link to="/readme/">ルーレットについて</router-link>
		<router-link to="/">MHWI</router-link>
		<a href="/lret/mhwi-br/">(BR)</a>
	</div>
</main>
</template>
```

## Blazor
最初に呼び出されるコンポーネントファイルはApp.razorとなります。
デフォルトではルーターの設定だけが書かれており、レイアウトに関する情報は全てMainLayout.razorに書かれています。
ルーターを使用する場合はルーター用のリンク(NavLink)とページコンポーネントが表示される場所(@<wbr>Body)を記載します。

```vue:MainLayout.razor
@inherits LayoutComponentBase

<main>
	@Body
	<div id=nav>
		<NavLink href="./readme/" Match="NavLinkMatch.All">ルーレットについて</NavLink>
		<NavLink href="./" Match="NavLinkMatch.All">MHWI</NavLink>
		<a href="/lret/mhwi-ve/">(VE)</a>
	</div>
</main>
```


# 文法の比較
記事を分離しました。
[つづきはこちらへ。](https://qiita.com/yosgspec/items/3cf93e70a81805d70d29)