JavaScript
Node.js
TypeScript
WebAssembly
AssemblyScript

WASM (on AssemblyScript) をChromeとnodeそれぞれで動かすまで

とりあえずとっかかり編

  • 「WASM全くわからない」の状態から「とりあえずWASMが動くとはどういうことか?」ぐらいまでを知るところまでやる
  • AssemblyScriptが気になってたのでAssemblyScript使う
  • ブラウザで動かす話があんまり見つからなかったので、ちょっとまとめてみる。

chromeで動かす

1.セットアップ

WASMをchromeで動かそうとすると、ローカルファイルのfetchがCORSで弾かれてしまうので、サーバーを立てて動かす必要がある。
(firefoxは57.0.1で確認したところローカルで動くようなので、firefoxで試す場合は不要)

htmlとWASMファイルをpublicのlocalhostでアクセスできれば良いので、poiとかparcelでもbudoでもwebpack-dev-serverでもなんでも良いが、今回はcreate-react-appを使って進める
(若干トイレットペーパーで鼻をかむのと似た気持ちの悪さがあるが、create-react-appはsandboxとしてはなんだかんだ使いやすくて困る・・・)

$ npx create-react-app wasm-sample

2. AssemblyScript + wasmの設定

今回はWASMの生成として、AssemblyScriptを使う。

wikiはだいたい情報が古くなってしまっていたので色々ひっかかりどころがあった。

$ yarn add -D assemblyscript

3. tsconfig.json

tsconfig.jsonをこんな感じで設定

{
  "extends": "./node_modules/assemblyscript/tsconfig.assembly.json",
  "include": [
    "./**/*.ts"
  ]
}

extendsのファイルはwikiでは./node_modules/assemblyscript/std/assembly.json"になっているが、正しくは上記だった。

4. scripts設定

yarn start前にビルドしたいので、package.jsonをこんな感じにした

  "scripts": {
    "start": "react-scripts start",
    "prestart": "asc wasm/bin.ts -b > public/bin.wasm"
  }

ascについてはwikiのオプション情報が古くなっているので、asc --helpするのが良い

5. AssemblyScriptを書く

// wasm/bin.ts

// 値を返すだけのスクリプト
export function echo(val: f64): f64 {
  return val;
}

6. html置き換え

html側のロード。

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <body>
    <script>
      fetch('./bin.wasm').then(response => response.arrayBuffer()
      ).then(bytes => {
        return WebAssembly.instantiate(bytes, {})
      }).then( r => {
        const ex = r.instance.exports
        const result = ex.echo(3)
        document.querySelector("#root").innerHTML = result
      })
    </script>
    <div id="root"></div>
  </body>
</html>

fetchで持ってきてbufferをWebAssembly.instantiateでインスタンス化すると、値が返ってくることが確認できる。

localhost_3000.png

これがWASMか!

nodeでも動かす。

同じwasmをnodeでも動かしてみる。

// src/node.js
const fs = require("fs")
const wasm = "./public/bin.wasm"
const buffer = fs.readFileSync(wasm)

const w = WebAssembly.compile(buffer)
  .then( module => {
    const instance = WebAssembly.Instance(module, {})
    console.log(instance.exports.echo(3))
  })

fsでbuffer化してそれをcompileする。

node でもちゃんと動いた!

% node src/node.js
3

assemblyscript-loaderでもうちょっとかんばる編

ここまで、メモリ設定やらlib設定やら色々すっ飛ばしてきた。
このへん自前でやるとめんどくさいが、assemblyscript-loader というのが提供されているのでこれを使ったパターンもやってみる。

chromeで動かす

require使いたいのでhtmlに直接書くのをここからはやめて、コンパイルされる方に書く

// src/index.js

const { load } = require("assemblyscript-loader")

fetch('./bin.wasm')
  .then(result => result.arrayBuffer())
  .then( buf => load(buf) )
  .then( module => {
    const ex = module.exports
    const result = ex.echo(3)
    document.querySelector("body").innerHTML = result
  })

本当は load('./bin.wasm')でいけるはずなのだが、どうも正しく動いてないので通常のfetch関数を取得するようにした。
(軽く見た感じ、assemblyscript-loaderのxfetchにバグがあるのかも?)

nodeで動かす

nodeの方はめちゃくちゃシンプルになる。

// node.js
const { load } = require("assemblyscript-loader")

load("./public/bin.wasm").then( module => {
  console.log(module.exports.echo(3))
})

まとめ

  • wasmについて、「なんかバイナリ動くんでしょ」というぼんやりしたイメージのものだったが、やってみることでちょっとだけ理解が進む。
  • AssemblyScriptまだまだお試し版で実用に耐えうるかというと微妙なステージ
    • 再帰関数などもまだ処理できてないっぽい?
  • とはいえnpm/yarnだけで物事が完結してくれるのでとっかかりとしてすごく良かった。
    • AssemblyScript頑張って欲しい!