Rust on WebAssembly on PWA

[Rust LT #5](https://rust.connpass.com/event/129406/)向けのスライドとなります。

---
## 目次
- 登場人物
- Rustセットアップ
- Nuxtセットアップ
- Hello Wasm
- 全文検索してみる
- Go、Kotlin

---
## IAM
| key      | value | 
|:-----------------|:------------------|
| なまえ            |              みなみじま|
| ぞくせい|            WEBエンジニア（なんでもやる）|
| Rust歴 | 4カ月（初心者）|

---
## 何するか

~~5分間ガンバル~~
Rust+wasmとpwaの組み合わせを検証する

---
## Rust
- 安全
- 速い
- Mozilla
- クセが強い

---

## WebAssembly
- 速い
- Webブラウザで動かせるバイトコード
- C, C++, Rust, Go, Kotlinが対応
- おおよそのブラウザが対応済み(IE除く)

---
## NuxtJS
- Vuejsアプリケーションを作成するフレームワーク
- SSR、SPA
- ルーティング
- 簡単

---
## PWA
- Progressive Web Apps
- Webサイトをネイティブアプリっぽく使える
- プッシュ通知もできる
- キャッシュを使えばオフラインでも使える

---
## Rust on WebAssembly on PWA

- wasmファイルをキャッシュできればよりネイティブアプリに近づくのではないか。
- PWAであればファイルが大きかったとしてもそれほど気にならないのでは？
- さくさく？

---
## Set up: Rust, wasm-pack

```
$ npm --version
6.9.0
$ cargo --version
cargo 1.34.0 (6789d8a0a 2019-04-01)
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
 Project Name: RuxtWasm
 Renaming project called `RuxtWasm` to `ruxt-wasm`...
 Creating project called `ruxt-wasm`...
 Done! New project created /home/ec2-user/ruxt-wasm
$ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
$ cd ruxt-wasm
$ wasm-pack build
```

---
## Set up: NuxtJS

```
$ npm install -g vue-cli
$ vue init nuxt-community/starter-template www
$ cd www && npm install && npm install --save-dev @nuxtjs/pwa
$ mkdir modules && cp -a ../pkg ./modules/ruxt-wasm
(nuxt.config.jsとpage/index.vueをvim)
$ npm run build
```

## Set up: pwa

```js:nuxt.config.js(の一部分)

  modules: [
    '@nuxtjs/pwa'
  ],
  manifest: {
    name: 'ruxt-wasm',
    lang: 'ja'
  }
```

---
## Set up: pages/index.vue

```vue:www/pages/index.vue（※scriptだけ)
<script>
import AppLogo from '~/components/AppLogo.vue'
import * as wasm from '~/modules/ruxt-wasm'

export default {
  components: {
    AppLogo
  },
  mounted: function() {
    wasm.greet();
  }
}
</script>
```

---
## Set up: firebase (1/2)
プロジェクト作成：　https://console.firebase.google.com/
![107.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/ffdb5714-00af-0ff1-8bc8-8d7f644794ea.png)

---
## Set up: firebase (2/2)

```
$ npm install -g firebase-tools
$ firebase login
$ cd <project-name>
$ firebase init
$ npm run generate
$ firebase deploy
```

---
## cacheの様子

```
$ find ./dist -type f -ls
34299086    4 -rw-rw-r--   1 ec2-user ec2-user     1059 May  3 03:00 ./dist/sw.js
34299087    4 -rw-rw-r--   1 ec2-user ec2-user     1620 May  3 03:00 ./dist/icon.png
34299088    4 -rw-r--r--   1 ec2-user ec2-user     1150 May  3 03:00 ./dist/favicon.ico
34299089   24 -rw-rw-r--   1 ec2-user ec2-user    20981 May  3 03:00 ./dist/crab.png
34299090    4 -rw-r--r--   1 ec2-user ec2-user      354 May  3 03:00 ./dist/README.md
38617411    4 -rw-rw-r--   1 ec2-user ec2-user      799 May  3 03:00 ./dist/_nuxt/manifest.36054caa.json
3970850    4 -rw-rw-r--   1 ec2-user ec2-user     2574 May  3 03:00 ./dist/_nuxt/icons/icon_64.9qcoYVJwzAP.png
3970851   56 -rw-rw-r--   1 ec2-user ec2-user    55098 May  3 03:00 ./dist/_nuxt/icons/icon_512.9qcoYVJwzAP.png
3970852   36 -rw-rw-r--   1 ec2-user ec2-user    34872 May  3 03:00 ./dist/_nuxt/icons/icon_384.9qcoYVJwzAP.png
3970853   12 -rw-rw-r--   1 ec2-user ec2-user    12265 May  3 03:00 ./dist/_nuxt/icons/icon_192.9qcoYVJwzAP.png
3970854   12 -rw-rw-r--   1 ec2-user ec2-user     9024 May  3 03:00 ./dist/_nuxt/icons/icon_152.9qcoYVJwzAP.png
3970855    8 -rw-rw-r--   1 ec2-user ec2-user     8130 May  3 03:00 ./dist/_nuxt/icons/icon_144.9qcoYVJwzAP.png
3970856    8 -rw-rw-r--   1 ec2-user ec2-user     5400 May  3 03:00 ./dist/_nuxt/icons/icon_120.9qcoYVJwzAP.png
38617412   40 -rw-rw-r--   1 ec2-user ec2-user    40012 May  3 03:00 ./dist/_nuxt/d174999b4965f00627c2.js
38617413    4 -rw-rw-r--   1 ec2-user ec2-user     3077 May  3 03:00 ./dist/_nuxt/c47da5aa0a73269598d6.js
38617415   12 -rw-rw-r--   1 ec2-user ec2-user    12205 May  3 03:00 ./dist/_nuxt/bd22632b15312d566e1b.js
38617416    4 -rw-rw-r--   1 ec2-user ec2-user      427 May  3 03:00 ./dist/_nuxt/LICENSES
38617418    8 -rw-rw-r--   1 ec2-user ec2-user     4252 May  3 03:00 ./dist/_nuxt/51db91afabe7c3392626.js
38617424    8 -rw-rw-r--   1 ec2-user ec2-user     4356 May  3 03:00 ./dist/_nuxt/4d57da47643d33aa7414.js
38617425  148 -rw-rw-r--   1 ec2-user ec2-user   149796 May  3 03:00 ./dist/_nuxt/2340463d96e5f0bad1e2.js
38617426    4 -rw-rw-r--   1 ec2-user ec2-user     1934 May  3 03:00 ./dist/_nuxt/0ae67e94edd4b41d300f.module.wasm
34299091    0 -rw-rw-r--   1 ec2-user ec2-user        0 May  3 03:00 ./dist/.nojekyll
34299092    4 -rw-rw-r--   1 ec2-user ec2-user     3558 May  3 03:00 ./dist/index.html
34299093    4 -rw-rw-r--   1 ec2-user ec2-user     3768 May  3 03:00 ./dist/200.html
```

---
## Hello wasm

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/80501034-c4b5-a4d4-ddb1-56796ad68413.gif)


