Rust Web 全栈开发之编写 WebAssembly 应用

这篇具有很好参考价值的文章主要介绍了Rust Web 全栈开发之编写 WebAssembly 应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Rust Web 全栈开发之编写 WebAssembly 应用

MDN Web Docs:https://developer.mozilla.org/zh-CN/docs/WebAssembly

官网:https://webassembly.org/

项目结构 和 功能

Web App 教师注册 <-> WebService <-> WebAssembly App 课程管理

什么是 WebAssembly

  • WebAssembly 是一种新的编码方式,可以在现代浏览器中运行
    • 它是一种低级的类汇编语言
    • 具有紧凑的二进制格式
    • 可以接近原生的性能运行
    • 并为 C/C ++ 、 C# 、 Rust 等语言提供一个编译目标,以便它们可以在 Web上运行
    • 它也被设计为可以与 JavaScript 共存,允许两者一起工作。

机器码

  • 机器码是计算机可直接执行的语言
  • 汇编语言比较接近机器码

ASSEMBLY -> ASSEMBLER -> MACHINE CODE

机器码与 CPU 架构

  • 不同的 CPU 架构需要不同的机器码和汇编
  • 高级语言可以“翻译”成机器码,以便在 CPU 上运行
  • SOURCE CODE
    • x64
    • x86
    • ARM

WebAssembly

  • WebAssembly 其实不是汇编语言,它不针对特定的机器,而是针对浏览器的。
  • WebAssembly 是中间编译器目标
  • SOURCE CODE
    • WASM
      • x64
      • x86
      • ARM

WebAssembly 是什么样的?

  • 文本格式 .wat
  • 二进制格式: .wasm

WebAssembly 能做什么

  • 可以把你编写 C/C++ 、 C# 、 Rust 等语言的代码编译成 WebAssembly 模块
  • 你可以在 Web 应用中加载该模块,并通过 JavaScript 调用它
  • 它并不是为了替代 JS ,而是与 JS 一起工作
  • 仍然需要 HTML 和 JS ,因为WebAssembly 无法访问平台 API ,例如 DOM , WebGL...

WebAssembly 如何工作

  • 这是 C/C++ 的例子

Hello.c -> EMSCRIPTEN(编译器) -> hello.wasm hello.js hello.html

WebAssembly 的优点

  • 快速、高效、可移植
    • 通过利用常见的硬件能力, WebAssembly 代码在不同平台上能够以接近本地速度运行。
  • 可读、可调试
    • WebAssembly 是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准最终版本目前仍在编制),这允许通过手工来写代码,看代码以及调试代码。
  • 保持安全
    • WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
  • 不破坏网络
    • WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。

Rust WebAssembly 部分相关 crate

  • wasm-bindgen
  • wasm-bindgen-future
  • web-sys
  • js-sys

搭建环境

Rust 官网:https://www.rust-lang.org/zh-CN/what/wasm

Rust and WebAssembly:https://rustwasm.github.io/docs/book/

Rust和WebAssembly中文文档:https://rustwasm.wasmdev.cn/docs/book/

安装

wasm-pack

下载安装地址:https://rustwasm.github.io/wasm-pack/installer/

Install wasm-pack

You appear to be running a *nix system (Unix, Linux, MacOS). Install by running:

curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

If you're not on *nix, or you don't like installing from curl, follow the alternate instructions below.

cargo-generate

cargo-generate helps you get up and running quickly with a new Rust project by leveraging a pre-existing git repository as a template.

Install cargo-generate with this command:

cargo install cargo-generate
~ via 🅒 base
➜ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

info: downloading wasm-pack
info: successfully installed wasm-pack to `/Users/qiaopengjun/.cargo/bin/wasm-pack`

~ via 🅒 base took 8.9s
➜

ws on  main via 🦀 1.67.1 via 🅒 base took 23.7s
➜ cargo install cargo-generate

    Updating crates.io index
warning: spurious network error (2 tries remaining): failed to connect to github.com: Operation timed out; class=Os (2)
warning: spurious network error (1 tries remaining): failed to connect to github.com: Operation timed out; class=Os (2)
  Downloaded cargo-generate v0.18.3
  Downloaded 1 crate (94.7 KB) in 0.73s
	......
   Compiling git2 v0.17.2
   Compiling cargo-generate v0.18.3
    Finished release [optimized] target(s) in 3m 48s
  Installing /Users/qiaopengjun/.cargo/bin/cargo-generate
   Installed package `cargo-generate v0.18.3` (executable `cargo-generate`)

ws on  main via 🦀 1.67.1 via 🅒 base took 3m 48.8s
➜ npm --version
9.5.0

ws on  main via 🦀 1.67.1 via 🅒 base

Clone the Project Template

