Angularからwasm-bindgenで作成したパッケージを読み込む
はじめに
wasm-bindgen等のライブラリ・ツールの整備によって、Rustで書いたプログラムをWebフロントエンドで動かすことが現実味を帯びてきました。
しかし、それらを本格的なWebフロントエンドで活用するためには、Angular等のWebフロントエンドフレームワークと共存させる必要があるでしょう。
本稿では、wasm-bindgenでマンデルブロ集合の記事で作成したmandelbrotパッケージを例に、angular-cli(バージョン7.0)で作成したAngularプロジェクトからWebAssemblyのコードを読み込んで動かす際の注意点をご紹介します。
準備
$ ng new angular-example
$ cd angular-example
$ npm i path/to/mandelbrot/pkg
コンポーネントの実装
mandelbrotパッケージを動かすのに必要な最小限の部分をコンポーネントに実装します。
必要に応じてモジュール化を行なってください。
import { AfterViewInit, Component, ViewChild } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
@ViewChild('display') display;
ngAfterViewInit () {
const mod = import('mandelbrot')
const bg = import('mandelbrot/mandelbrot_bg')
Promise.all([mod, bg]).then(([{ mandelbrot, Screen }, { memory }]) => {
const canvas = this.display.nativeElement;
const screen = new Screen(canvas.width, canvas.height);
const bytes = new Uint8ClampedArray(memory.buffer, screen.pointer(), screen.size());
const image = new ImageData(bytes, screen.width, screen.height);
mandelbrot(screen, -3, -2, 0.01, 100);
const ctx = canvas.getContext('2d');
ctx.putImageData(image, 0, 0);
})
}
}
<canvas #display width="600" height="400"></canvas>
この段階でビルドを行うと以下のようにエラーが出ているかと思います。
ERROR in src/app/app.component.ts(12,17): error TS1323: Dynamic import is only supported when '--module' flag is 'commonjs' or 'esNext'.
src/app/app.component.ts(13,16): error TS1323: Dynamic import is only supported when '--module' flag is 'commonjs' or 'esNext'.
src/app/app.component.ts(13,23): error TS2307: Cannot find module 'mandelbrot/mandelbrot_bg'.
これを解消していきます。
Dynamic importの有効化
まず、エラーメッセージの通りDynamic importを有効化するために、tsconfig.json を以下のように編集します。
@@ -5,7 +5,7 @@
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
- "module": "es2015",
+ "module": "esNext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
再度ビルドを行うと以下のようにエラーが減っていることが確認できます。
ERROR in src/app/app.component.ts(13,23): error TS2307: Cannot find module 'mandelbrot/mandelbrot_bg'.
bgモジュールの型付け
[追記] mandelbrot_bg.d.ts を生成するようにwasm-bindgenにパッチが入ったので今後のリリースでは起こらなくなるかと思われます
残りのエラーは、mandelbrot/mandelbrot_bg の型定義(mandelbrot_bg.d.ts)が存在しないために発生しています。
回避方法はいくつか考えられますが、mandelbrotパッケージの方であらかじめ作成しておくのが簡単かと思います。
※ wasm-bindgenにissueを上げたら、wasm-bindgen側で生成するように対応してくれそうです。
mandelbrotパッケージ側で以下のファイルを追記します。
import 'webassembly-js-api';
export var memory : WebAssembly.Memory;
また、依存パッケージを追加しておきます。
$ npm i @types/webassembly-js-api
Angular側に戻ってビルドをすると以下のようなエラーに代わりました。
ERROR in ../mandelbrot/pkg/mandelbrot.js
Module not found: Error: Can't resolve './mandelbrot_bg' in '/Users/likr/src/ionic20181122demo/mandelbrot/pkg'
ERROR in ./src/app/app.component.ts
Module not found: Error: Can't resolve 'mandelbrot/mandelbrot_bg' in '/Users/likr/src/ionic20181122demo/angular-example/src/app'
.wasmファイルのimport
'mandelbrot/mandelbrot_bg'の拡張子は.wasmとなっていて、今度は.wasm拡張子のファイルをimportできないことでエラーが発生しています。
通常のwebpack環境であれば.wasm拡張子のファイルをimportできるのですが、angular-cliの環境ではそれができなくなっています。
@angular-builders/custom-webpackを使って、angular-cli標準のwebpack設定を拡張しましょう。
まず必要なパッケージのインストールを行います。
$ npm i -D @angular-builders/custom-webpack @angular-builders/dev-server
拡張する設定を記述するためにextra-webpack.config.jsを以下のように作成します。
module.exports = {
resolve: {
extensions: ['.wasm']
}
}
@angular-builders/custom-webpackを使用するようにangular.jsonを編集します。
@@ -11,8 +11,12 @@
"schematics": {},
"architect": {
"build": {
- "builder": "@angular-devkit/build-angular:browser",
+ "builder": "@angular-builders/custom-webpack:browser",
"options": {
+ "customWebpackConfig": {
+ "path": "./extra-webpack.config.js"
+ },
+ "showCircularDependencies": false,
"outputPath": "dist/angular-example",
"index": "src/index.html",
"main": "src/main.ts",
@@ -55,7 +59,7 @@
}
},
"serve": {
- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular-builders/dev-server:generic",
"options": {
"browserTarget": "angular-example:build"
},
@@ -72,8 +76,11 @@
}
},
"test": {
- "builder": "@angular-devkit/build-angular:karma",
+ "builder": "@angular-builders/custom-webpack:karma",
"options": {
+ "customWebpackConfig": {
+ "path": "./extra-webpack.config.js"
+ },
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
これで無事に ng build と ng serve がエラーなく通るようになりました。
プロダクションビルド
まだ ng build --prod したときにエラーが発生してしまいます。
70% finish module graph WasmFinalizeExportsPlugin/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js:33
const importedNames = ref.importedNames;
^
TypeError: Cannot read property 'importedNames' of null
at compilation.hooks.finishModules.tap.modules (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js:33:36)
at SyncHook.eval [as call] (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:16:1)
at SyncHook.lazyCompileHook (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/Hook.js:154:20)
at Compilation.finish (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:1133:28)
at hooks.make.callAsync.err (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compiler.js:545:17)
at _done (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:9:1)
at _err1 (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:36:22)
at _addModuleChain (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:1065:12)
at processModuleDependencies.err (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:981:9)
at process._tickCallback (internal/process/next_tick.js:61:11)
現行angular-cliでインストールされる @angular-devkit/build-angular@0.10 が依存している webpack@4.19.1 は、プロダクションビルドで.wasmファイルを読み込む際にエラーを起こしてしまいます。
次期リリースの@angular-devkit/build-angular@0.11 では、webpackが修正済みバージョンとなっているので、以下のコマンドで @angular-devkit/build-angular を更新します。
$ npm i @angular-devkit/build-angular@next
これについては時間が解決してくれるでしょう。
おわりに
紆余曲折ありましたが、なんとかAngularプロジェクト内からwasm-bindgenで生成したパッケージを読み込むことができました。
angular-cli標準の設定から変更しなければならない部分があるという点にはご注意ください。
ちなみにIonic v4でも同様の手順でWebAssemblyのパッケージを読み込むことができます。