【Blazor WebAssembly Client】Javascriptコードをモジュール化して呼び出す

## サンプルコード

https://github.com/dhq-boiler/Qiita/tree/develop/Article20230105/BlazorApp1

## 状況

```index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorApp1</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
    <script type="text/javascript">
        var temp;
        function add(a, b) {
            return a + b;
        }
        function subtract(a, b) {
            return a - b;
        }
        function multiply(a, b) {
            return a * b;
        }
        function divide(a, b) {
            return a / b;
        }
        function push(val) {
            temp = val;
        }
        function pop() {
            return temp;
        }
    </script>
</head>

<body>
    <div id="app">
        <svg class="loading-progress">
            <circle r="40%" cx="50%" cy="50%" />
            <circle r="40%" cx="50%" cy="50%" />
        </svg>
        <div class="loading-progress-text"></div>
    </div>

    <div id="blazor-error-ui">
        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>
```

```cs:Index.razor
@page "/"
@inject IJSRuntime JsRuntime;

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<p>1 + 1 = @sum</p>
<p>6 - 2 = @difference</p>
<p>11.1 * 3 = @product</p>
<p>33.3 / 3 = @quotient</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
    private double sum;
    private double difference;
    private double product;
    private double quotient;
    protected override async Task OnInitializedAsync()
    {
        sum = await JsRuntime.InvokeAsync<double>("add", 1, 1);
        difference = await JsRuntime.InvokeAsync<double>("subtract", 6, 2);
        product = await JsRuntime.InvokeAsync<double>("multiply", 11.1, 3);
        quotient = await JsRuntime.InvokeAsync<double>("divide", 33.3, 3);
        await base.OnInitializedAsync();
    }
}
```

javascriptのコードを次々に書いているとどうしてもモジュール化まで手が回らない状況が予想されます。上記のコードはその状況を再現したコードとなっており、index.html上に6つの関数と1つのグローバル変数が展開されています。これがもし増えたらどうなるでしょうか。。。それはindex.htmlの**肥大化**です！index.html上にjavascriptコードを書かれてしまうと、そのコードをユニットテストするのが難しくなるので避けるべきです。以下の手順でモジュール化しましょう♫

なお、モジュール化する際に、Mochaでテストできるようにするために、CommonJSモジュール化（module.exportsを使う）を施します。

## Javascriptコードをモジュール化する手順

