开发者社区> 谷中仁> 正文

基于 Rust 的 WebAssembly 工程开发过程小记

简介: 使用 rust 开发 web assembly 的 happpy path
+关注继续查看

初始化工程

$ npm init rust-webpack web_assembly_demo
npx: 18 安装成功,用时 3.989 秒
 Rust +  WebAssembly + Webpack = ️
Installed dependencies 

安装 Web 依赖

$ yarn
yarn install v1.19.1
warning package.json: No license field
info No lockfile found.
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning rust-webpack-template@0.1.0: No license field
[1/4]   Resolving packages...
warning @wasm-tool/wasm-pack-plugin > watchpack > chokidar > fsevents@1.2.9: One of your dependencies needs to upgrade to fsevents v2: 1) Proper nodejs v10+ support 2) No more fetching binaries from AWS, smaller package size
[2/4]   Fetching packages...
[3/4]   Linking dependencies...
[4/4]   Building fresh packages...
success Saved lockfile.
  Done in 17.87s.

修改 Cargo.toml 为

# You must change these to your own details.
[package]
name = "web_assembly_demo"
description = "My super awesome Rust, WebAssembly, and Webpack project!"
version = "0.1.0"
authors = ["guzhongren <guzhoongren@live.cn>"]
categories = ["wasm"]
readme = "README.md"
edition = "2018"

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

