如何将Rust的WASM作为Angular服务使用
嘿嘿!奧斯汀好啊!
概述
我用Rust和wasm_bindgen制作了一个可以作为Angular服务使用的WASM的方法,现在将轻松地介绍给大家。
为什么选择在Angular中使用WASM?
有些读者可能会质疑为什么要在Angular中使用WASM,原因可能是想追求性能,因此我们假设这样的想法,并进行讨论。
Angular 的性能确实较差。特别是首次渲染(即在 DOM 中呈现)所需的时间非常糟糕。尽管加载后的性能比 React 好,但并不是特别出色。
然而,Angular适用于业务应用程序,并且在这些业务应用程序中,流畅运行非常重要。在我之前工作的公司中,我们使用Angular进行应用程序开发,但性能是最大的问题。更准确地说,我们面临的是JavaScript内存管理问题,而不是使用Angular本身有问题。
那个应用程序是在终端设备上进行各种基于内存的计算,而不是在服务器上进行的,这时候如果有WASM就正好可以派上用场了,我感到非常遗憾。如果我仍在那家公司工作,我肯定会提出WASM来使用(还有想要将后端服务微服务化的遗憾至今)。
Rust + WASM可以克服Angular和JavaScript的最大困难——内存管理。因此,回答问题,为什么要在Angular中使用WASM,是因为WASM可以极大地弥补Angular的弱点。
设置项目
如果您没有安装Angular的CLI,请进行安装。
那么,我会创建一个新的Angular项目。
ng new wasm-ng
让我们进入其中创建服务。请进入文件夹。
ng g service wasm/wasm
我們還要創建一個Cargo.toml文件。
cd wasm-ng
touch Cargo.toml
我将Workspace的设置放入了上述的Cargo.toml,以便可以创建多个WASM服务。先把最初的包位置放进去。
[workspace]
members = ["src/app/wasm.service/wasm_service"]
创建 Rust 的 Crate。也许将其命名为 count_service_wasm 等更直观一些可能更好。
cargo new src/app/wasm.service/wasm_service --lib
目前暂时的准备工作已经完成。
添加 wasm_bindgen
有一个很棒的Crate称为”wasm_bindgen”,可以让您舒适地将使用Rust编译的代码作为WASM在JavaScript中使用。使用它可以轻松转换Rust类型和JavaScript类型。您可以用Rust写Rust的方式,然后在JavaScript中以JavaScript的方式使用它,它可以帮助您实现这样的效果。
要执行以下命令来进行追加。
cargo add -p wasm_service wasm-bindgen
然后,我们需要对 `src/app/wasm.service/wasm_service/Cargo.toml` 进行少许修改。我们需要在 Rust 的库配置中添加 cdylib(C Dynamic Lib)。因为 wasm-bindgen 是被用于 Rust 以外的库(WASM)中。
[package]
name = "wasm_service"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2.87"
在Rust中实现服务方法。
首先,我们将从 Rust 的实现开始。需要包括 wasm_bindgen 的前言。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct _WasmService {}
我选择将_WasmService命名为Wasm服务,原因稍后会解释。
使用#[wasm_bindgen]宏,struct会被转换为与JavaScript的class相当的形式。然而,需要注意的是,诸如new _WasmService()的构造函数无法直接调用!
为了能够调用new _WasmService()的构造函数。
当我研究 wasm_bindgen 的文档时,我找到了一种方法!
如果在struct的一个方法上添加#[wasm_bindgen(constructor)],它会帮助我们编译,使得在JavaScript中可以使用new关键字!
#[wasm_bindgen]
impl _WasmService {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {}
}
}
实现想要在Angular中使用的方法。
在这里我们会实现想要在Angular服务中使用的方法。在本文章中,我们仅仅实现了一个返回简单问候语的方法并结束了,但是读者们可尽情在这个地方试验有趣的内容!
请注意,在这里创建的struct属性(self.*)将存储在WASM内存中。如果实施导致从WASM边界复制或移动,将对性能造成打击。虽然有点难以理解,但由于本文的范围超出了解释WASM内存的机制,请如有兴趣,务必阅读有关WASM内存的内容。
本文将按照以下的实施步骤进行!
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct _WasmService {}
#[wasm_bindgen]
impl _WasmService {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {}
}
pub fn greet(&self) -> String {
String::from("Hello World")
}
}
安装 wasm-pack 并将 Rust 编译为 WASM。
建议使用wasm-pack工具,它可以简化将Rust代码编译为WASM的过程,因为直接使用cargo build编译代码并不可行。
只需一个选项:即使可以通过 `–target wasm32-unknown-unknown` 用 Cargo 构建为 wasm32-unknown-unknown,但为了在 JavaScript 中使用,需要额外的步骤,所以使用 wasm-pack 是比较好的选择!
安装请点击这里。
安装后,执行以下命令进行构建。
cd src/app/wasm.service/wasm_service
wasm-pack build
然后,将编译成各种文件并存放在pkg目录中。

查看这些文件的内容很有趣,但重要的是src/app/wasm.service/wasm_service/pkg/wasm_service.d.ts。这里列出了我们实现的pub方法和属性,并且友好地提供了它们在JavaScript/TypeScript中的类型和使用方式。
/* tslint:disable */
/* eslint-disable */
/**
*/
export class _WasmService {
free(): void;
/**
*/
constructor();
/**
* @returns {string}
*/
greet(): string;
}
好的!
将其装饰为Angular的服务.
我想使用@Injectable这个常见的装饰器将Angular的服务注入进来,但据我所知,不可能在导入的类中添加装饰器。至少在TypeScript中,如果想使用装饰器,需要重新定义类,因此我使用extends再次导出类。为此,在Rust端添加了下划线(_)。
import { Injectable } from '@angular/core';
import { _WasmService } from './wasm_service/pkg/wasm_service';
@Injectable({
providedIn: 'root',
})
export class WasmService extends _WasmService {}
可能有些熟悉JavaScript的WASM的人会对这一点感到困惑,为什么我们可以像ESM一样导入WASM呢?这可能并不奇怪,我认为这是由于Webpack的工作所致。我最初对此感到有些担心,因为我想可能需要进行一些配置,但当我不抱太大希望地尝试后,发现并没有问题。最近的Webpack似乎默认提供了WASM的ESM填充。
目前有一个提案,即将把WASM作为ESM来使用。
尝试在AppComponent中使用
作为最后的更改,将默认的AppComponent的title属性更改为greet()。
import { Component, inject } from '@angular/core';
import { WasmService } from './wasm.service/wasm.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
wasmService = inject(WasmService);
title = this.wasmService.greet();
}
让我们启动Angular服务器来看看。
这是一个令人紧张的瞬间,不知道会不会顺利!
ng serve

非常好!
不过,这个演示根本无法给我带来任何感动!
我对WASM的大小感到担心,感觉14KB太大了。

这个问题有很多地方可以削减,所以我们想要进行削减。
让用户下载14kb来返回”Hello World”是很愚蠢的。
总结
我介绍了一种在Angular服务中使用WASM的方法,您觉得怎么样?
即使演示内容没有令人感动,但是想象它所能带来的可能性,我感到非常激动。由于Angular非常适用于商业工具方面的用途,如果能与WASM一起使用,可能会大大辅助解决难题。
与RxJS的兼容性
如果实现一个满足RxJS接口的Rust trait,似乎可以直接调用.subscribe。我得试试看!