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によるエレメントの取得ができない、という現象が出ました。前者だとこれらの現象は出なかったので、もしこれらの機能を使うのであれば、後者の方法はやめたほうがよさそうです。