如何将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目录中。

Screenshot 2023-09-20 at 16.39.38.png

查看这些文件的内容很有趣,但重要的是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
Screenshot 2023-09-20 at 16.52.24.png

非常好!

不过,这个演示根本无法给我带来任何感动!

我对WASM的大小感到担心,感觉14KB太大了。

Screenshot 2023-09-20 at 16.52.51.png

这个问题有很多地方可以削减,所以我们想要进行削减。
让用户下载14kb来返回”Hello World”是很愚蠢的。

总结

我介绍了一种在Angular服务中使用WASM的方法,您觉得怎么样?

即使演示内容没有令人感动,但是想象它所能带来的可能性,我感到非常激动。由于Angular非常适用于商业工具方面的用途,如果能与WASM一起使用,可能会大大辅助解决难题。

与RxJS的兼容性

如果实现一个满足RxJS接口的Rust trait,似乎可以直接调用.subscribe。我得试试看!

bannerAds