[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true

[features]
# If you uncomment this line, it will enable `wee_alloc`:
#default = ["wee_alloc"]

[dependencies]
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
wasm-bindgen = "0.2.45"

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. However, it is slower than the default
# allocator, so it's not enabled by default.
wee_alloc = { version = "0.4.2", optional = true }

# The `web-sys` crate allows you to interact with the various browser APIs,
# like the DOM.
[dependencies.web-sys]
version = "0.3.22"
features = ["console"]

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled
# in debug mode.
[target."cfg(debug_assertions)".dependencies]
console_error_panic_hook = "0.1.5"

# These crates are used for running unit tests.
[dev-dependencies]
wasm-bindgen-test = "0.2.45"
futures = "0.1.27"
js-sys = "0.3.22"
wasm-bindgen-futures = "0.3.22"

Rust 的依赖会在启动 Web 程序的时候自动安装。

启动程序

$ yarn start
yarn run v1.19.1
warning package.json: No license field
$ rimraf dist pkg && webpack-dev-server --open -d
🧐  Checking for wasm-pack...

  wasm-pack is installed. 

ℹ️  Compiling your crate in development mode...

ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/c4/Desktop/Personal/01.Project/web_assembly/web_assembly_demo/dist
[INFO]:   Checking for the Wasm target...
[INFO]:   Compiling to Wasm...
ℹ 「wdm」: wait until bundle finished: /
   Compiling proc-macro2 v1.0.6
   Compiling unicode-xid v0.2.0
...
 3 assets
Entrypoint index = index.js
[./pkg/index.js] 4.41 KiB {0} [built]
[./pkg/index_bg.wasm] 145 KiB {0} [built]
    + 33 hidden modules
ℹ 「wdm」: Compiled successfully.
ℹ️  Compiling your crate in development mode...

[INFO]:   Checking for the Wasm target...
[INFO]:   Compiling to Wasm...
   Compiling rust-webpack-template v0.1.0 (/Users/c4/Desktop/Personal/01.Project/web_assembly/web_assembly_demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s
[INFO]: ⬇️  Installing wasm-bindgen...
[INFO]: Optional fields missing from Cargo.toml: 'repository', 'license'. These are not necessary, but recommended
[INFO]:    Done in 0.77s
[INFO]:    Your wasm pkg is ready to publish at ./pkg.
  Your crate has been correctly compiled

ℹ 「wdm」: Compiling...
ℹ 「wdm」: Hash: d4e8a3c57ad23f847707
Version: webpack 4.41.2
Time: 411ms
Built at: 2019-11-23 20:16:55
                           Asset     Size  Chunks                         Chunk Names
                            0.js   17 KiB       0  [emitted]              
beee557fb69dcfa0df60.module.wasm  161 KiB       0  [emitted] [immutable]  
                        index.js  897 KiB   index  [emitted]              index
Entrypoint index = index.js
[./pkg/index.js] 4.93 KiB {0} [built]
[./pkg/index_bg.wasm] 161 KiB {0} [built]
    + 33 hidden modules
ℹ 「wdm」: Compiled successfully.
ℹ 「wdm」: Compiling...
ℹ 「wdm」: Hash: 3e1681b9b4c4c940722e
Version: webpack 4.41.2
Time: 16ms
Built at: 2019-11-23 20:17:14
   Asset     Size  Chunks             Chunk Names
index.js  897 KiB   index  [emitted]  index
 + 2 hidden assets

在src中新建parse.rs 并编写处理 markdown 的 rust 代码

struct Parser {
  pos: usize,
  input: String,
}

pub fn parse(source: String) -> String {
  Parser {
    pos: 0,
    input: source,
  }.parse_lines()
}

impl Parser {
  fn parse_lines(&mut self) -> String {
    let mut result = String::new();
    loop {
      self.consume_whitespace();
      if self.end_of_line() {
        break;
      }
      result.push_str(&self.parse_line());
    }
    result
  }

  fn parse_line(&mut self) -> String {
    match self.next_char() {
      '#' => self.parse_title(),
      '-' => {
        if char::is_whitespace(self.input[self.pos + 1..].chars().next().unwrap()) {
          self.parse_list()
        } else {
          self.parse_text()
        }
      }
      _ => self.parse_text(),
    }
  }

  fn parse_list(&mut self) -> String {
    self.consume_char();
    self.consume_whitespace();
    let text = self.parse_text();
    create_html_element("li".to_string(), text)
  }

  fn parse_title(&mut self) -> String {
    let pound = self.consume_while(|c| c == '#');
    self.consume_whitespace();
    let text = self.parse_text();

    create_html_element(format!("h{}", pound.len()), text)
  }

  fn parse_text(&mut self) -> String {
    self.consume_while(|c| !is_new_line(c))
  }

  fn end_of_line(&self) -> bool {
    self.pos >= self.input.len()
  }

  // fn starts_with(&self, s: &str) -> bool {
  //   self.input[self.pos..].starts_with(s)
  // }

  fn next_char(&self) -> char {
    self.input[self.pos..].chars().next().unwrap()
  }

  fn consume_char(&mut self) -> char {
    let mut iter = self.input[self.pos..].char_indices();
    let (_, cur_char) = iter.next().unwrap();
    let (next_pos, _) = iter.next().unwrap_or((1, ' '));
    self.pos += next_pos;
    cur_char
  }

  fn consume_while<F>(&mut self, cond: F) -> String
  where
    F: Fn(char) -> bool,
  {
    let mut result = String::new();
    while !self.end_of_line() && cond(self.next_char()) {
      result.push(self.consume_char());
    }
    result
  }

  fn consume_whitespace(&mut self) {
    self.consume_while(char::is_whitespace);
  }
}

fn create_html_element(tag_name: String, text: String) -> String {
  format!("<{}>{}</{}>", tag_name, text, tag_name)
}

fn is_new_line(c: char) -> bool {
  c == '\n'
}

在 src/lib.rs 中引入 parse mod, 并编写向外暴露的函数

mod parser;

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn parse(input: &str) -> String {
  let result = parser::parse(input.to_string());
  result
}

在 js/index.js 中使用 Web Assembly ,修改结果如下

document.body.onload = addElement;
function addElement() {
  const markdown = document.createElement('textarea')
  markdown.id = 'markdown'
  markdown.style = "height: 300px; width: 400px ;"

  document.body.appendChild(markdown)
  const parseBtn = document.createElement('button')
  parseBtn.id = 'parse'
  parseBtn.innerHTML = '解析markdown'
  document.body.appendChild(parseBtn)

  const previewArea = document.createElement('div')
  previewArea.id = 'preview'
  document.body.appendChild(previewArea)


  const rust = import('../pkg/index.js')

  rust.then(module => {
    const btn = document.getElementById('parse')
    const previewArea = document.getElementById('preview')

    btn.addEventListener('click', () => {
      const input = document.getElementById('markdown').value
      previewArea.innerHTML = module.parse(input)
    })
  })
}


// import("../pkg/index.js").then(module => {
//   const input = '1233'
//     previewArea.innerHTML = module.parse(input)
// }).catch(console.error);

效果图

image

后续

后面会在这个基础上继续开发,并将 WASM 中的一些概念配置也写成文档共享在博客中。

仓库地址:https://github.com/AndorLab/web_assembly/tree/based_rust

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
OceanBase 2.2 开发者指南(文档)
本文是 ORACLE 和 MySQL 实例开发者指南。欢迎感兴趣的朋友下载看看。
1646 0
OceanBase加速生态开放步伐,技术能量助力开发者
作为全球唯一经过大规模金融场景长时间考验的分布式关系数据库,OceanBase在技术领域不断深耕的同时,也十分注重推动开发者生态的发展。
344 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
29869 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
13913 0
【我的Android进阶之旅】快速创建和根据不同的版本类型(Dev、Beta、Release)发布Android 开发库到Maven私服
前言 由于项目越来越多,有很多公共的代码都可以抽取出一个开发库出来传到公司搭建好的Maven私服,以供大家使用。 之前搭建的Maven仓库只有Release和Snapshot两个仓库,最近由于开发库有时候不稳定有bug,不便于测试。
1313 0
+关注
126
文章
1
问答
文章排行榜
最热
最新