LoginSignup
8
9

More than 3 years have passed since last update.

wasm-pack + WebWorker + react-scripts

Posted at

前提

  • 重い計算を 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 を使う。

worker-loader では WebWorker から WebAssembly を import できなかったので、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.jsonscripts を以下のように書いておく。

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;

実用例

凸包を計算して描画するプログラムを書いてみた。

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9