ElectronとWebAssemblyとBlazorの違い

## はじめに

ElectronとWebAssemblyとBlazorの違いがよくわかっていないので、JS関連の学習を始めるきっかけとして整理してみる。

Windows 以外のOS（MacやLinuxなど）でも利用できるようにしたいとか、今でも超現役の Windows Forms の代替っぽいものがほしいとか、クロスプラットフォームでデスクトップアプリケーション（っぽいもの）を実現したいとか、できればC#の方法もあわせて試していきたい。

## 参考（Windows アプリケーションの開発経歴）

* 2000年ごろ
  * Win32 / MFC (C++)
     * 今でも大変お世話になってます。
  * Visual Basic 系 (VB5, VB6, VB.NET, VBA)
     * そろそろ彼らとは距離をおきたい。
* 2005年以降
  * Windows Forms (C#)
     * .NET Framework 2.0 からかなり安定。C# は大好き。
     * .NET 3.5 からの LINQ や Entity Framework 6 が好き。
     * .NET Core 3.0 お試し中。 .NET 5 とかどうなるんだろ。
* 2010年以降
  * Silverlight / WPF / UWP (C#)
     * Silverlight 終了のお知らせに泣かされました。
     * WPF とか UWP って実際のところどの程度利用されてるのでしょうか？

MS系 Windows アプリケーションの技術ばかりなので、フロントエンドの技術をもっと経験したい。

## Electron

GitHubが開発したオープンソースのソフトウェアフレームワーク

https://electronjs.org/

ChromiumとNode.jsを利用し、HTML、CSS、JavaScriptのようなWeb技術を用いて、macOS、Windows、Linuxに対応したクロスプラットフォームのデスクトップアプリケーションを実現できる。

Node.js がアプリケーションのロジックを担当し、Chromium がアプリケーションのUIを担当する。
JavaScriptエンジンとブラウザをまとめてデスクトップアプリケーションとして配布することで、プラットフォーム間の差異を吸収できる。

**Web技術を利用して配布可能なデスクトップアプリケーションを構築できることが特徴**

* HTML/CSSやJavaScriptといったオープンな技術でアプリケーションを作成できる
* 単体で配布可能なバイナリを生成できる
* 単一ソースでWindows、Mac、Linuxで動作するアプリケーションを作成できる

### Electronを使用しているソフトウェアの例

* Atom
* Docker GUI
* Skype
* Slack
* Visual Studio Code
* など

## Electron.NET

.NET ベースのソフトウェアフレームワーク

https://github.com/ElectronNET/Electron.NET/

.NET Core および ASP.NET NET Coreを使用して、クロスプラットフォームのデスクトップアプリケーションを実現。Electron.NETは、ASP.NET Coreアプリケーションが埋め込まれた Electron アプリケーションのラッパーとなる。Electron.NET IPCブリッジを介して、.NETからElectron APIを呼び出す。

> IPCとは、interprocess communicationの略で「プロセス間通信」と呼ばれる。

Electron は JavaScript をベースとした技術を利用しているが、Electron.NETは C#（ASP.NET Core）をベースとした技術を利用しているため、.NETのノウハウを生かした開発を行うことができる。

**ASP.NET Coreを利用してElectronアプリケーションを構築できることが特徴**

* Electronを.NETに移植したものではなく、ASP.NET CoreとElectronを組み合わせたもの
* .NET のノウハウを生かして、Electronアプリケーションを開発できる
* マイクロソフトオフィシャルではないため、将来性は未知数？

## WebAssembly

ウェブブラウザのクライアントサイドスクリプトとして動作するプログラミング言語（低水準言語）

https://webassembly.org/

WebAssembly（省略形は*Wasm*）は、スタックベースの仮想マシン用のバイナリ命令フォーマットで、C / C ++ / C# / Rust / Go / Kotlin/Native のような高級言語のコンパイルのための移植性のあるターゲットとして設計されている。簡単にいうとブラウザでバイナリコード（アセンブリ）を直接実行できるようにする技術。

バイナリコードをブラウザが扱えるようになることで、JavaScriptに比べてファイルサイズを大幅に小さくすることができ、処理を高速化することができる。

**ブラウザ上でバイナリフォーマットの形でプログラムを実行できることが特徴**

* WebAssembly そのものはデスクトップアプリケーションを実現するための技術ではない
* 様々な開発言語で作成されたコードを解釈してブラウザ上で実行する
* UIはHTMLやCSSやJavaScriptなどで実現する必要がある

## Blazor

C＃、Razor、およびHTMLをベースにしたWeb UIフレームワーク

https://dotnet.microsoft.com/apps/aspnet/web-apps/client

Blazor は、JavaScript の代わりに C＃ を使用してインタラクティブなWeb UIを構築する。Blazor アプリケーションは、C＃、HTML、CSSを使用して実装された再利用可能なWeb UIコンポーネントで構成され、クライアントとサーバーの両方のコードが C＃で記述されるため、コードとライブラリを共有することができる。

Blazor は、WebAssembly を使用してクライアント側のC#のコードをブラウザ上で直接実行する。

**JavaScript の代わりに C# を利用してインタラクティブなUIを実現できることが特徴**

* JavaScript ではなく C# でコードを記述できるもので、デスクトップアプリケーションを実現するための技術ではない
* .NET ライブラリなど既存の .NETのノウハウを活用できる
* サーバーとクライアント全体でアプリケーションのロジックを共有できる

## そのほか

ほかにも NW.js や Meteor や PWA などありますね・・・。

## まとめ

クロスプラットフォームでデスクトップアプリケーションを実現するための方法としては、Electronが成熟して妥当なものになるのでしょうか。C#や.NETのノウハウを生かすという点では、Electron.NETも候補になるけど、ASP.NET CoreによるElectronのラッパーなので、まずは通常のElectronを利用して理解を深めていきたい。

JavaScript ではなく C# を活用してインタラクティブなUIを実現する方法として、ブラウザでC#のコードを直接実行できる Blazor も個人的には興味あり。

JavaScript、HTML、CSSなどのフロントエンド技術の理解不足を痛感してるので、これらの学習を最優先とします。

## デモ

### Blazor を利用してみる

* [ASP.NET Core Blazor の概要](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/get-started?view=aspnetcore-3.1&tabs=visual-studio)

* [最初の Blazor アプリをビルドする](https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1)

* [チュートリアル: ASP.NET Core で Razor Pages Web アプリを作成する](https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/?view=aspnetcore-3.1)

Windows 10 Pro x64 + Visual Studio 2019 16.6.0 を利用します。

1. Visual Studio 2019 に最新のアップデート 16.6.0 を適用します。（2020/5/21時点）

1. PowerShell で下記コマンドを実行し、ASP.NET Core Blazor WebAssembly プロジェクトのテンプレートをインストールします。

    ```powershell
    > dotnet new --install Microsoft.AspNetCore.Components.WebAssembly.Templates :: 3.2.0
    ```

    > 新しいバージョンの Visual Studio にアップグレードした場合、またはインストールした場合で、VS の UI に Blazor WebAssembly テンプレートが表示されないときは、前述の dotnet new コマンドを使用してテンプレートを再インストールしてください。

1. Visual Studio 2019 を起動します。

1. 新しいプロジェクトを作成します。

1. [Blazor アプリ]を選択して「次へ」をクリックします。

1. 任意のプロジェクト名を入力して「作成」をクリックします。

1. [Blazor WebAssembly App] を選択して「作成」をクリックします。

1. プロジェクト作成後にデバッグ実行するとサンプルプログラムがブラウザで実行されます。

1. [Pages/Counter.razor]を下記のように修正します。

    ```C#
    @page "/counter"
    
    <h1>Counter</h1>
    <p>Current count: @currentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    
    @code {
        private int currentCount = 0;
    
        [Parameter]
        public int IncrementAmount { get; set; } = 1;
    
        private void IncrementCount()
        {
            currentCount += IncrementAmount;
        }
    }
    ```

1. [Pages/Index.razor]を下記のように修正します。

    ```C#
    @page "/"
    
    <h1>Hello, world!</h1>
    Welcome to your new app.
    <Counter IncrementAmount="10" />
    <SurveyPrompt Title="How is Blazor working for you?" />
    ```

1. デバッグ実行し、カウンターがホームに設置され10ずつカウントアップされることを確認します。

### Electron を利用してみる

Windows 10 Pro x64 + nodist を利用

1. Windows で Node.jsのバージョンを管理するために [nodist](https://github.com/nullivex/nodist/releases) をインストールします。

1. 下記コマンドを実行して、利用可能なNode.jsのバージョンを確認します。

    ```powershell
    > nodist dist
    ```

1. 下記コマンドを実行して、必要となるNode.jsのバージョンをインストールします。

    ```powershell
    > nodist + 14.15
    ```

1. 下記コマンドを実行して、インストールしたNode.jsのバージョンを切り替えます。

    ```powershell
    > nodist global 14.15
    ```

1. 下記コマンドを実行して、npm も node にあわせたバージョンにしておきます。

    ```powershell
    # node と npm のバージョンをあわせておかないとエラーになる
    # https://nodejs.org/ja/download/releases/
    > nodist npm global 6.14.8
    ```

1. 下記コマンドを実行して、Node.jsのバージョンや切り替えた結果を確認します。

    ```powershell
    > nodist
      (x64)
      11.13.0
      12.8.0
      12.16.3
    > 14.15.0  (global: 14.15)

    > node -v
    v14.15.0

    > npm -v
    6.14.8
    ```

1. nodeのパスに関する警告が発生する場合は、下記コマンドを実行します。

    ```powershell
    > npm config set scripts-prepend-node-path true
    ```

1. 下記コマンドを実行して、プロジェクトのディレクトリを作成して移動します。

    ```powershell
    > mkdir electron01
    > cd electron01
    ```

1. 下記コマンドを実行して、Node.jsのプロジェクトを初期化します。

    ```powershell
    > npm init
    ```

    ```powershell
    This utility will walk you through creating a package.json file.
    It only covers the most common items, and tries to guess sensible defaults.
    
    See `npm help json` for definitive documentation on these fields
    and exactly what they do.
   
    Use `npm install <pkg> --save` afterwards to install a package and
    save it as a dependency in the package.json file.
    
    Press ^C at any time to quit.
    name: (electron01)
    version: (1.0.0) 0.0.1
    description: Electron Sample 01
    entry point: (index.js) main.js
    test command:
    git repository:
    keywords:
    author: hogehoge
    license: (ISC)
    ```

1. package.jsonをエディタで開き、Electronを実行するためのエイリアスコマンドを追加します。

    ```json
    {
      "name": "electron01",
      "version": "0.0.1",
      "description": "Electron Sample 01",
      "main": "main.js",
      "scripts": {
        "start": "electron .", // この行を追加する
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "hogehoge",
      "license": "ISC"
    }
    ```

1. 下記コマンドを実行して、Electron本体とログ出力やビルドに関連するパッケージをインストールします。

    ```powershell
    > npm install --save-dev electron
    > npm install --save electron-log
    > npm install --save electron-packager
    ```

1. プロジェクトのディレクトリにmain.jsファイルを作成します。

    ```javascript
    const { app, BrowserWindow } = require('electron')
    
    // ウインドウオブジェクトのグローバル参照を保持
    let win
    
    function createWindow () {
        // ブラウザウィンドウを生成
        win = new BrowserWindow({
            width: 800,
            height: 600,
            webPreferences: {
                nodeIntegration: true
            }
        })
        // index.htmlをロード
        win.loadFile('index.html')
        // 開発者ツールを開く
        win.webContents.openDevTools()
        // ウィンドウが閉じられた際に発火
        win.on('closed', () => {
            // ウインドウオブジェクトの参照を外す
            win = null
        })
    }
    
    // Electronの初期化が完了した際に発火（ウィンドウ生成）
    app.on('ready', createWindow)
    
    // 全てのウィンドウが閉じられた際に発火
    app.on('window-all-closed', () => {
        // MacOS以外はアプリケーションを終了
        if (process.platform !== 'darwin') {
            app.quit()
        }
    })
    
    app.on('activate', () => {
        // アプリケーションのウィンドウが無かったら再作成
        if (win === null) {
            createWindow()
        }
    })
    ```

1. プロジェクトのディレクトリにindex.htmlファイルを作成します。

    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>Hello World!</title>
      </head>
      <body>
        <h1>Hello World!</h1>
        We are using node <script>document.write(process.versions.node)</script>,
        Chrome <script>document.write(process.versions.chrome)</script>,
        and Electron <script>document.write(process.versions.electron)</script>.
      </body>
    </html>
    ```

1. ファイル構成です。

    ```powershell
    ├─ node_modules
    ├─ index.html
    ├─ main.js
    └─ package.json
    ```

1. プロジェクトのディレクトリで下記コマンドを実行してアプリケーションを起動します。

    ```powershell
    > npm start
    ```

1. デスクトップアプリケーションが起動することを確認します。

1. Windows用の実行ファイルとして出力してみます。

1. package.jsonをエディタで開き、Windows用の実行ファイルを出力するためのエイリアスコマンドを追加します。

    ```json
    {
      "name": "electron01",
      "version": "0.0.1",
      "description": "Electron Sample 01",
      "main": "main.js",
      "scripts": {
        "start": "electron .",
        "export": "electron-packager . electron01 --platform=win32 --arch=x64 --overwrite --electron-version=6.0.2", // この行を追加する
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "hogehoge",
      "license": "ISC",
      "devDependencies": {
        "electron": "^6.0.2"
      },
      "dependencies": {
        "electron-log": "^3.0.7",
        "electron-packager": "^14.0.4"
      }
    }
    ```

1. プロジェクトのディレクトリで下記コマンドを実行してWindows用の実行ファイルを出力します。

    ```powershell
    > npm run export
    ```

1. 出力された実行ファイルを起動できることを確認します。