基于 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

目录
相关文章
|
7月前
|
Rust API
使用Rust开发命令行工具
使用Rust开发命令行工具
60 1
|
7月前
|
存储 Rust 前端开发
给 Web 前端工程师看的用 Rust 开发 wasm 组件实战
wasm 全称 WebAssembly,是通过虚拟机的方式,可以在服务端、客户端如浏览器等环境执行的二进制程序。它有速度快、效率高、可移植的特点
125 0
|
4月前
|
Rust 前端开发 JavaScript
Rust 编译为 WebAssembly 在前端项目中使用(二)
Rust 编译为 WebAssembly 在前端项目中使用(二)
110 0
|
1月前
|
Rust 安全 程序员
拜登:“一切非 Rust 项目均为非法”,开发界要大变天?
白宫国家网络总监办公室(ONCD,以下简称网总办)在本周一发布的报告中说道:“程序员编写代码并非没有后果,他们的⼯作⽅式于国家利益而言至关重要。”
32 1
|
2月前
|
Rust 前端开发 JavaScript
Rust在前端与全栈开发中的实践探索
随着Rust语言的日渐成熟,其应用场景已经从后端扩展到前端和全栈开发领域。本文将深入探讨Rust语言在前端与全栈开发中的实际应用案例,分析Rust语言在这些领域的优势和面临的挑战,并展望Rust未来的发展趋势。
|
3月前
|
Web App开发 Rust JavaScript
Rust 笔记:WebAssembly 的 JavaScript API
Rust 笔记:WebAssembly 的 JavaScript API
93 0
|
3月前
|
Rust JavaScript 前端开发
将 Rust 程序编译为 WebAssembly
将 Rust 程序编译为 WebAssembly
112 0
|
4月前
|
Rust 前端开发 JavaScript
Rust 编译为 WebAssembly 在前端项目中使用(一)
Rust 编译为 WebAssembly 在前端项目中使用(一)
|
4月前
|
存储 Rust 测试技术
Rust 开发命令行工具(中)(三)
Rust 开发命令行工具(中)(三)
|
4月前
|
Rust 测试技术 人机交互
Rust 开发命令行工具(中)(二)
Rust 开发命令行工具(中)(二)