💡

WASMをNext.jsで動かす

2022/06/21に公開

先日Scrapbox記法をMarkdownに変換するライブラリをRustで書いたという記事を書いた。

このライブラリは当初CLIとして使うことを想定していた。しかしせっかくRustで書いたのでWASMを生成してフロントエンドから動かせるようにしてみた。できたのは下記のページ。
https://razokulover.com/playgrounds/sb2md

Scrapboxのテキストを入力して変換するボタンを押すとMarkdownが生成される。

今回はこれを作る過程でNext.jsでWASMを動かすというのができたので大まかなやり方を書き残しておく。ざっくりとした手順としては下記。

  • WASMファイルをNext.js内に配置
  • next.config.jsの設定
  • dynamic importを使ってWASMを読み込む

実際にやってみるとわかるがとても簡単。

WASMファイルの配置

Rustからwasmの生成にはwasm-packを使った。これを使うとbg.wasmとかbg.wasm.d.tsなどのファイルが生成される。RustからWASMを生成する方法についてはCompiling from Rust to WebAssembly - WebAssembly | MDNあたりの記事を読んでみると良い。

wasm-packでwasm-pack build --target web --releaseをして生成したファイル群は下記のようになっているはず。

 ├── package.json
 ├── sb2md_converter.d.ts
 ├── sb2md_converter.js
 ├── sb2md_converter_bg.wasm
 └── sb2md_converter_bg.wasm.d.ts

これら全てをNext.jsのソースコードのルートに配置する。自分の場合はsrc/の中にresources/wasm/というディレクトリを作ってファイルを丸ごと入れた。こんな感じ→https://github.com/YuheiNakasaka/personal-site/tree/master/src/resources/wasm

next.config.jsの設定

次にwebpackの設定。webpack5から導入された実験的機能の中にWASMファイルを同梱してくれるオプションがあるのでそれを使う。

next.config.jsにはこんな感じで書いておけばOK。

 module.exports = {
   webpack: (config, { isServer }) => {
     config.experiments = {
    	  asyncWebAssembly: true,
       layers: true,
     };
     config.output.webassemblyModuleFilename = (isServer ? "../" : "") + "static/wasm/[modulehash].wasm";
     return config;
   }
 }

dynamic import

実際にWASMを読み込んで呼び出して使ってみる。これにはいくつかのやり方があると思うが、dynamic importを使う方法が1番楽そうだった。

 import { NextPage } from "next";
 import { useCallback, useEffect } from "react";
 import dynamic from "next/dynamic.js";
 
 const WasmSample = dynamic({
   loader: async () => {
     const initWasm = await import("../../resources/wasm");
     const { Greeting } = initWasm;
     const onClick = useCallback(async () => {
       const result = await Greeting.hello()
       console.log(result)
     }, []);
     
     useEffect(() => {
       initWasm.default();
     }, [])
     
     return () => {
       return (
         <button onClick={onClick}>実行する</button>
       )
     }
   }
 }, { ssr: false })
 
 const Example: NextPage = () => {
	  return <WasmSample />
 }

dynamic importで{ ssr: false }を設定するのがポイント。

あとwasmファイルの中身にもよるんだけど上記の例はGreetingstructにhelloメソッドを生やしたRustのコードをWASM化した場合の例を書いてみてる。細かい部分は元のRustファイル側の実装による。なのであくまで参考までに。

ちなみにScrapboxのテキストをMarkdownに変換するWASMを実行してるコンポーネントのコードの例は下記。大枠は同じ。
personal-site/sb2md.tsx at master · YuheiNakasaka/personal-site

まとめ

Next.jsでWASMファイルを読み込んで呼び出して使う方法を書いた。一度動かせるようになってしまえば今後は雑にRustでコードを書きWASMを作ってフロントエンドから気軽に使えるようになるはず。

目下の課題としては生成されるWASMファイルが機能の割にデカすぎるという問題があったり、V8が強力すぎてWASMの出番ない?みたいなレベルの処理もあったりするから何でもかんでも本番導入できるという感じではない。

今回作ったScrapbox to Markdownも計算量の多いようなものでもないからJSで書いても十分な速度は出る。

が、まぁ面白かったのでいいじゃんガハハというノリでやった。

Discussion