Rust+Viteでwasmを動かすところ

Viteで用意したフロントからwasmを動かすところまで。

環境

  • rustc: 1.69.0
  • wasm-pack: 0.11.0
  • vite: 4.3.5

Rust側の準備

cargo newでプロジェクトを作成する。 src/lib.rsを作成し、最低限以下のコードを書いておく。

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;

use winit::event::{Event, WindowEvent};

#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
pub async fn run() {
   #[cfg(not(target_arch = "wasm32"))]
   {
       env_logger::init();
   }
   #[cfg(target_arch = "wasm32")]
   {
       std::panic::set_hook(Box::new(console_error_panic_hook::hook));
       console_log::init().expect("could not initialize logger");
   }

   let event_loop = winit::event_loop::EventLoop::new();
   let window = winit:🪟:WindowBuilder::new()
       .with_title("Your Project")
       .build(&event_loop)
       .unwrap();

   #[cfg(target_arch = "wasm32")]
   {
       use winit::platform::web::WindowExtWebSys;

       web_sys::window()
           .and_then(|win| win.document())
           .and_then(|doc| {
               let dst = doc.get_element_by_id("wasm-canvas")?;
               let canvas = web_sys::Element::from(window.canvas());
               dst.append_child(&canvas).ok()?;
               Some(())
           })
           .expect("Could not get canvas element");
   }

   event_loop.run(move |event, _, control_flow| {
       *control_flow = winit::event_loop::ControlFlow::Poll;
       match event {
           Event::WindowEvent {
               ref event,
               window_id,
           } if window_id == window.id() => match event {
               WindowEvent::CloseRequested => *control_flow = winit::event_loop::ControlFlow::Exit,
               _ => {}
           },
           _ => {}
       }
   });
}

ここまででcargo runを実行すると、真っ黒なWindowが表示される。

React側の準備

以下のコマンドを実行し、viteでReact+Typescript+SWCのプロジェクトを作成する。

yarn create vite

プロンプトの選択肢はReactTypescript + SWCを選択する。

viteで作成されたデフォルトのmain.tsxでは<React.strictMode>が使われているので、これを削除しておく。 React.strictModeでは副作用のチェックのために2回レンダリングされる様子。 そのため、wasmの初期化処理が2回呼ばれてしまい、エラーになる。

import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <App />
);

App.tsxも以下のように書き換える。 pkg/project.jsは後述のwasm-pack buildで生成されるファイル名を指定する。

import { useEffect } from "react";
import "./App.css";
import init from "../pkg/project.js";

function App() {
  useEffect(() => {
    init().then(() => {
      console.log("WASM initialized");
    });
  });

  return <div id="wasm-canvas"></div>;
}

export default App;

wasmのビルド&実行

以下のコマンドを実行し、wasmをビルドする。

wasm-pack build --target web --out-dir ./path/to/vite-project/pkg

viteのプロジェクト内でyarn devを実行すると、真っ黒なcanvasが表示される。