1. 始めに
タイトルの通り、Angularで実装した処理からWebAssemblyで実装した関数を呼び出してみました。
AngularはTypeScriptでの開発が推奨されているため、WebAssemblyのホームページに記載されているサンプルコードをそのままコピペするだけでは動かず、細工が必要でした。
今回はその一連の作業をまとめてみました。
2. Angularのサンプルアプリを作成
まずはAngularの環境を構築します。Angular CLIのインストールは既に完了しているものとして、コマンドラインで以下を実行してワークスペースを作成します。
ng new Application
コマンドラインでApplicationフォルダに移動し、以下のコマンドを実行してアプリが起動することを確認します。この「Hello, Application」と書かれている部分を、WebAssemblyで実装した関数から取得した値で書き換えます。
ng serve --open
ちなみに、Application/src/app/app.component.tsを見てみると以下のようになっています。
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'Application';
}
3. WebAssemblyのバイナリファイル作成
Emscriptenを使ってC++のコードから.wasmファイル及びそれのローダーとなる.jsファイルを作成します。
3.1 ビルド環境構築
公式サイトを読んで、コマンドラインでセットアップを行います。
3.2 WASM作成の元となるソースコードを準備
C++で書いた、以下のソースファイルを準備します。
#include <emscripten/emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int add(int x, int y) {
return x + y;
}
}
ここで、以下の点に注意する必要があります。
- 呼び出す関数の前に、EMSCRIPTEN_KEEPALIVEを書いておきます
- 呼び出す関数にはCリンケージを指定します。こうしないとC++の機能で名前マングリングが行われてしまいます
呼び出す関数のexport方法については、こちらの記事を参考にしました。
Emscriptenを使ったC/C++の関数のエキスポート方法まとめ
3.3 Emscriptenを使ってwasmへコンパイル
コマンドラインから以下を実行します。その結果、test.cppと同じフォルダにtest.wasmとtest.jsが生成されます。test.wasmの読み込みはtest.jsがやってくれるので、test.jsを参照するラッパーをTypeScriptで実装していきます。
em++ test.cpp -o test.wasm -o test.js -sMODULARIZE=1 -sEXPORT_NAME=WASM --no-entry
オプションの指定については、こちらの記事を参考にしました。
Emscriptenを使ってブラウザでWebAssemblyを動かす
4. AngularアプリからWebAssemblyの関数を呼び出す
ここからが本番です。
4.1 Emscriptenで生成したファイルを配置
Applicationフォルダにwasmフォルダを追加し、そこに生成した2つのファイルを置きます。そして、これらを利用できるようにするため各種設定ファイルを更新します。
angular.jsonのassetsに以下の設定を追記します。これにより、wasmフォルダの中身がそのままWebアプリの方へコピーされます。
{
"glob": "**/*",
"input": "wasm"
}
package.jsonのルート直下に以下の設定を追記します。これによりEmscripten生成の.jsコードの中のNode依存のコードを無視するようになります。
"browser": {
"fs": false,
"path": false
}
tsconfigのcompilerOptionsに以下の設定を追記します。これによりTypeScriptのソースコードの中からtest.jsを参照できるようになります。
"baseUrl": "src",
"paths": {
"wasm/*": ["../wasm/*"]
}
4.2 型情報の定義ファイルを追加
srcの直下にtypesフォルダを作成し、wasm.d.tsを追加します。Emscriptenが生成するのは型情報の無い.jsファイルなので、型情報を定義する.d.tsファイルを自作する必要があります。ここで、関数名が"_add"になっていますが、Emscriptenで生成された.jsでは関数名の先頭に"_"が付くため、このような記載になっています。
declare module 'wasm/test.js' {
const WASM: () => Promise<{
_add: (a: number, b: number) => number;
}>;
export default WASM;
}
4.3 wasm読み込みクラスを作成
srcの直下にwasmフォルダを作成し、wasm.tsを追加します。
import WASM from 'wasm/test.js'
export class WasmWrapper {
private instance: Awaited<ReturnType<typeof WASM>> | undefined = undefined;
public load(): Promise<void> {
if (this.instance) {
return Promise.resolve();
}
return WASM().then((result) => {
this.instance = result;
});
}
public add(a: number, b: number): number {
let result: number = 0;
if (this.instance) {
result = this.instance._add(a, b);
}
return result;
}
}
4.4 WebAssemblyの関数を呼び出す
上記で作成したwasm.tsを使用し、WebAssemblyの関数を呼び出します。
import { WasmWrapper } from '../wasm/wasm';
// 省略
export class AppComponent {
title = 'Application';
wasm = new WasmWrapper();
constructor() {
this.wasm.load().then(() => {
this.title = '1 + 2 = ' + this.wasm.add(1, 2);
});
}
}
4.5 アプリ実行
この状態でアプリを実行すると、以下のように表示されます。
「Hello, Application」が「Hello, 1 + 2 = 3」に変わっていることが分かります。

最後に
というわけで、AngularからWebAssemblyで実装した関数を呼び出してみました。
どちらもモダンなWeb技術なので、使いこなせるようになっていきたいです。
