色々やったら手数が多くてややこしかったので、現時点のセットアップをメモがてら記事に書くことにした。
想定読者は フロントエンドプログラマ or Rust プログラマ。両方詳しいことは想定していないので、それぞれで自明なこともみっちり書くことにした。
node/npm と rust/cargo (rustup) のセットアップは略。ググればいくらでも出てくるので…
概要
- webpack: 静的アセットを生成するバンドラー
- ts-loader: webpack で
.ts
ファイルを typescript として読み込む loader。wasm-pack が typescript の型定義を生成するので、せっかくなので ts もセットアップする - @wasm-tool/wasm-pack-plugin: webpack 内で wasm-pack で生成されるコードを読み込むもの
- html-webpack-plugin: webpack で index.html を生成するプラグイン
- ts-loader: webpack で
- webpack-dev-server: 開発用サーバー
- rust
- wasm-pack: rust プロジェクトを js で読み込みやすい wasm にコンパイルする
- wasm-bindgen:
rust <-> js
のデータ構造をバインディングするライブラリ。これを使わないと数値と ArrayBuffer しか受け渡せないのでほぼ必須
とりあえず動かしてみたい人用
$ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
$ git clone git@github.com:mizchi-sandbox/wasm-playground.git
$ cd wasm-playground
$ yarn install
# dev server
$ yarn dev
# bundle static
$ yarn build # => dist
webpack プロジェクトのセットアップ
webpack + typescript のセットアップ
※ webpack 詳しくない人向け。フロントエンドの人にとってはいつものやつなので、飛ばして大丈夫
$ npm i -g yarn
$ mkdir wasm-playground
$ cd wasm-playground
$ yarn init -y
$ yarn add -D webpack webpack-cli webpack-dev-server typescript ts-loader html-webpack-plugin
yarn 使わない人は npm install -D/npx
で読み替えてください。
tsconfig.json
を編集
{
"compilerOptions": {
"target": "es2018",
"module": "esNext",
"strict": true,
"strictNullChecks": true,
"moduleResolution": "node",
"noEmit": true,
"esModuleInterop": true
}
}
src/index.ts
console.log("hello");
src/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>wasm-playground</title>
</head>
<body>
<h1>Wasm Playground</h1>
</body>
</html>
これはテンプレートで、ビルド時は html-webpack-plugin
によって body 末尾に <script src="...">
が自動的に挿入される。
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"]
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "src/index.html")
})
]
};
ここまでの構成
src/
index.html
index.ts
node_modules/...
package.json
tsconfig.json
webpack.config.js
$ yarn webpack-dev-server
でサーバーを実行
http://localhost:8080
を開く。 Cmd(Ctrl)+Shift+I
で DevTools を開いて console.log("hello")
が実行されていれば OK。
静的吐き出ししたい場合は yarn webpack --mode production
で dist
ディレクトリに生成される。
crate ディレクトリに rust プロジェクトを生成
※ この節は Rust の人には馴染み深いが、フロントエンドには馴染みがないもの
こういうディレクトリを想定している
src/index.ts
crate/
src/
lib.rs
Cargo.toml
というわけで crate ディレクトリを cargo new
する。
$ cargo new crate --lib
ディレクトリ的な主従関係はどっちでもいいが、とりあえず混ぜないほうが幸せだと思う。
crate/Cargo.toml
に wasm-bindgen
を追加
[package]
name = "crate"
version = "0.1.0"
edition = "2018"
[dependencies]
wasm-bindgen = "^0.2"
[lib]
crate-type = ["cdylib"]
crate/src/lib.rs
に wasm-bindgen でバインディングされたコードを書く
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greeting() -> String {
"hello".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(greeting(), "hello".to_string());
}
}
build して テストする
$ cargo build
$ cargo test
とりあえずテストが通るところまで確認
wasm-pack + wasm-pack-plugin で wasm ローディング
wasm-pack をインストールしておく
$ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
$ wasm-pack -h # 確認
$ wasm-pack build # crate ディレクトリ
これをすると、crate/pkg
に次のようなファイルが生成される。
crate/pkg/
crate.d.ts
crate.js
crate.d.ts
crate_bg.wasm
crate_bg.d.ts
package.json
一つの npm モジュールのようになっている。
*_bg.wasm
が wasm のバイナリで、crate.js
がそれをラップしたもの。crate.d.ts
はそれに対する typescript 型定義ファイル。今回はこういう感じのものが生成されているはず。
/* tslint:disable */
/**
* @returns {string}
*/
export function greeting(): string;
これらを webpack から扱うためのプラグインを追加
$ yarn add @wasm-tool/wasm-pack-plugin -D
webpack.config.js
を次のように変更。差分としては、resolve.extensions
(省略可能拡張子) に .wasm
を追加して(wasm-pack がそれを要求するコードを生成している)、plugins に WasmPackPlugin
を追加して、crate
ディレクトリにビルド結果を生成するようにする。
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".wasm"]
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "src/index.html")
}),
new WasmPackPlugin({
crateDirectory: path.join(__dirname, "crate")
})
]
};
準備が整ったので、 src/index.ts
からこの crate/pkg
を呼び出す。(create/pkg/package.json
の main 指定によって、crate/pkg/crate.js
がエントリポイントになっている)
import("../crate/pkg").then(mod => {
console.log(mod.greeting());
});
静的 import ではなく dynamic import を使っている。これは wasm をロードする基点は非同期でないといけない、という制約があるため。
現在のディレクトリ構成
src/
index.html
index.ts
crate/
pkg/...
src/
lib.rs
Cargo.toml
Cargo.lock
node_modules/...
package.json
tsconfig.json
webpack.config.js
この状態で yarn webpack-dev-server
でサーバーを立てると、このログが読み込まれているはず。
wasm-bindgen を使わない場合、TextEncoder を使って自分で文字列組み立てしないと行けないので、だいぶだるい。wasm-bindgen 様様
.gitignore
dist
node_modules
pkg
target
.cache
.DS_Store
*.log
これらはコミットしなくていい
次にやりたいこと
- js-sys/web-sys
- https://github.com/fitzgen/dodrio
感想
やることは多いけど、 wasm-pack(-plugin) のおかげで昔やったときよりかなりストレスフリーになっていてよかった
LICENSE
MIT