WebAssembly 入门实战 Rust & Typescript

7,196 阅读4分钟

前言

WebAssembly 是未来的趋势

高效

WebAssembly 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件能力以达到原生执行效率

安全

WebAssembly 运行在一个沙箱化的执行环境中,甚至可以在现有的 JavaScript 虚拟机中实现。在web环境中,WebAssembly 将会严格遵守同源策略以及浏览器安全策略。

标准

WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下。

WebAssembly支持多种语言

目前能编译成 WebAssembly 字节码的高级语言有:

AssemblyScript:语法和 TypeScript 一致,对前端来说学习成本低,为前端编写 WebAssembly 最佳选择;
c\c++:官方推荐的方式。
Rust:语法复杂、学习成本高,对前端来说可能会不适应。
Kotlin:语法和 Java、JS 相似,语言学习成本低。
Golang:语法简单学习成本低。但对 WebAssembly 的支持还处于未正式发布阶段。

开发环境搭建

安装WebAssembly for Rust 环境

安装Rust

移步到 www.rust-lang.org/tools/insta… 哦~

构建工具

想要把 Rust 编译成 WebAssembly 需要一个工具: wasm-pack 。使用 wasm-pack 可以一条命令转成 WebAssembly 。 使用下面命令下载和安装:

bash cargo install wasm-pack

安装 wasm-pack 有坑

Ubuntu上可能会缺少依赖

installed libssl-dev on ubuntu 18 and still get this error:
error: failed to run custom build command for openssl-sys v0.9.39
process didn't exit successfully: /home/forecast/cs453_finalproj_backend/target/release/build/openssl-sys-864259d0b1f702fc/build-script-main (exit code: 101)

可能需要安装:

sudo apt install pkg-config
sudo apt install libssl-dev

来写点 Rust

新建 Rust 项目,本文以斐波那契数列为例

cargo new --lib hello-wasm-fibonacci
创建成功后提示: Created library hello-wasm-fibonacci package


项目结构:

.
├── Cargo.toml
└── src
    └── lib.rs
1 directory, 2 files

配置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"

终于可以开始写 Rust 代码

(本文不展开 Rust 语法,专注 WebAssembly for Rust)

删除原来 lib.rs 的所有代码,在里面写想要的东西


extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

//生成 n 阶斐波那契数列
fn fibonacci(n: u32) -> u32 {
    if n == 0 {
        return 0;
    } else if n == 1 {
        return 1;
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

#[wasm_bindgen]
pub fn get_fibonacci(n: u32) -> u32 {
    return fibonacci(n);
}

小问号你是否有很多朋友?

  • 在 Rust 当中,库被称为 “crates”,因为我们使用的是一个外部库,所以有 "extern"
  • use xxx ::nnn::*; 是将库中的代码引入到你的代码中的使用命令。在这个情况下,将会引入 xxx::nnn 的全部模块
  • 在 #[] 中的内容叫做 "属性",并以某种方式改变下面的语句,类似于Typescript或者Java里面的注解。这里使用#[wasm_bindgen]属性,就可以把 Rust 函数开放给 JavaScript 使用。

打包发布到 npm

使用 wasm-pack 构建 WebAssembly

wasm-pack build --scope mynpmusername

登录 npm (默认已安装 npm )

npm adduser
Username: yournpmusername
Password:
Email: (this IS public) you@example.com

进入 WebAssembly 文件目录 pkg

cd pkg

由于 wasm-pack 生成的 package.js 漏了_bg.js ,需要修改package.json补充

{
  "name": "@mynpmusername/hello-wasm-fibonacci",
  "collaborators": [
    "yourgitname <yourgitmail>"
  ],
  "version": "0.1.0",
  "files": [
    "hello_wasm_fibonacci_bg.wasm",
    "hello_wasm_fibonacci.js",
    "hello_wasm_fibonacci_bg.js",
    "hello_wasm_fibonacci.d.ts"
  ],
  "module": "hello_wasm_fibonacci.js",
  "types": "hello_wasm_fibonacci.d.ts",
  "sideEffects": false
}

发布到npm

npm publish --access=public

在前端项目中使用

新建前端项目

mkdir web

新建 package.json

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "dependencies": {
    "@mynpmusername/hello-wasm-fibonacci": "^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.htm

<!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 fibonacci = import("./node_modules/@yournpmusername/hello-wasm-fibonacci/hello_wasm_fibonacci.js");

fibonacci.then(fibonacci => {
  const a = fibonacci.get_fibonacci(10)
  console.log(a)
});

AssemblyScript

typescript 编写 wasm

  • 官方地址
  • 实现方法
    • yarn init -y
    • yarn add @assemblyscript/loader
    • yarn add assemblyscript -D
    • npx asinit .
    • yarn
    • yarn asbuild or yarn asbuild:watch
{
    "scripts":{
    "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --validate --sourceMap --debug",
    "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --validate --sourceMap --optimize",
    "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
    "asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild" 需要用 onchange库实现开发模式
    }
}

tsconfig.json 配置

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

开发方式类似于 ts

// The entry file of your WebAssembly module.

export function add(a: i32, b: i32): i32 {
  return a + b;
}

export function isPrime(x: u32): bool {
  if (x < 2) {
      return false;
  }

  for (let i: u32 = 2; i < x; i++) {
      if (x % i === 0) {
          return false;
      }
  }

  return true;
}


export function fabonacci(n:number):number{
  return n < 2 ? n : fabonacci(n - 1) +  fabonacci(n - 2);
}

总结

  • 经过比较 TypeScript 生成的 wasm 速度比 Rust 快, Rust 打包出来的 wasm 比 TypeScrip 要小很多倍 (下次再详细写)
  • Rust 语言还需要学习
  • wasm 性能比 JS 要强很多, 可以应用在高性能需求场景。
  • wasm 目前打包工作流完善

相关Demo

如果懒得配环境,又想试试 WebAssembly

可以直接用 webassembly.studio/

作者


Benny Shi

Ken.Xu