1. モジュール化したい関数やグローバル変数を新しいファイルにまとめる

    手始めにモジュール化したい関数とグローバル変数を新しいファイル（今回はjs/module.jsとします）に移します。モジュール化により、グローバル変数はグローバルではなくなります。

    ```index.html
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>BlazorApp1</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
    -   <script type="text/javascript">
    -       var temp;
    -       function add(a, b) {
    -           return a + b;
    -       }
    -       function subtract(a, b) {
    -           return a - b;
    -       }
    -       function multiply(a, b) {
    -           return a * b;
    -       }
    -       function divide(a, b) {
    -           return a / b;
    -       }
    -       function push(val) {
    -           temp = val;
    -       }
    -       function pop() {
    -           return temp;
    -       }
    -   </script>
    +   <script type="text/javascript" src="js/module.js"></script>
    </head>
    
    <body>
        <div id="app">
            <svg class="loading-progress">
                <circle r="40%" cx="50%" cy="50%" />
                <circle r="40%" cx="50%" cy="50%" />
            </svg>
            <div class="loading-progress-text"></div>
        </div>
    
        <div id="blazor-error-ui">
            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>
    ```    

    ```js/module.js

1. 各パブリックインスタンスフィールドの先頭についている、余計な"var"キーワードを取り除く

    モジュール化すると、変更前のグローバル変数はクラスのパブリックインスタンスフィールドになります。パブリックインスタンスフィールドには前置詞は必要ないので、varがついていたら削除します。

    ```js/module.js
    -   var temp;
    +   temp;
        function add(a, b) {
            return a + b;
        }
        function subtract(a, b) {
            return a - b;
        }
        function multiply(a, b) {
            return a * b;
        }
        function divide(a, b) {
            return a / b;
        }
        function push(val) {
            temp = val;
        }
        function pop() {
            return temp;
        }
    ```

1. 各メソッドの先頭についている、余計な"function"キーワードを取り除く

    こちらも同様、モジュール化する＝クラス化するということなのですが、関数はメソッドに変換されるので、メソッドにはfunctionキーワードは必要ないよね、ということで削除します。

    ```js/module.js
        ﻿temp;
    -   function add(a, b) {
    +   add(a, b) {
            return a + b;
        }
    -   function subtract(a, b) {
    +   subtract(a, b) {
            return a - b;
        }
    -   function multiply(a, b) {
    +   multiply(a, b) {
            return a * b;
        }
    -   function divide(a, b) {
    +   divide(a, b) {
            return a / b;
        }
    -   function push(val) {
    +   push(val) {
            temp = val;
        }
    -   function pop() {
    +   pop() {
            return temp;
        }
    ```

1. クラス定義で囲む

    モジュールとして公開する部分をクラス定義で囲みます。この時、クラス名は特に制限が加わるといったことはないと思います。たしか。

    ```js/module.js
    -       ﻿temp;
    -       add(a, b) {
    -           return a + b;
    -       }
    -       subtract(a, b) {
    -           return a - b;
    -       }
    -       multiply(a, b) {
    -           return a * b;
    -       }
    -       divide(a, b) {
    -           return a / b;
    -       }
    -       push(val) {
    -           temp = val;
    -       }
    -       pop() {
    -           return temp;
    +       ﻿class Module {
    +           temp;
    +           add(a, b) {
    +               return a + b;
    +           }
    +           subtract(a, b) {
    +               return a - b;
    +           }
    +           multiply(a, b) {
    +               return a * b;
    +           }
    +           divide(a, b) {
    +               return a / b;
    +           }
    +           push(val) {
    +               temp = val;
    +           }
    +           pop() {
    +               return temp;
    +           }
            }
    ```

1. reduce関数やsome関数で無名関数を使用している場合に、その中でthisを使用している場合は無名関数外部に"const that = this;"を定義して差し替える

    今回の例では採用していなかったのですが、モジュールの公開するメソッド内で、もしreduce関数やsome関数等のいわゆるコレクション関数で無名関数を使用する場合、かつ無名関数内部でthisを使用している場合には一工夫が必要です。別途例を挙げます。

    ```sample.js
        draw(context, items, ...) {
            this.items = items;
            var count = 0;
            const that = this;
            this.items.some(function (elem) {
                :
                :
                that.drawA(context, ...);
                count++;
                return false;
            });
            :
        }
    ```

    この例だとわかりやすいのですが、モジュール化した後の公開するメソッド内でsome関数を使っています。さらに、some関数のパラメーターの無名関数内で、同クラスの別メソッドをコールしています。通常、thisを使うとsome関数のドット演算子の手前のオブジェクトがthisに当てられたかと思います（？）それだと、クラスのメソッドにアクセスできないので、some関数の外でthat変数を定義してやり、thisで初期化します。そして、some関数の中でそれを使ってやればよいのです。

1. ファイルの終わりに特殊な定義を追記する

    参考：http://dotnsf.blog.jp/archives/1080323559.html
    最後に、ファイル末尾に3行追記すればモジュール化は完了です。

    ```js/module.js
        class Module {
            temp;
            add(a, b) {
                return a + b;
            }
            subtract(a, b) {
                return a - b;
            }
            multiply(a, b) {
                return a * b;
            }
            divide(a, b) {
                return a / b;
            }
            push(val) {
                temp = val;
            }
            pop() {
                return temp;
            }
        }
        
    +   if (typeof module === 'object') {
    +       module.exports = Module;
    +   }
    ```

#### 

## Javascriptモジュールを利用する側 (Blazor WebAssembly Client プロジェクト) のコードの書き方

```index.html
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>BlazorApp1</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
        <script type="text/javascript" src="js/module.js"></script>
+       <script type="text/javascript">
+           var singletonObj;
+           function instanciate() {
+               if (singletonObj == null) {
+                   singletonObj = new Module();
+               }
+               return singletonObj;
+           }
+       </script>
    </head>
    
    <body>
        <div id="app">
            <svg class="loading-progress">
                <circle r="40%" cx="50%" cy="50%" />
                <circle r="40%" cx="50%" cy="50%" />
            </svg>
            <div class="loading-progress-text"></div>
        </div>
    
        <div id="blazor-error-ui">
            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>
```
Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsyncメソッドを使用したいので、別途Nugetで、Microsoft.JSInterop をインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/def2679e-3e19-fc92-48f8-9f5472bd9b9a.png)

あと一息です！！！

```cs:Index.razor
    ﻿@page "/"
    @inject IJSRuntime JsRuntime;
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <p>1 + 1 = @sum</p>
    <p>6 - 2 = @difference</p>
    <p>11.1 * 3 = @product</p>
    <p>33.3 / 3 = @quotient</p>
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    @code {
+       private IJSObjectReference? _jsClass;
        private double sum;
        private double difference;
        private double product;
        private double quotient;
        protected override async Task OnInitializedAsync()
        {
-           sum = await JsRuntime.InvokeAsync<double>("add", 1, 1);
-           difference = await JsRuntime.InvokeAsync<double>("subtract", 6, 2);
-           product = await JsRuntime.InvokeAsync<double>("multiply", 11.1, 3);
-           quotient = await JsRuntime.InvokeAsync<double>("divide", 33.3, 3);
+           _jsClass = await JsRuntime.InvokeAsync<IJSObjectReference>("instanciate");
+           sum = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "add", 1, 1);
+           difference = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "subtract", 6, 2);
+           product = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "multiply", 11.1, 3);
+           quotient = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "divide", 33.3, 3);
            await base.OnInitializedAsync();
        }
    }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4734/29419a84-b8e2-266d-317d-23aedf053f7e.png)

できました！！！

## 終わりに

ここまで読んでくださった方は、「モジュール化するのはいいんだけど、ユニットテストにはしないの？？？」と疑問を持つ方もいると思います。なので、次回の記事で「Mochaを使ったユニットテストプロジェクトを作成する」を書きたいと思います。