RustとWebAssemblyでDOMを操作する

## 概要
WebAssemblyを使用して、HTMLのDOMを操作することを目的とします。
メモベースで書いていきます。


## まずはプロジェクト作成
`cargo new`でライブラリのテンプレートを作成します。

```bash
$ cargo new --lib dom
```

### Cargo.tomlに追記
多言語からもビルド後のライブラリを触れるようにするために、 `carte-type`に`cdylib`
を設定します。

```toml
[lib]
crate-type = ["cdylib"]
```

https://doc.rust-lang.org/reference/linkage.html

WebAssemblyとJavaScript間でデータの受け渡しができるように、`wasm-bindgen` をインストールします。

```toml
[dependencies]
wasm-bindgen = "0.2.83"
```


ビルドを高速にするためにweb-sysのAPIを制限しているっぽいです。なので、`dependencies.web-sys`に`features`としてリストする必要があります。

https://rustwasm.github.io/wasm-bindgen/web-sys/using-web-sys.html

> To keep build times super speedy, web-sys gates each Web interface behind a cargo feature. Find the type or method you want to use in the API documentation; it will list the features that must be enabled to access that API.

以下のような感じ

```toml
[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'Node',
  'Window',
]
```

他のAPIを追加したい場合は以下を参照して探すと良さそうです。

https://rustwasm.github.io/wasm-bindgen/api/web_sys/

### Rustのコード

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    // Use `web_sys`'s global `window` function to get a handle on the global
    // window object.
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    // Manufacture the element we're gonna append
    let val = document.create_element("p")?;
    val.set_text_content(Some("Hello from Rust!"));

    body.append_child(&val)?;

    Ok(())
}

```

expectは実行の失敗時に表示される文字列を引数とするメソッドです。

`document.create_element`で `p`タグを作成し、`Hello from Rust!` をセットしています。

https://developer.mozilla.org/ja/docs/Web/API/Document/createElement


val.set_text_contentの引数で使用している`Some(T)`は何かしらの型を1つ持った、匿名タプル構造体型です。

コード的には意外とJavaScriptでの書き方に似ているのでわかりやすいですね。

### JavaScriptのプロジェクトを作成
webpack-dev-serverで起動するための準備をします。

```package.json
{
  "scripts": {
    "build": "webpack",
    "serve" : "webpack serve"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "1.5.0",
    "text-encoding": "^0.7.0",
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.49.0",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2"
  }
}
```

```webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js',
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html'
        }),
        new WasmPackPlugin({
            crateDirectory: path.resolve(__dirname, ".")
        }),
        // Have this example work in Edge which doesn't ship `TextEncoder` or
        // `TextDecoder` at this time.
        new webpack.ProvidePlugin({
          TextDecoder: ['text-encoding', 'TextDecoder'],
          TextEncoder: ['text-encoding', 'TextEncoder']
        })
    ],
    mode: 'development',
    experiments: {
        asyncWebAssembly: true
   }
};
```

#### HtmlWebpackPlugin
HtmlWebpackPluginは、バンドルしたjsファイルをsourceにしたscriptタグを挿入してくれます。

https://webpack.js.org/plugins/html-webpack-plugin/

#### WasmPackPlugin
WasmPackPluginは、Rustで書かれたlibテンプレートをpkgディレクトリにビルドしてくれる設定してくれます。

https://github.com/wasm-tool/wasm-pack-plugin

```index.html
<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
  </head>
  <body>
  </body>
</html>
```

```index.js
import('./pkg')
  .catch(console.error);
```

これで準備が整いました。

### 起動
`npm run serve` でwebpack-dev-serverを起動します。
localhost:8080にアクセスするとtemplateのindex.htmlに対してrustで書いたwasm用のコードの`p`タグのappendがされて `Hello from Rust!`という表示がでました。
![スクリーンショット 2022-12-13 1.43.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154845/38fb424c-f3b7-8fd2-3d48-24eb65408cdb.png)