Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

wasm-pack + WebWorker + react-scripts

前提

  • 重い計算を 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;

実用例

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

https://likr-sandbox.github.io/convex-hull/

_likr
はたらきたくない
https://vdslab.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away