1. _likr

    Posted

    _likr
Changes in title
+wasm-pack + WebWorker + react-scripts
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,118 @@
+# 前提
+
+- 重い計算を Rust + WebAssembly でやる
+- WebAssembly のパッケージは wasm-pack でビルドする
+- WebAssembly は WebWorker で動かす
+- アプリは React で書く
+- アプリの設定を頑張りたくないので react-scripts (create-react-app) を使う
+
+# react-app-rewired
+
+react-scripts では、WebWorker と WebAssembly のロードができないので設定する必要がある。
+react-scripts で eject せずに設定を追加するために [react-app-rewired](https://github.com/timarney/react-app-rewired) を使う。
+
+[worker-loader](https://github.com/webpack-contrib/worker-loader) では WebWorker から WebAssembly を import できなかったので、[workerize-loader](https://github.com/developit/workerize-loader) を使う。
+
+こんな感じで `config-overrides.js` を書く。
+
+```config-overrides.js
+const path = require("path");
+
+module.exports = function override(config, env) {
+ config.module.rules.push({
+ test: /\.worker\.js$/,
+ use: { loader: "workerize-loader" },
+ });
+
+ const wasmExtensionRegExp = /\.wasm$/;
+
+ config.resolve.extensions.push(".wasm");
+
+ config.module.rules.forEach((rule) => {
+ (rule.oneOf || []).forEach((oneOf) => {
+ if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
+ // Make file-loader ignore WASM files
+ oneOf.exclude.push(wasmExtensionRegExp);
+ }
+ });
+ });
+
+ // Add a dedicated loader for WASM
+ config.module.rules.push({
+ test: wasmExtensionRegExp,
+ include: path.resolve(__dirname, "src"),
+ use: [{ loader: require.resolve("wasm-loader"), options: {} }],
+ });
+
+ return config;
+};
+```
+
+これで拡張子が `.worker.js` のファイルを WebWorker として読み込めるようになる。
+
+`package.json` の `scripts` を以下のように書いておく。
+
+```package.json
+ "scripts": {
+ "build": "react-app-rewired build",
+ "start": "react-app-rewired start"
+ },
+```
+
+
+
+# WebWorker の実装
+
+こんな感じで WebWorker を実装する。
+
+```example.worker.js
+export const twice = async (v) => {
+ const { twice } = await import("example");
+ return twice(v);
+};
+
+```
+
+`example` は wasm-pack で作られたパッケージで、dynamic import で読み込む。
+
+workerize-loader では、WebWorkerで呼び出せる関数を named export する。
+async/await が使える。
+
+# アプリの実装
+
+いい感じにアプリ側を実装する。
+workerize-loader で import したモジュールは関数になっていて、呼び出すと関数を取り出すことができる。
+関数は Promise を返すようになっている。
+
+```App.js
+import React, { useState } from "react";
+import worker from "./hoge.worker";
+
+const { twice } = worker();
+
+const App = () => {
+ const [value, setValue] = useState(1);
+ return (
+ <div>
+ <button
+ onClick={() => {
+ twice(value).then((result) => {
+ setValue(result);
+ });
+ }}
+ >
+ click me
+ </button>
+ <p>{value}</p>
+ </div>
+ );
+};
+
+export default App;
+```
+
+# 実用例
+
+凸包を計算して描画するプログラムを書いてみた。
+
+https://likr-sandbox.github.io/convex-hull/