ws on  main via 🦀 1.67.1 via 🅒 base
➜ cd ..

~/rust via 🅒 base
➜ cargo generate --git https://github.com/rustwasm/wasm-pack-template

🤷   Project Name: wasm-game-of-life
🔧   Destination: /Users/qiaopengjun/rust/wasm-game-of-life ...
🔧   project-name: wasm-game-of-life ...
🔧   Generating template ...
🔧   Moving generated files into: `/Users/qiaopengjun/rust/wasm-game-of-life`...
Initializing a fresh Git repository
✨   Done! New project created /Users/qiaopengjun/rust/wasm-game-of-life

~/rust via 🅒 base took 21.2s
➜ cd wasm-game-of-life

wasm-game-of-life on  master [?] via 🦀 1.67.1 via 🅒 base
➜ c

wasm-game-of-life on  master [?] via 🦀 1.67.1 via 🅒 base
➜

构建项目

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 1m 23.4s 
➜ wasm-pack build

[INFO]: 🎯  Checking for the Wasm target...
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
[INFO]: 🌀  Compiling to Wasm...
   Compiling proc-macro2 v1.0.59
   Compiling unicode-ident v1.0.9
   Compiling quote v1.0.28
   Compiling wasm-bindgen-shared v0.2.86
   Compiling log v0.4.18
   Compiling bumpalo v3.13.0
   Compiling once_cell v1.17.2
   Compiling wasm-bindgen v0.2.86
   Compiling cfg-if v1.0.0
   Compiling syn v2.0.18
   Compiling wasm-bindgen-backend v0.2.86
   Compiling wasm-bindgen-macro-support v0.2.86
   Compiling wasm-bindgen-macro v0.2.86
   Compiling console_error_panic_hook v0.1.7
   Compiling wasm-game-of-life v0.1.0 (/Users/qiaopengjun/rust/wasm-game-of-life)
warning: function `set_panic_hook` is never used
 --> src/utils.rs:1:8
  |
