Blazorでnpmとwebpackでバンドルしたjsを使用する

# はじめに

BlazorではC#とJavaScirptの相互運用が可能となっています。
デバイス制御やクラウドサービスとの接続など、JSでしか用意されていないライブラリを使用したい場合などのユースケースが考えられます。

少し前ですと、WEBアプリケーションでJavascirptを使用するにはhtmlタグにscriptタグで個々にjsファイルを参照追加するといった事が多かったと思いますが、現在ではnpm(またはyarn)といったパッケージ管理ツールとwebpackなどのモジュールバンドラーを使用することが多くなっています。
(JS系のSPAフレームワークは使用がほぼ前提。)

モジュールバンドラを使用して個々のコンポーネントや外部ライブラリをまとめて1つのjsファイルとする事で都度参照追加が不要となります。
(Blazorの.NET5で対応したCSSの分離機能も、ビルド時にコンポーネントごとのcssをバンドルして、1つのcssファイルを生成しているのでこういったモジュールバンドラーを使用していることになるかと思います。)

昨今の多くのJavaScriptライブラリもnpm(yarn)とモジュールバンドラーの使用を前提としたサンプルが多く、Blazorで使用する際に自前でjs参照に置き換えるのは大変だったり、うまくいかない事もあったりします。

そこでBlazorでもパッケージ管理とモジュールバンドラーを使ったjsと相互運用を行うための手順を試してみようと思います。

# 手順説明

## 環境

.NET 5.01
Visual Studio 2019 16.8.2
Node.js 14.15.1
npm 6.14.8

Blazor WebAssemblyを使用

