React+TypeScriptで、Emscripten製jsモジュールを実行してみた
Abstract
前回の記事では、App.tsxから直接wasmを読込んで実行した。
けど、規模が大きいプロブラムだと、この方法は相当めんどい。
で、実は、Emscriptenはビルドするとき*.jsも一緒に出力してくれてて、それを使わない手はない。全然楽。
ということで、今回は、Emscripten製jsを、TypeScriptで読み込んで実行してみた。
結論
今回の成果物はココ↓
前提
- windowsだよ。
- React+Typescriptの開発環境は構築済 [環境構築]WindowsにVSCode+React+TypeScriptの開発環境を構築してみた。
- ソースは、前回プロジェクトReactTs-WebAsm002から。
- ↑hello.wasmとhello.jsはビルド出力済。
手順
0. 不要ファイルの削除
- src/asm-cpp/helloif.d.ts
- src/asm-cpp/helloif.js
今回は使わないので削除
- 3: import { helloif } from './asm-cpp/helloif';
今回は使わないので削除
1. Emscripten製jsの読込み
1-1. Emscriptenでビルド出力したhello.jsのオブジェクトModuleをexportにする。
- 15: var Module = typeof Module != 'undefined' ? Module : {};
+ 15: export var Module = typeof Module != 'undefined' ? Module : {};
1-2. hello.wasmの配置場所に修正。
- 715: wasmBinaryFile = 'hello.wasm';
+ 715: wasmBinaryFile = 'wasm/hello.wasm';
1-3. App.tsxでhello.jsを読み込む
- 3: import { helloif } from './asm-cpp/helloif';
+ 3: import * as hWasm from './asm-cpp/hello';
4:
5: function App() {
6: const [count, setCount] = useState(0)
7:
+ 8: console.log("hWasm.Module add---", hWasm.Module.__Z3addii(11, 22));
+ 9: console.log("hWasm.Module sub---", hWasm.Module.__Z3subii(11, 22));
1-4. この状態で実行すると下記エラーがでる!!
fsがない。pathがない。
Compiled with problems:
ERROR in ./src/asm-cpp/hello.js 75:11-24
Module not found: Error: Can't resolve 'fs' in 'D:\Products\React.js\ReactTs-WebAsm004\src\asm-cpp'
ERROR in ./src/asm-cpp/hello.js 76:17-32
Module not found: Error: Can't resolve 'path' in 'D:\Products\React.js\ReactTs-WebAsm004\src\asm-cpp'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
1-5. エラー解決のためpackage.jsonに下記を追加
たぶん、osは余計な気がする。
"dependencies": {
~略~
},
+ "browser": {
+ "fs": false,
+ "os": false,
+ "path": false
+ },
1-6. この状態で実行すると下記エラーがでる!!
hWasm.Module初期化前に、_Z3addiiを呼んでる!!
Aborted(Assertion failed: native function `_Z3addii` called before runtime initialization)
RuntimeError: Aborted(Assertion failed: native function `_Z3addii` called before runtime initialization)
at abort (http://localhost:3000/static/js/bundle.js:704:11)
at assert (http://localhost:3000/static/js/bundle.js:450:5)
at Object.__Z3addii (http://localhost:3000/static/js/bundle.js:768:5)
at App (http://localhost:3000/static/js/bundle.js:32:86)
at renderWithHooks (http://localhost:3000/static/js/bundle.js:21386:22)
at mountIndeterminateComponent (http://localhost:3000/static/js/bundle.js:24670:17)
at beginWork (http://localhost:3000/static/js/bundle.js:25966:20)
at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:10982:18)
at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:11026:20)
at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:11083:35)
1-7. App.tsxで読み込む
requireで読み込む様に修正。
- 1: import React, { useState } from 'react'; // useState()は使わない。
- 1: import React, { useRef } from 'react'; // useRef()を使う。
- 3: import * as hWasm from './asm-cpp/hello'; // 追加したけど削除。requireで読込む。
3:
4: function App() {
- 5: const [count, setCount] = useState(0) // useState()は使わない。
+ 5: const refWasm = useRef<any>() // useRef()を使う。
6:
+ 7: const hWasm = require('./asm-cpp/hello.js');
+ 8: hWasm.Module.onRuntimeInitialized = () => {
+ 9: console.log("hWasm.Module add---", hWasm.Module.__Z3addii(11, 22));
+ 10: console.log("hWasm.Module sub---", hWasm.Module.__Z3subii(11, 22));
+ 11: refWasm.current = hWasm.Module
+ 12: }
7行目のrequireで、Emscripten製jsモジュール(hello.js)を読み込む。
8行目の関数で、初期化完了を待って、戻りをrefWasmに設定する。以降は、refWasmで操作できるようになる。
2. この状態で実行すると下記エラーがでる!!
eslintエラーs
ERROR in [eslint]
src\App.tsx
Line 7:16: Require statement not part of import statement @typescript-eslint/no-var-requires
src\asm-cpp\hello.js
Line 80:12: Require statement not part of import statement @typescript-eslint/no-var-requires
Line 81:18: Require statement not part of import statement @typescript-eslint/no-var-requires
Line 145:13: 'read' is not defined no-undef
Line 150:29: 'readbuffer' is not defined no-undef
Line 152:16: 'read' is not defined no-undef
Line 162:39: Unexpected empty arrow function @typescript-eslint/no-empty-function
Line 171:18: 'scriptArgs' is not defined no-undef
Line 173:18: 'arguments' is not defined no-undef
Line 195:9: 'quit' is not defined no-undef
Line 203:40: Read-only global 'console' should not be modified no-global-assign
Line 205:125: 'printErr' is not defined no-undef
Line 567:10: Unexpected constant condition no-constant-condition
Line 890:7: Expected to return a value in method 'get' getter-return
Line 966:7: Expected to return a value in method 'get' getter-return
Line 1015:7: Expected a 'break' statement before 'case' no-fallthrough
Line 1045:7: Expected a 'break' statement before 'case' no-fallthrough
eslintが出力するエラーを無視する。
2-1. Require statement not part of import statement @typescript-eslint/no-var-requires
"rules": {
+ "@typescript-eslint/no-var-requires": "off",
}
2-2. 'xxx' is not defined no-undef
+ "globals": {
+ "read": false,
+ "readbuffer": false,
+ "scriptArgs": false,
+ "arguments": false,
+ "quit": false,
+ "printErr": false,
+ },
"rules": {
2-3. Unexpected empty arrow function @typescript-eslint/no-empty-function
"rules": {
"@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/no-empty-function": "off",
}
2-4. Read-only global 'console' should not be modified no-global-assign
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
+ "no-global-assign": "off",
}
2-5. Unexpected constant condition no-constant-condition
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"no-global-assign": "off",
+ "no-constant-condition": ["error", { "checkLoops": false }],
}
2-6. Expected to return a value in method 'get' getter-return
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"no-global-assign": "off",
"no-constant-condition": ["error", { "checkLoops": false }],
+ "getter-return": "off",
}
2-7. Expected a 'break' statement before 'case' no-fallthrough
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"no-global-assign": "off",
"no-constant-condition": ["error", { "checkLoops": false }],
"getter-return": "off",
+ "no-fallthrough": "off",
}
修正完了。
- 実行
出来た!!
見た目地味だけど、WebAssemblyが実現できてるぞ!!
Wasm.ModuleのAny型になるので、扱いにくいかな~と思ったけど、そうでもなくって。
型定期ファイル(.d.ts)使うのは、困ってからでいいや。
次は、OpenCVのWebAssemblyをやる!!
function addRunDependency(id) {
runDependencies++;
Module["monitorRunDependencies"] ? .(runDependencies);
if (id) {
assert(!runDependencyTracking[id]);
runDependencyTracking[id] = 1;
if (runDependencyWatcher === null && typeof setInterval != "undefined") {
runDependencyWatcher = setInterval(() => {
if (ABORT) {
clearInterval(runDependencyWatcher);
runDependencyWatcher = null;
return
}
var shown = false;
for (var dep in runDependencyTracking) {
if (!shown) {
shown = true;
err("still waiting on run dependencies:")
}
err(dependency: ${dep}
)
}
if (shown) {
err("(end of list)")
}
}, 1e4)
}
} else {
err("warning: run dependency added without ID")
}
}
Discussion