1 | pub fn set_panic_hook() {
  |        ^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `wasm-game-of-life` (lib) generated 1 warning
    Finished release [optimized] target(s) in 11.45s
[INFO]: ⬇️  Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨   Done in 1m 41s
[INFO]: 📦   Your wasm pkg is ready to publish at /Users/qiaopengjun/rust/wasm-game-of-life/pkg.

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 1m 52.0s 
➜ 

问题

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base took 3.8s 
➜ wasm-pack build

[INFO]: 🎯  Checking for the Wasm target...
Error: wasm32-unknown-unknown target not found in sysroot: "/opt/homebrew/Cellar/rust/1.67.1"

Used rustc from the following path: "/opt/homebrew/bin/rustc"
It looks like Rustup is not being used. For non-Rustup setups, the wasm32-unknown-unknown target needs to be installed manually. See https://rustwasm.github.io/wasm-pack/book/prerequisites/non-rustup-setups.html on how to do this.

Caused by: wasm32-unknown-unknown target not found in sysroot: "/opt/homebrew/Cellar/rust/1.67.1"

Used rustc from the following path: "/opt/homebrew/bin/rustc"
It looks like Rustup is not being used. For non-Rustup setups, the wasm32-unknown-unknown target needs to be installed manually. See https://rustwasm.github.io/wasm-pack/book/prerequisites/non-rustup-setups.html on how to do this.


wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base 
➜ 

解决

https://rustwasm.github.io/wasm-pack/book/prerequisites/non-rustup-setups.html

电脑中有两个Rust,默认使用的是brew install rust

通过 brew uninstall rust 卸载 Rust

并运行 rustup self uninstall 卸载通过官网安装的 Rust

最后重新通过官网安装Rust,再次执行命令解决问题。

Rust安装

~ via 🅒 base
➜ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /Users/qiaopengjun/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

  /Users/qiaopengjun/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /Users/qiaopengjun/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /Users/qiaopengjun/.profile
  /Users/qiaopengjun/.bash_profile
  /Users/qiaopengjun/.zshenv

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: aarch64-apple-darwin
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

info: profile set to 'default'
info: default host triple is aarch64-apple-darwin
info: syncing channel updates for 'stable-aarch64-apple-darwin'
info: latest update on 2023-06-01, rust version 1.70.0 (90c541806 2023-05-31)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 13.5 MiB /  13.5 MiB (100 %)   4.6 MiB/s in  2s ETA:  0s
info: downloading component 'rust-std'
 25.5 MiB /  25.5 MiB (100 %)   4.9 MiB/s in  5s ETA:  0s
info: downloading component 'rustc'
 52.6 MiB /  52.6 MiB (100 %)   6.5 MiB/s in  8s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 13.5 MiB /  13.5 MiB (100 %)   7.9 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
 25.5 MiB /  25.5 MiB (100 %)  20.9 MiB/s in  1s ETA:  0s
info: installing component 'rustc'
 52.6 MiB /  52.6 MiB (100 %)  23.6 MiB/s in  2s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-aarch64-apple-darwin'

  stable-aarch64-apple-darwin installed - rustc 1.70.0 (90c541806 2023-05-31)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source "$HOME/.cargo/env"

~ via 🅒 base took 2m 46.4s
➜ source "$HOME/.cargo/env"

~ via 🅒 base
➜ rustc --version
rustc 1.70.0 (90c541806 2023-05-31)

~ via 🅒 base
➜ rustup toolchain list
stable-aarch64-apple-darwin (default)

~ via 🅒 base
➜

构建

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 1m 23.4s 
➜ wasm-pack build

[INFO]: 🎯  Checking for the Wasm target...
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
[INFO]: 🌀  Compiling to Wasm...
   Compiling proc-macro2 v1.0.59
   Compiling unicode-ident v1.0.9
   Compiling quote v1.0.28
   Compiling wasm-bindgen-shared v0.2.86
   Compiling log v0.4.18
   Compiling bumpalo v3.13.0
   Compiling once_cell v1.17.2
   Compiling wasm-bindgen v0.2.86
   Compiling cfg-if v1.0.0
   Compiling syn v2.0.18
   Compiling wasm-bindgen-backend v0.2.86
   Compiling wasm-bindgen-macro-support v0.2.86
   Compiling wasm-bindgen-macro v0.2.86
   Compiling console_error_panic_hook v0.1.7
   Compiling wasm-game-of-life v0.1.0 (/Users/qiaopengjun/rust/wasm-game-of-life)
warning: function `set_panic_hook` is never used
 --> src/utils.rs:1:8
  |
1 | pub fn set_panic_hook() {
  |        ^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `wasm-game-of-life` (lib) generated 1 warning
    Finished release [optimized] target(s) in 11.45s
[INFO]: ⬇️  Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨   Done in 1m 41s
[INFO]: 📦   Your wasm pkg is ready to publish at /Users/qiaopengjun/rust/wasm-game-of-life/pkg.

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 1m 52.0s 
➜ 

Npm 初始化项目

npm 中文网:https://npm.nodejs.cn/

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 24.9s 
➜ npm init wasm-app www
🦀 Rust + 🕸 Wasm = ❤

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 22.7s 
➜ 

www/package.json

{
  "name": "create-wasm-app",
  "version": "0.1.0",
  "description": "create an app to consume rust-generated wasm packages",
  "main": "index.js",
  "bin": {
    "create-wasm-app": ".bin/create-wasm-app.js"
  },
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "start": "webpack-dev-server"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/rustwasm/create-wasm-app.git"
  },
  "keywords": [
    "webassembly",
    "wasm",
    "rust",
    "webpack"
  ],
  "author": "Ashley Williams <ashley666ashley@gmail.com>",
  "license": "(MIT OR Apache-2.0)",
  "bugs": {
    "url": "https://github.com/rustwasm/create-wasm-app/issues"
  },
  "homepage": "https://github.com/rustwasm/create-wasm-app#readme",
  "dependencies": {
    "wasm-game-of-life": "file:../pkg"
  },
  "devDependencies": {
    "hello-wasm-pack": "^0.1.0",
    "webpack": "^4.29.3",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.1.5",
    "copy-webpack-plugin": "^5.0.0"
  }
}

安装依赖

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 22.7s 
➜ cd www                                                               

www on  master [!] is 📦 0.1.0 via ⬢ v19.7.0 via 🅒 base 
➜ npm install        

www/index.js

import * as wasm from "wasm-game-of-life";

wasm.greet();

运行

www on  master [!] is 📦 0.1.0 via ⬢ v19.7.0 via 🅒 base took 2m 15.4s 
➜ npm run start

访问:http://localhost:8080/

src/lib.rs

mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

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

#[wasm_bindgen]
pub fn greet(s : &str) {
    alert(format!("Hello, {}!", s).as_str());
}

构建

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base 
➜ wasm-pack build
[INFO]: 🎯  Checking for the Wasm target...
[INFO]: 🌀  Compiling to Wasm...
   Compiling wasm-game-of-life v0.1.0 (/Users/qiaopengjun/rust/wasm-game-of-life)
warning: function `set_panic_hook` is never used
 --> src/utils.rs:1:8
  |
1 | pub fn set_panic_hook() {
  |        ^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `wasm-game-of-life` (lib) generated 1 warning
    Finished release [optimized] target(s) in 0.37s
[INFO]: ⬇️  Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨   Done in 0.80s
[INFO]: 📦   Your wasm pkg is ready to publish at /Users/qiaopengjun/rust/wasm-game-of-life/pkg.

wasm-game-of-life on  master [?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base 
➜ 

安装并运行

www on  master [!] is 📦 0.1.0 via ⬢ v19.7.0 via 🅒 base took 8m 0.8s 
➜ npm install && npm run start
npm WARN deprecated fsevents@1.2.9: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2

www/index.js

import * as wasm from "wasm-game-of-life";

wasm.greet("Dave");

打开项目 ws

webservice/Cargo.toml

[package]
name = "webservice"
version = "0.1.0"
edition = "2021"
default-run = "teacher-service"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-cors = "0.6.4"
actix-web = "4.3.1"
actix-rt = "2.8.0"
dotenv = "0.15.0"
serde = { version = "1.0.163", features = ["derive"] }
chrono = { version = "0.4.24", features = ["serde"] }
openssl = { version = "0.10.52", features = ["vendored"] }
sqlx = { version = "0.6.3", default_features = false, features = [
    "postgres",
    "runtime-tokio-rustls",
    "macros",
    "chrono",
] }


[[bin]]
name = "teacher-service"

webservice/src/bin/teacher-service.rs

use actix_cors::Cors;
use actix_web::{http, web, App, HttpServer};
use dotenv::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::io;
use std::sync::Mutex;

#[path = "../dbaccess/mod.rs"]
mod dbaccess;
#[path = "../errors.rs"]
mod errors;
#[path = "../handlers/mod.rs"]
mod handlers;
#[path = "../models/mod.rs"]
mod models;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;

use errors::MyError;
use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> io::Result<()> {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set.");
    let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap();
    let shared_data = web::Data::new(AppState {
        health_check_response: "I'm Ok.".to_string(),
        visit_count: Mutex::new(0),
        db: db_pool,
    });
    let app = move || {
        let cors = Cors::default()
            .allowed_origin("http://localhost:8080/")
            .allowed_origin_fn(|origin, _req_head| {
                origin.as_bytes().starts_with(b"http://localhost")
            })
            .allowed_methods(vec!["GET", "POST", "DELETE"])
            .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
            .allowed_header(http::header::CONTENT_TYPE)
            .max_age(3600);

        App::new()
            .app_data(shared_data.clone())
            .app_data(web::JsonConfig::default().error_handler(|_err, _req| {
                MyError::InvalidInput("Please provide valid json input".to_string()).into()
            }))
            .configure(general_routes)
            .configure(course_routes) // 路由注册
            .wrap(cors)
            .configure(teacher_routes)
    };

    HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

Wasm 克隆项目模板

ws on  main via 🦀 1.70.0 via 🅒 base 
➜ cargo generate --git https://github.com/rustwasm/wasm-pack-template

🤷   Project Name: wasm-client
🔧   Destination: /Users/qiaopengjun/rust/ws/wasm-client ...
🔧   project-name: wasm-client ...
🔧   Generating template ...
🔧   Moving generated files into: `/Users/qiaopengjun/rust/ws/wasm-client`...
Initializing a fresh Git repository
✨   Done! New project created /Users/qiaopengjun/rust/ws/wasm-client

ws on  main [!?] via 🦀 1.70.0 via 🅒 base took 29.2s 
➜ 

Npm 初始化项目

ws/wasm-client on  main [!?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 11.9s 
➜ npm init wasm-app www
🦀 Rust + 🕸 Wasm = ❤

ws/wasm-client on  main [!?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base took 3.5s 
➜ 

构建项目

wasm-pack build

Cargo.toml

[workspace]
members = ["webservice", "webapp", "wasm-client"]

wasm-client/Cargo.toml

[package]
name = "wasm-client"
version = "0.1.0"
authors = ["QiaoPengjun5162 <qiaopengjun0@gmail.com>"]
edition = "2018"

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

[features]
default = ["console_error_panic_hook"]

[dependencies]
chrono = { version = "0.4.26", features = ["serde"] }
serde = { version = "1.0.163", features = ["derive"] }
serde_derive = "1.0.163"
serde_json = "1.0.96"
js-sys = "0.3.63"
wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4.36"
web-sys = { version = "0.3.63", features = [
    "Headers",
    "Request",
    "RequestInit",
    "RequestMode",
    "Response",
    "Window",
    "Document",
    "Element",
    "HtmlElement",
    "Node",
    "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 isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }

[dev-dependencies]
wasm-bindgen-test = "0.3.13"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

注意:并不是所有的 Rust crate 都能在wasm中使用

https://getbootstrap.com/docs/5.2/getting-started/introduction/#cdn-links

项目目录

ws on  main [!?] via 🦀 1.70.0 via 🅒 base 
➜ tree -a -I "target|.git|node_modules|.bin"
.
├── .env
├── .gitignore
├── .vscode
│   └── settings.json
├── Cargo.lock
├── Cargo.toml
├── README.md
├── wasm-client
│   ├── .appveyor.yml
│   ├── .gitignore
│   ├── .travis.yml
│   ├── Cargo.toml
│   ├── LICENSE_APACHE
│   ├── LICENSE_MIT
│   ├── README.md
│   ├── pkg
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── wasm_client.d.ts
│   │   ├── wasm_client.js
│   │   ├── wasm_client_bg.js
│   │   ├── wasm_client_bg.wasm
│   │   └── wasm_client_bg.wasm.d.ts
│   ├── src
│   │   ├── errors.rs
│   │   ├── lib.rs
│   │   ├── models
│   │   │   ├── course.rs
│   │   │   └── mod.rs
│   │   └── utils.rs
│   ├── tests
│   │   └── web.rs
│   └── www
│       ├── .gitignore
│       ├── .travis.yml
│       ├── LICENSE-APACHE
│       ├── LICENSE-MIT
│       ├── README.md
│       ├── bootstrap.js
│       ├── index.html
│       ├── index.js
│       ├── package-lock.json
│       ├── package.json
│       └── webpack.config.js
├── webapp
│   ├── .env
│   ├── Cargo.toml
│   ├── src
│   │   ├── bin
│   │   │   └── svr.rs
│   │   ├── errors.rs
│   │   ├── handlers.rs
│   │   ├── mod.rs
│   │   ├── models.rs
│   │   └── routers.rs
│   └── static
│       ├── css
│       │   └── register.css
│       ├── register.html
│       └── teachers.html
└── webservice
    ├── Cargo.toml
    └── src
        ├── bin
        │   └── teacher-service.rs
        ├── dbaccess
        │   ├── course.rs
        │   ├── mod.rs
        │   └── teacher.rs
        ├── errors.rs
        ├── handlers
        │   ├── course.rs
        │   ├── general.rs
        │   ├── mod.rs
        │   └── teacher.rs
        ├── main.rs
        ├── models
        │   ├── course.rs
        │   ├── mod.rs
        │   └── teacher.rs
        ├── routers.rs
        └── state.rs

19 directories, 65 files

ws on  main [!?] via 🦀 1.70.0 via 🅒 base 
➜ 

wasm-client/www/package.json

{
  "name": "create-wasm-app",
  "version": "0.1.0",
  "description": "create an app to consume rust-generated wasm packages",
  "main": "index.js",
  "bin": {
    "create-wasm-app": ".bin/create-wasm-app.js"
  },
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "start": "webpack-dev-server"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/rustwasm/create-wasm-app.git"
  },
  "keywords": [
    "webassembly",
    "wasm",
    "rust",
    "webpack"
  ],
  "author": "Ashley Williams <ashley666ashley@gmail.com>",
  "license": "(MIT OR Apache-2.0)",
  "bugs": {
    "url": "https://github.com/rustwasm/create-wasm-app/issues"
  },
  "homepage": "https://github.com/rustwasm/create-wasm-app#readme",
  "dependencies": {
    "wasm-client": "file:../pkg"
  },
  "devDependencies": {
    "hello-wasm-pack": "^0.1.0",
    "webpack": "^4.29.3",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.1.5",
    "copy-webpack-plugin": "^5.0.0"
  }
}

wasm-client/src/errors.rs

use serde::Serialize;

#[derive(Debug, Serialize)]
pub enum MyError {
    SomeError(String),
}

impl From<String> for MyError {
    fn from(s: String) -> Self {
        MyError::SomeError(s)
    }
}

impl From<wasm_bindgen::JsValue> for MyError {
    fn from(js_value: wasm_bindgen::JsValue) -> Self {
        MyError::SomeError(js_value.as_string().unwrap())
    }
}

wasm-client/src/models/mod.rs

pub mod course;

wasm-client/src/models/course.rs

use super::super::errors::MyError;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};

#[derive(Debug, Deserialize, Serialize)]
pub struct Course {
    pub teacher_id: i32,
    pub id: i32,
    pub name: String,
    pub time: NaiveDateTime,

    pub description: Option<String>,
    pub format: Option<String>,
    pub structure: Option<String>,
    pub duration: Option<String>,
    pub price: Option<i32>,
    pub language: Option<String>,
    pub level: Option<String>,
}

pub async fn get_courses_by_teacher(teacher_id: i32) -> Result<Vec<Course>, MyError> {
    // 访问webservice 读取课程
    let mut opts = RequestInit::new();
    opts.method("GET");
    opts.mode(RequestMode::Cors); // 跨域

    let url = format!("http://localhost:3000/courses/{}", teacher_id);

    let request = Request::new_with_str_and_init(&url, &opts)?;
    request.headers().set("Accept", "application/json")?;

    let window = web_sys::window().ok_or("no window exists".to_string())?;
    let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;

    assert!(resp_value.is_instance_of::<Response>());

    let resp: Response = resp_value.dyn_into().unwrap();
    let json = JsFuture::from(resp.json()?).await?;

    // let courses: Vec<Course> = json.into_serde().unwrap();
    let courses: Vec<Course> = serde_wasm_bindgen::from_value(json).unwrap();

    Ok(courses)
}

pub async fn delete_course(teacher_id: i32, course_id: i32) -> () {
    let mut opts = RequestInit::new();
    opts.method("DELETE");
    opts.mode(RequestMode::Cors);

    let url = format!("http://localhost:3000/courses/{}/{}", teacher_id, course_id);

    let request = Request::new_with_str_and_init(&url, &opts).unwrap();
    request.headers().set("Accept", "application/json").unwrap();

    let window = web_sys::window().unwrap();
    let resp_value = JsFuture::from(window.fetch_with_request(&request))
        .await
        .unwrap();

    assert!(resp_value.is_instance_of::<Response>());

    let resp: Response = resp_value.dyn_into().unwrap();
    let json = JsFuture::from(resp.json().unwrap()).await.unwrap();

    // let _course: Course = json.into_serde().unwrap();
    let _courses: Course = serde_wasm_bindgen::from_value(json).unwrap();
}

use js_sys::Promise;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub async fn add_course(name: String, description: String) -> Result<Promise, JsValue> {
    let mut opts = RequestInit::new();
    opts.method("POST");
    opts.mode(RequestMode::Cors);
    let str_json = format!(
        r#"
        {{
            "teacher_id": 1,
            "name": "{}",
            "description": "{}"
        }}
        "#,
        name, description
    );
    opts.body(Some(&JsValue::from_str(str_json.as_str())));
    let url = "http://localhost:3000/courses/";

    let request = Request::new_with_str_and_init(&url, &opts)?;
    request.headers().set("Content-Type", "application/json")?;
    request.headers().set("Accept", "application/json")?;

    let window = web_sys::window().ok_or("no window exists".to_string())?;
    let resp_value = JsFuture::from(window.fetch_with_request(&request))
        .await
        .unwrap();

    assert!(resp_value.is_instance_of::<Response>());

    let resp: Response = resp_value.dyn_into().unwrap();
    Ok(resp.json()?)
}

wasm-client/src/lib.rs

mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
    fn confirm(s: &str) -> bool;

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

#[wasm_bindgen]
pub fn greet(_name: &str) {
    alert("Hello, wasm-client!");
}

pub mod errors;
pub mod models;

use models::course::{delete_course, get_courses_by_teacher, Course};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::*;
use web_sys::HtmlButtonElement;

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global window exists");
    let document = window.document().expect("no global document exists");

    let left_tbody = document
        .get_element_by_id("left-tbody")
        .expect("left div not exists");

    let courses: Vec<Course> = get_courses_by_teacher(1).await.unwrap();
    for c in courses.iter() {
        let tr = document.create_element("tr")?;
        tr.set_attribute("id", format!("tr-{}", c.id).as_str())?;
        let td = document.create_element("td")?;
        td.set_text_content(Some(format!("{}", c.id).as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        td.set_text_content(Some(c.name.as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        td.set_text_content(Some(c.time.format("%Y-%m-%d").to_string().as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        if let Some(desc) = c.description.clone() {
            td.set_text_content(Some(desc.as_str()));
        }
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        // let btn = document.create_element("button")?;
        let btn: HtmlButtonElement = document
            .create_element("button")
            .unwrap()
            .dyn_into::<HtmlButtonElement>()
            .unwrap();

        let cid = c.id;
        let click_closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
            let r = confirm(format!("确认删除 ID 为 {} 的课程?", cid).as_str());
            match r {
                true => {
                    spawn_local(delete_course(1, cid)); // delete_course 异步函数 spawn_local 把 future 放在当前线程
                    alert("删除成功!");

                    web_sys::window().unwrap().location().reload().unwrap();
                }
                _ => {}
            }
        }) as Box<dyn Fn(_)>);

        btn.add_event_listener_with_callback("click", click_closure.as_ref().unchecked_ref())?; // 要把闭包转化为 function 的引用
        click_closure.forget(); // 走出作用域后函数依然有效 但会造成内存泄漏

        btn.set_attribute("class", "btn btn-danger btn-sm")?;
        btn.set_text_content(Some("Delete"));
        td.append_child(&btn)?;
        tr.append_child(&td)?;

        left_tbody.append_child(&tr)?;
    }

    Ok(())
}

wasm-client/www/index.js

import * as wasm from "wasm-client";

const myForm = document.getElementById('form');
myForm.addEventListener('submit', (e) => {
  e.preventDefault();
  const name = document.getElementById('name').value;
  const desc = document.querySelector('#description').value;

  wasm.add_course(name, desc).then((json) => {
    alert("添加成功!");
    window.location.reload();
  });
});

wasm-client/www/index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Hello wasm-pack!</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
</head>

<body>
  <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
  <nav class="navbar navbar-dark bg-primary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Wasm-client</a>
    </div>
  </nav>
  <div class="m-3" style="height: 600px">
    <div class="row">
      <div class="col">
        <div class="card border-info mb-3">
          <div class="card-header">课程列表</div>
          <!-- <div class="card-body">
            <button type="button" class="btn btn-primary">Add</button>
          </div> -->
          <table class="table talbe-hover table-bordered table-sm">
            <thead>
              <tr>
                <th scope="col">ID</th>
                <th scope="col">名称</th>
                <th scope="col">时间</th>
                <th scope="col">简介</th>
                <th scope="col">操作</th>
              </tr>
            </thead>
            <tbody id="left-tbody"></tbody>
          </table>
          <div id="left"></div>
        </div>
      </div>
      <div class="col">
        <div class="card border-info mb-3">
          <div class="card-header">添加课程</div>
          <div class="card-body">
            <form class="row g-3 needs-validation" id="form">
              <div class="mb-3">
                <label for="name" class="form-label">课程名称</label>
                <input type="name" class="form-control" id="name" required placeholder="课程名称" />
                <div class="invalid-feedback">
                  请填写课程名!
                </div>
              </div>
              <div class="mb-3">
                <label for="description" class="form-label">课程简介</label>
                <textarea class="form-control" id="description" rows="3"></textarea>
              </div>
              <div class="col-12">
                <button type="submit" class="btn btn-primary">提交</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script src="./bootstrap.js"></script>
</body>

</html>

运行

ws/wasm-client on  main [!?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base 
➜ wasm-pack build           

ws/webservice on  main [!?] is 📦 0.1.0 via 🦀 1.70.0 via 🅒 base 
➜ cargo run    

www on  master [!] is 📦 0.1.0 via ⬢ v19.7.0 via 🅒 base 
➜ npm install && npm run start

访问:http://localhost:8080/

问题:报错 Uncaught (in promise) DOMException: Failed to execute 'appendChild' on 'Node': The new child element contains the parent. 前端获取不到课程信息

解决:

这个错误提示说明在尝试将一个元素添加为其父元素的子元素时出现了问题,导致了循环引用。具体来说,错误发生在这一行代码中:

td.append_child(&td)?;

在这行代码中,你试图将创建的td元素添加为其自身的子元素。这是不允许的,因为一个元素不能成为自己的子元素。

正确的代码是: tr.append_child(&td)?;

修改之后的代码:文章来源地址https://www.toymoban.com/news/detail-470730.html

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global window exists");
    let document = window.document().expect("no global document exists");

    let left_tbody = document
        .get_element_by_id("left-tbody")
        .expect("left div not exists");

    let courses: Vec<Course> = get_courses_by_teacher(1).await.unwrap();
    for c in courses.iter() {
        let tr = document.create_element("tr")?;
        tr.set_attribute("id", format!("tr-{}", c.id).as_str())?;
        let td = document.create_element("td")?;
        td.set_text_content(Some(format!("{}", c.id).as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        td.set_text_content(Some(c.name.as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        td.set_text_content(Some(c.time.format("%Y-%m-%d").to_string().as_str()));
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        if let Some(desc) = c.description.clone() {
            td.set_text_content(Some(desc.as_str()));
        }
        tr.append_child(&td)?;

        let td = document.create_element("td")?;
        // let btn = document.create_element("button")?;
        let btn: HtmlButtonElement = document
            .create_element("button")
            .unwrap()
            .dyn_into::<HtmlButtonElement>()
            .unwrap();

        let cid = c.id;
        let click_closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
            let r = confirm(format!("确认删除 ID 为 {} 的课程?", cid).as_str());
            match r {
                true => {
                    spawn_local(delete_course(1, cid)); // delete_course 异步函数 spawn_local 把 future 放在当前线程
                    alert("删除成功!");

                    web_sys::window().unwrap().location().reload().unwrap();
                }
                _ => {}
            }
        }) as Box<dyn Fn(_)>);

        btn.add_event_listener_with_callback("click", click_closure.as_ref().unchecked_ref())?; // 要把闭包转化为 function 的引用
        click_closure.forget(); // 走出作用域后函数依然有效 但会造成内存泄漏

        btn.set_attribute("class", "btn btn-danger btn-sm")?;
        btn.set_text_content(Some("Delete"));
        td.append_child(&btn)?;
        tr.append_child(&td)?;

        left_tbody.append_child(&tr)?;
    }

    Ok(())
}

到了这里,关于Rust Web 全栈开发之编写 WebAssembly 应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 快速配置 Rust 开发环境并编写一个小应用

    安装: curl --proto \\\'=https\\\' --tlsv1.2 -sSf https://sh.rustup.rs | sh 更新: Rust 的升级非常频繁. 如果安装 Rustup 后已有一段时间,那么很可能 Rust 版本已经过时, 运行 rustup update 获取最新版本的 Rust rustc:编译Rust程序 rustc只适合简单的Rust程序,较大型的项目还是推荐使用Cargo Cargo:Rust 的构建

    2024年02月16日
    浏览(28)
  • 前端Rust开发WebAssembly与Swc插件快速入门

    现代前端对速度的追求已经进入二进制工具时代,Rust 开发成为每个人的必修课。 一般我们将常见的前端 Rust 开发分为以下几类,难度由上至下递增: 开发 wasm 。 开发 swc 插件。 开发代码处理工具。 我们将默认读者具备最简单的 Rust 知识,进行快速入门介绍。 开发 wasm 意义

    2024年02月12日
    浏览(19)
  • 〖Web全栈开发⑤〗— CSS基础

    🏘️🏘️个人简介:以山河作礼。 🎖️🎖️: Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主 🎁🎁:Web全栈开发专栏:《Web全栈开发》免费专栏,欢迎阅读! CSS 的意思为 Cascading Style Sheets,中文名是层叠样式表。 CSS 是由大名鼎鼎的 W3C 中 CSS 工作组来发布以

    2024年02月09日
    浏览(27)
  • 【 Python 全栈开发 - WEB开发篇 - 26 】Javascript 基础

    Javascript 是一种动态的、基于对象的编程语言,通常用于网页的客户端脚本编程。它可以在网页上实现交互效果、动态效果、表单验证、数据处理等功能。 学习 Javascript 可以通过以下途径: 在线教程:像 w3schools、MDN 等网站提供了详细的 Javascript 教程和示例代码。 书籍:可以

    2024年02月08日
    浏览(19)
  • 【 Python 全栈开发 - WEB开发篇 - 21 】进程与线程

    进程和线程都是计算机中用来实现多任务并发的机制,但它们有区别和联系。 区别: 定义不同:进程是操作系统分配资源的基本单位,是程序执行时的一个实例,包括代码、数据和资源,可以看成是程序的一次执行过程。而线程是进程内的一个执行单元,是程序执行流的最

    2024年02月08日
    浏览(19)
  • 前端面试:【Angular】打造强大Web应用的全栈框架

    嗨,亲爱的Angular探险家!在前端开发的旅程中,有一个全栈框架,那就是 Angular 。Angular提供了模块化、组件化、依赖注入、路由和RxJS等特性,助力你构建强大、可扩展的Web应用。 1. 什么是Angular? Angular是一个由Google开发的JavaScript框架,用于构建现代Web应用。它是一个全栈

    2024年02月11日
    浏览(20)
  • 【保姆级教程】如何用Rust编写一个ChatGPT桌面应用

    原因实在太多,我们需要便捷地导出记录,需要在回答长度超长的时候自动加上“继续”,需要收藏一些很酷很实用的prompt...... (首先我假设你是一名如我一样习惯用IDEA开发的java仔) 效率高、资源占用量低。 安全性高:Rust 是一种内存安全的语言,其所有操作都经过系统级

    2024年02月04日
    浏览(20)
  • 〖Web全栈开发①〗—网络编程基础(上)

    🏘️🏘️个人简介:以山河作礼。 🎖️🎖️: Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主 📌 计算机网络 : 📜📜计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网

    2024年02月05日
    浏览(21)
  • 〖Web全栈开发②〗—网络编程基础(下)

    🏘️🏘️个人简介:以山河作礼。 🎖️🎖️: Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主 🎁🎁:Web全栈开发专栏:《Web全栈开发》免费专栏,欢迎阅读! 🎯 学习目标 能够知道TCP客户端程序的开发流程 1. TCP 网络应用程序开发流程的介绍 TCP 网络应用程

    2024年02月04日
    浏览(14)
  • 〖Web全栈开发③〗—HTTP协议和静态web服务器

    🏘️🏘️个人简介:以山河作礼。 🎖️🎖️: Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主 🎁🎁:Web全栈开发专栏:《Web全栈开发》免费专栏,欢迎阅读! TCP (Transmission Control Protocol) 是在互联网协议(IP)上的一种基于连接(面向连接)的传输层协议 。数据

    2024年02月05日
    浏览(19)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包