WebAssembly

go

构建

// main.go
package main
import "fmt"

func main() {
	fmt.Println("Hello, WebAssembly!")
}
GOOS=js GOARCH=wasm go build -o main.wasm .

使用

# 拷贝必要的js
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

# 安装web工具
go get -u github.com/shurcooL/goexec

# 启动web服务
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
<!-- index.html -->
<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(
        fetch("main.wasm"),
        go.importObject
      ).then((result) => {
        go.run(result.instance);
      });
    </script>
  </head>
  <body></body>
</html>

更多例子

注册函数(Register Functions)

// main.go
package main

import "syscall/js"

func fib(i int) int {
	if i == 0 || i == 1 {
		return i
	}
	return fib(i-1) + fib(i-2)
}

func fibFunc(this js.Value, args []js.Value) interface{} {
	return js.ValueOf(fib(args[0].Int()))
}

func main() {
	done := make(chan int)
	js.Global().Set("fibFunc", js.FuncOf(fibFunc))
	<-done
}
<!-- index.html -->
<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(
        fetch("main.wasm"),
        go.importObject
      ).then((result) => {
        go.run(result.instance);
      });
    </script>
  </head>
  <body>
    <input id="num" type="number" />
    <button id="btn" onclick="ans.innerHTML=fibFunc(num.value * 1)">
      Click
    </button>
    <p id="ans">1</p>
  </body>
</html>

操作 DOM

// main.go
package main

import (
	"strconv"
	"syscall/js"
)

func fib(i int) int {
	if i == 0 || i == 1 {
		return i
	}
	return fib(i-1) + fib(i-2)
}

var (
	document = js.Global().Get("document")
	numEle   = document.Call("getElementById", "num")
	ansEle   = document.Call("getElementById", "ans")
	btnEle   = js.Global().Get("btn")
)

func fibFunc(this js.Value, args []js.Value) interface{} {
	v := numEle.Get("value")
	if num, err := strconv.Atoi(v.String()); err == nil {
		ansEle.Set("innerHTML", js.ValueOf(fib(num)))
	}
	return nil
}

func main() {
	done := make(chan int)
	btnEle.Call("addEventListener", "click", js.FuncOf(fibFunc))
	<-done
}
<!-- index.html -->
<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(
        fetch("main.wasm"),
        go.importObject
      ).then((result) => {
        go.run(result.instance);
      });
    </script>
  </head>
  <body>
    <input id="num" type="number" />
    <button id="btn">Click</button>
    <p id="ans">1</p>
  </body>
</html>

回调函数(Callback Functions)

// main.go
package main

import (
	"syscall/js"
	"time"
)

func fib(i int) int {
	if i == 0 || i == 1 {
		return i
	}
	return fib(i-1) + fib(i-2)
}

func fibFunc(this js.Value, args []js.Value) interface{} {
	callback := args[len(args)-1]

    // 异步执行
	go func() {
		time.Sleep(3 * time.Second)
		v := fib(args[0].Int())
		callback.Invoke(v) // 执行完成,调用回调函数
	}()

	js.Global().Get("ans").Set("innerHTML", "Waiting 3s...")
	return nil
}

func main() {
	done := make(chan int)
	js.Global().Set("fibFunc", js.FuncOf(fibFunc))
	<-done
}
<!-- index.html -->
<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go();
      WebAssembly.instantiateStreaming(
        fetch("main.wasm"),
        go.importObject
      ).then((result) => {
        go.run(result.instance);
      });
    </script>
  </head>
  <body>
    <input id="num" type="number" />
    <button id="btn" onclick="fibFunc(num.value * 1, (v)=> ans.innerHTML=v)">
      Click
    </button>
    <p id="ans"></p>
  </body>
</html>

Performance - JavaScript vs Go

package main

import "syscall/js"

func fib(i int) int {
	if i == 0 || i == 1 {
		return i
	}
	return fib(i-1) + fib(i-2)
}

func fibFunc(this js.Value, args []js.Value) interface{} {
	return js.ValueOf(fib(args[0].Int()))
}