--- 
## まだまだつづく
SQLite使ってみます。
https://crates.io/crates/rusqlite/

日本郵便の郵便番号データ

```
$ wget http://www.post.japanpost.jp/zipcode/dl/kogaki/zip/ken_all.zip
$ unzip -p ken_all.zip | iconv -f sjis -t utf8 | head -n 1
01101,"060  ","0600000","ﾎｯｶｲﾄﾞｳ","ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ","ｲｶﾆｹｲｻｲｶﾞﾅｲﾊﾞｱｲ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,
$ unzip -p ken_all.zip |iconv -f sjis -t utf8 | cut -d ',' -f 8-9 | sed -e 's/","//' | sed -e 's/"//g' > addr.csv
```

---
### せっかくなので全文検索にしておく
- sqlite3はFTS(Full Test Search)に対応している。
- addressをmecabで分かち書きにしてkeywordsカラムに入れています

```bash
$ echo 'Rust界隈、猛者ばかり' | mecab -Owakati
Rust 界隈 、 猛者 ばかり
```


---
### SQLiteにinsert

```bash
$ sqlite3 asset_dir/addr.db
SQLite version 3.28.0 2019-04-16 19:49:53
sqlite> create virtual table addr using fts4( id integer primary key, address text);
sqlite>
$ i=0; for l in `cat ../addr.csv | head -n 50000`; do i=`expr $i + 1`; sqlite3 -echo addr.db "INSERT INTO addr(id, address) VALUES(${i},'$(echo $l|mecab -Owakati)');"; done
$ sqlite3  addr.db 'SELECT count(*) FROM addr;'
50000
```
なお、dbファイルは6.2MBになりました。


