create-react-appで作ったプロジェクトにwasm-pack+wasm-bindgenで生成したwasmのパッケージを入れようとしたらハマった

May 17, 2022

create-react-app(versionは5.0.0)で作成したreactのプロジェクトに、wasm-pack(wasm-bindgenを使用)で生成したパッケージを組み込もうとしたら結構はまってしまいました。具体的には、reactのソースからパッケージをimportしたコードをnpm run buildすると”Can’t import the named export ‘memory’.’buffer’ (imported as ‘wasm’) from default-exporting module (only default export is available)”というエラーが出てしまい、ビルドできない状況になりました。

調査した結果から書きますと、解決方法は2種類ありました。一つは、webpackの設定を変更しない方法、もう一つは、webpackの設定を変更する方法です。

前者は、

wasm-pack build --target web

のようにオプション(詳細はこちら)を指定してビルドして、reactのコンポーネントからは

import init, { add } from "wasm-lib";

function App() {
  // 略
  const [ans, setAns] = useState(0);
  useEffect(() => {
    init().then(() => {
     setAns(add(1, 1)); // addはwasmの関数。initが終わったらansにadd(1,1)の値がセットされる
    })
  }, []);
  // 略

のようにinitを呼んでからwasmの関数を使用する方法です。webpackの設定はそのまま(create-rust-appが生成したもの)でOKですが、initを呼び出す手間が増えます。

create-react-app(version5.0.0)が使用するwebpackのバージョンは5なので、まだexperimentalではありますが、wasmの各種ロード(同期,非同期)にも対応しているのですが、この方法ではその機能を使用せずにwasm-bindgenで生成するコードでロードすることになります。

後者は、webpack5のwasmのロード機能を使用するため、jsからは通常のwasmのパッケージ(ビルド時に–target webは不使用)をimportするだけでwasmを利用できますが、webpackの設定をカスタマイズする必要があるため少し手間がかかります。まず、react-app-rewired(参考ページ)というパッケージをnpmでインストールして、プロジェクトのルートに以下のようなconfig-overrides.jsというファイルを作成します。

module.exports = function override(config, env) {
  const experiments = config.experiments || {};
  config.experiments = {
		...experiments,
    asyncWebAssembly: true,
    syncWebAssembly: true,
    topLevelAwait: true,
  };
  
  const wasmExtensionRegExp = /\.wasm$/;
  config.resolve.extensions.push('.wasm');

  config.module.rules.forEach((rule) => {
    (rule.oneOf || []).forEach((oneOf) => {
      if (oneOf.type === "asset/resource") {
        oneOf.exclude.push(wasmExtensionRegExp);
      }
    });
  });
            
  return config;
}

さらに、ルートフォルダのpackage.jsonのscriptを書き換えます。

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },

これでnpm run buildを実行すると、ビルドは通りました。

(22/6/25追記)
後者の方法はwasmからのconsole.logの呼び出しができなかったり、idによるエレメントの取得ができない、という現象が出ました。前者だとこれらの現象は出なかったので、もしこれらの機能を使うのであれば、後者の方法はやめたほうがよさそうです。