func main() {
	done := make(chan int)
	js.Global().Set("fibFunc", js.FuncOf(fibFunc))
	<-done
}
<html>
  <head>
    <meta charset="utf-8" />
    <script src="./wasm_exec.js"></script>
    <script type="module">
      function fibonacciInJs(n) {
        if (n <= 1) return n;
        return fibonacciInJs(n - 1) + fibonacciInJs(n - 2);
      }

      async function run() {
        const go = new Go();
        const result = await WebAssembly.instantiateStreaming(
          fetch("main.wasm"),
          go.importObject
        );
        go.run(result.instance);

        const num = 20;

        console.time("Fibonnaci in go");
        const fibGo = fibFunc(num);
        console.timeEnd("Fibonnaci in go");

        console.time("Fibonnaci in JS");
        const fibJS = fibonacciInJs(num);
        console.timeEnd("Fibonnaci in JS");

        document.body.textContent = `Fib ${num}: Go ${fibGo} - JS ${fibJS}`;
      }

      run();
    </script>
  </head>
  <body></body>
</html>

优化

# 优化前命令
GOOS=js GOARCH=wasm go build -o ./main.wasm ./main.go

# 利用 tinygo
tinygo build -o ./main.wasm -target wasm ./main.go
tinygo build -o ./main.wasm -target wasm -no-debug ./main.go
rust

参考: https://developer.mozilla.org/zh-CN/docs/WebAssembly/Rust_to_wasm

环境

安装 rust

wasm-pack

cargo install wasm-pack

安装 npm

构建

# 创建一个lib项目
cargo new --lib hello-wasm

# 修改对应文件

# 构建包
# --target bundler - for bundlers like Webpack, Parcel, or Rollup.
# --target web - for the web as ECMAScript module.
# --target no-modules - for the web without ECMAScript module.
# --target nodejs - for Node.js
# wasm-pack build --scope mynpmusername
wasm-pack build --target bundler

# 发布包(可选)
cd pkg
npm publish --access=public
// src/lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
# Cargo.toml
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

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

[dependencies]
wasm-bindgen = "0.2"

使用构建生成的 wasm

新建 site 项目

// package.json
{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  //   "dependencies": {
  //     "@mynpmusername/hello-wasm": "^0.1.0"
  //   },
  "devDependencies": {
    "webpack": "^4.25.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  }
}
// webpack.config.js
const path = require("path");
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  mode: "development",
};
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hello-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>
// index.js
const js = import("../pkg/hello_wasm.js");
js.then((js) => {
  js.greet("WebAssembly");
});

测试

npm install
npm run serve

错误

  opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
  library: 'digital envelope routines',
  reason: 'unsupported',
  code: 'ERR_OSSL_EVP_UNSUPPORTED'
#For Windows, use the below command in cmd:
set NODE_OPTIONS=--openssl-legacy-provider
#For Unix, use:
export NODE_OPTIONS=--openssl-legacy-provider

更多例子

参考: https://rustwasm.github.io/wasm-bindgen/examples/index.html

wasm-pack build --target web --debug

# npm install --global http-server
# http-server
npx serve .

Execute Rust code from JavaScript

// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn helloworld() -> String {
    String::from("Hello world from Rust!")
}
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>"Hello world" in Rust + Webassembly</title>
    <script type="module">
      import init, { helloworld } from "./pkg/hello_wasm.js";

      async function run() {
        await init();
        document.body.textContent = helloworld();
      }

      run();
    </script>
  </head>

  <body></body>
</html>

Execute JavaScript code from Rust

// use the function console.log inside Rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn example() {
    log("Log from rust");
}
import init, { example } from "./pkg/helloworld.js";

async function run() {
  await init();
  example(); // This will log "Log from rust" to the console
}

run();

Performance - JavaScript vs Rust

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 | 1 => n,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}
import init, { fibonacci } from "./pkg/helloworld.js";

function fibonacciInJs(n) {
  if (n <= 1) return n;
  return fibonacciInJs(n - 1) + fibonacciInJs(n - 2);
}

async function run() {
  await init();
  const num = 20;

  console.time("Fibonnaci in rust");
  const fibRust = fibonacci(num);
  console.timeEnd("Fibonnaci in rust");

  console.time("Fibonnaci in JS");
  const fibJS = fibonacciInJs(num);
  console.timeEnd("Fibonnaci in JS");

  document.body.textContent = `Fib ${num}: Rust ${fibRust} - JS ${fibJS}`;
}

run();

Publishing to NPM

# They work the same way as with npm pack and npm publish
wasm-pack pack myproject/pkg
wasm-pack publish

run

use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[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(())
}
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'Node',
  'Window',
]
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>"Hello world" in Rust + Webassembly</title>
    <script type="module">
      import init from "./pkg/helloworld.js";

      async function run() {
        await init();
      }

      run();
    </script>
  </head>

  <body></body>
</html>
v0.1.0 github