---
### いざbuild

しかし、、、
```wasm-pack build```が、sqlite3のライブラリ関係で四苦八苦した末うまくできず。。

```bash
[INFO]: Compiling to Wasm...
   Compiling libsqlite3-sys v0.14.0
error: failed to run custom build command for `libsqlite3-sys v0.14.0`
process didn't exit successfully: `/home/ec2-user/ruxt-wasm/target/release/build/libsqlite3-sys-16fb9aff8b127c8d/build-script-build` (exit code: 1)
```

---
### Emscripten
別の方法を試します
https://emscripten.org/docs/getting_started/downloads.html

```.cargo/config
[target.wasm32-unknown-emscripten]
rustflags = [
    "-Clink-args=-s EXPORTED_FUNCTIONS=['_cnt','_ins','_add','_sel','_cntall'] -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap'] --embed-file asset_dir/addr.db -O1 -WASM1 -o ruxt.wasm.js -memory-init-file1 -ALLOW_MEMORY_GROWTH1  -s SINGLE_FILE=1",
]
```

---
### 簡単なソース(KSC)

```rust:main.rs
#[no_mangle]
pub fn cnt(raw: *mut u8, len : usize) -> i32{
    let buffer: &[u8] = unsafe { std::slice::from_raw_parts_mut(raw, len) };
    let rust_str: &str = str::from_utf8(buffer).unwrap();
    let keyword: String = rust_str.to_owned();
    let conn = Connection::open("asset_dir/addr.db").unwrap();
    let row0 = conn.query_row(&format!("SELECT count(*) as cnt FROM addr WHERE keywords MATCH '{}*'", keyword), params![], |row| {
        row.get(0)
    });

    let result:rusqlite::types::Value = row0.unwrap();
    match result {
        Integer(i) => i,
        _ => 0
    }
}
```

```bash
$ rustup target add wasm32-unknown-emscripten
$ env CC=emcc cargo build --release --target=wasm32-unknown-emscripten --verbose
 ...
 Finished dev [unoptimized + debuginfo] target(s) in 4m 27s
```

でけた！
・・jsは31Mとかになってます。。

---
### 簡単なjs

```vuejs:index.vue
 methods: {
    cnt: function(){
      var utf8 = (new TextEncoder).encode(this.cnttext);
      var len = utf8.byteLength;
      var bufptr = Module._malloc(len);
      Module.HEAPU8.set(utf8, bufptr);
      var _cnt = cwrap('cnt', 'number', ['number', 'number']);
      alert("SELECT count(*) FROM addr WHERE keywords MATCH '" + this.cnttext + "' = "+ `${_cnt(bufptr, len)}`);
      Module._free(bufptr);
    },
}
```

---
## いちおう動いた

![WPVmbmSiWa4qfnBdyz5zmu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/096f0b1a-919a-4949-b49b-83eec7fd34ac.gif)



---
## JSでもやる 下ごしらえ編


```bash
$ echo "[" > addr.json
$ sqlite3 asset_dir/addr.db "select '[\"' || replace(replace(keywords,' ' ,'\",\"') || '],',',\"]',']') from addr;" >> addr.json
$ echo "]" >> addr.json
```

```json

[
["札幌","市","中央","区","以下","に","掲載","が","ない","場合"],
["札幌","市","中央","区","旭ケ丘"],
...
```

---
## JSでもやる


