Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Rust on WebAssembly on PWA

More than 1 year has passed since last update.

Rust on WebAssembly on PWA

by ikegam1
1 / 36

Rust LT #5向けのスライドとなります。


目次

  • 登場人物
  • 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

nuxt.config.js(の一部分)
  modules: [
    '@nuxtjs/pwa'
  ],
  manifest: {
    name: 'ruxt-wasm',
    lang: 'ja'
  }

Set up: pages/index.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


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


まだまだつづく

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カラムに入れています
$ echo 'Rust界隈、猛者ばかり' | mecab -Owakati
Rust 界隈 、 猛者 ばかり

SQLiteにinsert

$ 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のライブラリ関係で四苦八苦した末うまくできず。。

[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

[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)

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
    }
}
$ 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

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


JSでもやる 下ごしらえ編

$ echo "[" > addr.json
$ sqlite3 asset_dir/addr.db "select '[\"' || replace(replace(keywords,' ' ,'\",\"') || '],',',\"]',']') from addr;" >> addr.json
$ echo "]" >> addr.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でやってみる

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

$ 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のhtmlとjsを使います。


nuxtにalert.wasm追加

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でもやってみる

$ 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

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追加

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

ikegam1
けっこう雑多な感じのエンジニアです。 最近はスマートスピーカーだとかBOT作りに禿げんでいます。 なんかアプトプットを残しておきたいお年頃です。おっさんです。aws認定は12冠。 5つのストレングス:戦略性、着想、活発性、最上志向、内省
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away