WebAssemblyで実装された仮想DOM asm-dom をさわってみた

## asm-domとは
https://github.com/mbasso/asm-dom

asm-domはWebAssemblyで実装された仮想DOMライブラリです。
仮想DOMのdiff、patch部分のアルゴリズムをWebAssemblyに任せたミニマルな実装なので、Reactのような多彩な機能はないようです。WebAssemblyなので、爆速を期待してしまいます。

[最初のコミット](https://github.com/mbasso/asm-dom/commit/1e619cab3895d846bf389ceb0c175f0b2f02292e)が2017/2/26なのでまだ一年も経っていないライブラリです。
[ライセンスの表記](https://github.com/mbasso/asm-dom#copyright-and-license)に[snabbdom](https://github.com/snabbdom/snabbdom)の名前があるので何か関係があるのかもしれません。
> Simon Friis Vindum, 2015 as part of project snabbdom

### 使い方
C++でも書けるようなのですが、今回はwebらしくjsで使ってみました。

```js
npm i --save asm-dom
```

```js
npm i -D arraybuffer-loader
```

webpackでバンドルするので、webpackとwebpack-dev-serverも入れます。

```js
npm i -D webpack webpack-dev-server
```

webpack.config.jsはこんな感じ。`.wasm`ファイルを読み込むためにarraybuffer-loaderを足しています。

```js
var resolve = require('path').resolve;

module.exports = env => {
  const dist = env.prod ? 'docs' : 'dist';
  return {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: resolve(__dirname, dist),
      pathinfo: !env.prod,
    },
    node: {
      fs: 'empty'
    },
    devtool: env.prod ? 'source-map' : 'eval',
    devServer: {
      contentBase: resolve(__dirname, dist),
      historyApiFallback: true,
      compress: true,
      port: 9000
    },
    module: {
      loaders: [
        {
          test: /\.wasm$/,
          loaders: ['arraybuffer-loader']
        }
      ]
    }
  };
}
```
[JSでの使い方の軽いドキュメント](https://github.com/mbasso/asm-dom/blob/master/docs/js.md)があるので、それに沿った形です。

## virtual-domとの速度を比べてみた
仮想DOMライブラリである[virtual-dom](https://github.com/Matt-Esch/virtual-dom)とasm-domの速度を比べてみました。
30000個の`div`のテキストをランダムな数値で入れ替えて時間を吐き出してみました。
仕様ブラウザは Chromeでバージョン61 です。

### 圧倒的にasm-domの方が速かった
その差は約2倍でした。ここまで差が出るとは思いませんでした。さすがWebAssembly。virtual-domがそもそも遅いのかと疑ってしまいたくなります。Reactとかとも純粋なdiff, patchの処理だけで速度を比べてみたいです。

asm-domの計測結果
![asm-dom.png](https://qiita-image-store.s3.amazonaws.com/0/29966/62506036-071b-5471-6766-e7705c88be2f.png)
virtual-domの計測結果
![virtual-dom.png](https://qiita-image-store.s3.amazonaws.com/0/29966/73ec62f9-5337-cdc7-782c-f6822a3ad0d1.png)

### 計測コード
コードは[githubのレポジトリ](https://github.com/SatoshiKawabata/asm-dom-vs-virtual-dom)に置いておきました。
DOMの定義の仕方は[hyperscript](https://github.com/hyperhype/hyperscript)風なので、virtual-domと同じように`h()`でノードを作って`patch()`で変更を適応します。

```js:asm-dom
import init from 'asm-dom';

init().then(asmDom => {
  const { h, patch } = asmDom;

  const root = document.getElementById('root');

  let vnode = h('div', {}, []);
  patch(root, vnode);

  let cnt = 0;
  const id = setInterval(() => {
    console.time('apply patch in 30000 elements');
    const list = [];
    for (let i = 0; i < 30000; i++) {
      list.push(
        h('div', {}, [ Math.random() + '' ])
      );
    }
    const newVnode = h('div', {}, list);
    patch(vnode, newVnode);
    vnode = newVnode;
    console.timeEnd('apply patch in 30000 elements');
    cnt++;
    if (cnt >= 10) {
      clearInterval(id);
    }
  }, 100);
});
```

```js:virtual-dom
const h = require('virtual-dom/h');
const diff = require('virtual-dom/diff');
const patch = require('virtual-dom/patch');
const createElement = require('virtual-dom/create-element');

let cnt = 0;
let tree = h('div', {}, []);
let rootNode = createElement(tree);
document.body.appendChild(rootNode);

const id = setInterval(function () {
  console.time('apply patch in 30000 elements');
  const list = [];
  for (let i = 0; i < 30000; i++) {
    list.push(
    h('div', {}, [ Math.random() + '' ])
    );
  }
  const newTree = h('div', {}, list);
  const patches = diff(tree, newTree);
  rootNode = patch(rootNode, patches);
  tree = newTree;
  console.timeEnd('apply patch in 30000 elements');
  cnt++;
  if (cnt >= 10) {
    clearInterval(id);
  }
}, 1000);
```

Web Componentsも試してみました。
『[WebAssemblyで実装された仮想DOM asm-dom でWeb Componentsを使ってみた](https://qiita.com/kwst/items/1571c6302bb6160d9b51)』