Initialization project
$ npm init rust-webpack web_assembly_demo npx: 18 Installation successful, time 3.989 second Rust + WebAssembly + Webpack = ️ Installed dependencies
Install Web dependency
$ 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.
Modify Cargo.toml to
# 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"
Trust's dependencies are automatically installed when you start the Web program.
Startup program
$ 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
Create parse.rs in src and write the rust code to handle markdown
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' }
Introduce parse mod into src/lib.rs, and write exposed functions
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 }
Using Web Assembly in js/index.js, the modification results are as follows
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 = 'analysis 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);
Design sketch
Follow-up
Later, we will continue to develop on this basis, and write some concept configurations in WASM as document sharing in the blog.