[試したソースのGitHub](https://github.com/nobu17/BlazorNpmTest)

## 事前準備

Node.js(npm)をインストールする必要があります。
windowsであればnodistやnvm-windowsなどのツールを使って任意のバージョンを切り替えるられるツールでインストールすることおすすめします。
今回はnodistを使用しています。

nodistですと下記の記事がわかりやすいかと思います。
[nodistでNode.jsをバージョン管理](https://qiita.com/satoyan419/items/56e0b5f35912b9374305)



# 手順

Blazorのプロジェクトのルートディレクトリに任意の名称のフォルダを作成します。
今回はNpmJSとします。

![npm1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/384541b8-cc6a-9c3d-59d6-3a5e91a6d5a1.png)

コマンドプロンプトを立ち上げて上記の作成したディレクトリに移動して下記のコマンドを実行します。

```cmd

npm init -y
```

-yのオプションをつける事で、細かい設定を省略して下記のようなデフォルトの設定でpackage.jsonといったnpmの設定ファイルが作成されます。

![npm2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/de8f9591-efc5-8114-a6d4-32d9e45d83b1.png)

次にwebpackとwebpack-cliをインストールします。

```

npm install webpack webpack-cli --save-dev
```

次に下記のようにsrcディレクトリとindex.jsファイルとwebpack.config.jsを作成します。

![npm3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/73712fde-5858-8ae6-70d5-d7b1273d7093.png)

webpack.config.jsを編集して下記のように記載します。

```javascript:webpack.config.js

const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        index: path.resolve(__dirname, "src/index.js"),
    },
    output: {
        path: path.resolve(__dirname, "../wwwroot/js"),
        filename: "[name].bundle.js",
        library: "MyModule"
    }
};

```

詳細は割愛しますが、index.jsを起点として参照されているjsファイルを全てまとめてindex.bundle.jsに出力するといった設定になります。
outputで指定するパスおよびファイル名はlibraryは自分の好みに合わせて変更してください。


次にpackage.jsonのscriptsの内容を書き換えます。

```json:package.json

{
  ...
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  ...
}

```

この設定により、npm run <scriptsに書かれたコマンド名> で任意のコマンドが実行可能になります。
webpackを実行してjsファイルをバンドルするコマンドを登録しています。
dev側がデバッグ用のバンドルでbuild側が本番環境用にミニファイされたバンドルを出力するオプションになっています。
webpackはバンドルするときに先ほど作成したwebpack.config.jsの設定を使用してjsファイルをバンドルします。

いったん下記のコマンドを実行してjsがバンドルされるか確認してみましょう。

```bash

npm run build
```

下記のようにwwwroot/jsディレクトリ下にindex.bundle.jsが出力されれば成功です。

![npm4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/9d0cee7b-ba4b-b3b6-6a28-c4f3fa1aea61.png)

次にindex.htmlにバンドルしたjsの参照を追加します。

```html:index.html
...
    <script src="js/index.bundle.js"></script>
...
```

次に毎回コマンドを打つのは大変なので、ビルド時にjsファイルもバンドルされるようにcsprojファイルに下記を追加します。

```xml:xxxx.csproj

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="npm install" WorkingDirectory="NpmJS" />
    <Exec Command="npm run build" WorkingDirectory="NpmJS" />
  </Target>
```

詳細は割愛しますが、npm installはpackage.jsonに記載された外部依存モジュールなどで、未インストールのモジュールがあればダウンロードするためのコマンドです。
npm installでインストールするモジュールはnode_modulesといったディレクトリにダウンロードされますが、一般的にGitでは管理しないディレクトリなのでCloneした直後などに未インストールの外部モジュールをダウンロードする際などに使用されます。

## サンプル実装

実際にバンドルしたjsが動くかを試してみましょう。
下記のようなtestCalc.jsを作成してみます。

![npm5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/5aa84c0b-81c0-2df5-718a-2eebd5ce63f3.png)

単純な足し算をするだけのクラスです。

```javascript:testCalc.js

export default class TestCalc {
    sum(x, y) {
        return x + y;
    }
}
```

これをindex.jsにインポートして使用します。

```javascript:index.js

import TestCalc from "./lib/testCalc"

export function sum(x, y) {
    const calc = new TestCalc();
    return calc.sum(x, y);
}

```

Blazorコンポーネントでこのjsを使用します。
[webpack.config.jsで指定したlibrary].[メソッド名]で呼び出しができます。

```csharp:Counter.razor

@page "/counter"
@inject IJSRuntime JSRuntime

<h1>Counter</h1>

<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCountAsync">Click me</button>

@code {
    private int currentCount = 0;

    private async Task IncrementCountAsync()
    {
        currentCount = await JSRuntime.InvokeAsync<int>("MyModule.sum", new object[] { currentCount, 1 });
    }
}
```

下記のようにimportしたモジュールが使用されて計算されていることが確認できますね。
![counter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/2da53b95-ca96-e635-8e8e-ffe5e8d445b5.gif)

## 外部モジュールをインストールして使用する

次にnpmを使用して外部のモジュールをインストールして使用します。
C#ではDateTime型が使えるので実際のケースで使うことは無いと思いますが、JSで時刻を扱う上でおなじみのmoment.jsをインポートして使用してみます。

まずはmoment.jsをインストールします。

```bash

npm install moment
```

momentをインポートして使用します。

```javascript:index.js

import moment from "moment"

export function getCurrentTime() {
    return moment().format();
}
```

同様にCounterコンポーネントに組み込んでみます。

```csharp:Counter.razor

@page "/counter"
@inject IJSRuntime JSRuntime

<p>Current time: @currentTime</p>
<button class="btn btn-danger" @onclick="DisplayCurrentTimeAsync">Display CurrentTime</button>
@code {

    private string currentTime;

    private async Task DisplayCurrentTimeAsync()
    {
        currentTime = await JSRuntime.InvokeAsync<string>("MyModule.getCurrentTime");
    }
}
```

momentを経由して時刻を取得できている事がわかりますね。

![time.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/d9f4fd9f-8b6d-d144-87df-660084b21b06.gif)



# まとめ

ということで、Blazorでnpmとwebpackを使用してバンドルしたjsを使用する方法を紹介しました。
JSの外部パッケージの使用や管理がこれで容易になるではないかと思います。

普段JSのSPAフレームワークを使用していると、自分でこの辺の設定を行わずに自動でよい感じにしてくれるので、触ることで勉強になるとともに機能と奥の深さを感じました。
まだまだ良い設定や見直すべき箇所はあると思います。。

## 忘備録

.NET 5で追加された動的なjsのインポートを使用してバンドルしたjsを呼び出しを試したのですがうまく呼ぶことができませんでした。
(webpackの設定などを見直せば呼び出せるかもしれません。)

## 参考文献

[webpack 4 入門](https://qiita.com/soarflat/items/28bf799f7e0335b68186)
[Using NPM Packages in Blazor](https://brianlagunas.com/using-npm-packages-in-blazor/)
[Using npm packages with Blazor](https://medium.com/swlh/using-npm-packages-with-blazor-2b0310279320)