```
import arrAddr from '~/assets/data/addr.json'

...

    cntjs: function(){
      var reg = new RegExp('^'+this.cnttext);
      var count = (arrAddr.filter((line) => line.some((v) => str.match(reg)))).length;
      alert(`${count}`);
    },
```

---
## performance(msec)

wasm | js
---------- | ----------
*7.2* | 56.3
*3.4* | 67.0
*3.3* | 53.1
*3.3* | 48.7
*4.6* | 50.4

(win10Pro 64bit corei5 MEM4GB)


---
## やってみて
- とりあえず早い
- wasm-packとlibsqliteがうまくいかなかった
- Rust to jsでString返すのが実装間に合わず
- とにかくファイルが大きい(31MB)
- 使いどころ次第？
- Go+wasmも良さそう


---
## おまけ
GOでやってみる

```go:main.go
package main
import "syscall/js"

func main() {
    var cb js.Func
    cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        js.Global().Call("alert", "Hello, go-wasm!")
        cb.Release() // release the function if the button will not be clicked again
        return nil
    })
    js.Global().Get("document").Call("getElementById", "go-alert").Call("addEventListener", "click", cb)

    select {}
}
```

---
### build

```bash
$ go version
go version go1.12.4 linux/amd64
$ GOARCH=wasm GOOS=js go build -o alert.wasm main.go
$ ls static/wasm/
alert.wasm  wasm_exec.js
```

[github:golang/go/tree/misc/wasm](https://github.com/golang/go/tree/master/misc/wasm)のhtmlとjsを使います。

---
### nuxtにalert.wasm追加

```vue:alert.vue(の一部)
    window.GoWasm = {
      functions: {},
      init () {
        if (!WebAssembly.instantiateStreaming) { // polyfill
          WebAssembly.instantiateStreaming = async (resp, importObject) => {
            const source = await (await resp).arrayBuffer
            return await WebAssembly.instantiate(source, importObject)
          }
        }
        const go = new Go()
        let mod, inst
        WebAssembly.instantiateStreaming(fetch("/wasm/alert.wasm"), go.importObject)
          .then(result => {
            mod = result.module
            inst = result.instance
            go.run(inst)
          })
          .catch(console.error)
      }
    }
    let ScriptGo = document.createElement('script');
    ScriptGo.setAttribute('src', '/wasm/wasm_exec.js');
    ScriptGo.onload = ScriptGo.onreadystatechange = function() {
      GoWasm.init()
    };
    document.head.appendChild(ScriptGo);
  },
```

---
## kotlinでもやってみる

```bash
$ cd ~/workdir && git clone https://github.com/JetBrains/kotlin-native.git
$ cd kotlin-native && ./gradlew dependencies:update && ./gradlew bundle
$ cd ~/workdir && git clone https://github.com/ftomassetti/kotlin-wasm-examples.git
$ cd kotlin-wasm-examples && ./build.sh
```

---
### main.kt

```kotlin:main.kt
import kotlinx.interop.wasm.dom.*
import kotlinx.wasm.jsinterop.*

fun main(args: Array<String>) {
    val s: String = "Hello Kotlin-wasm!!"
    s.forEach {
        it -> jsaddchar(it.toInt())
    }
}

@SymbolName("imported_jsaddchar")
external public fun jsaddchar(i: Int)
```
Stringの受け渡しとかやるまでは余力がなく、コールバックで文字を埋め込んでるだけ

---
### nuxtにkotlin.wasm追加

```vue:alert.vue(の一部)
  mounted() {
    let ScriptElm = document.createElement('script');
    ScriptKtln.setAttribute('src', '/kotlin.wasm.js');
    ScriptKtln.setAttribute('wasm', '/kotlin.wasm');
    ScriptKtln.onload = ScriptKtln.onreadystatechange = function() {
      konan.libraries.push({"imported_jsaddchar":function(i){ document.getElementById('hellokotlin').value = document.getElementById('hellokotlin').value + String.fromCharCode(i);}});
    };
    document.head.appendChild(ScriptKtln);
  },
```
---
## おわり

![6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/c3255bd1-97aa-8e4a-78bd-b67eee7eaa4a.gif)