Rust 编译为 WebAssembly 在前端项目中使用(二)

简介: Rust 编译为 WebAssembly 在前端项目中使用(二)

2.6 构建Web服务器

既然,我们通过上述的魔法,将Rust程序编译为了可以在浏览器环境下引用执行的格式。为了这口醋,我们还专门包顿饺子


我们需要一个Web服务器来测试我们的WebAssembly程序。我们将使用Webpack,我们需要创建三个文件:index.jspackage.jsonwebpack.config.js

下面的代码,我们最熟悉不过了,就不解释了。

index.js

// 直接引入了,刚才编译后的文件
const rust = import('./pkg/hello_world.js');
rust
  .then(m => m.helloworld('World!'))
  .catch(console.error);

package.json

{
  "scripts": {
    "build": "webpack",
    "serve": "webpack-dev-server"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "0.4.2",
    "text-encoding": "^0.7.0",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.29.4",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.0"
  }
}

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js',
    },
    plugins: [
        new HtmlWebpackPlugin(),
        new WasmPackPlugin({
            crateDirectory: path.resolve(__dirname, ".")
        }),
        // 让这个示例在不包含`TextEncoder`或`TextDecoder`的Edge浏览器中正常工作。
        new webpack.ProvidePlugin({
            TextDecoder: ['text-encoding', 'TextDecoder'],
            TextEncoder: ['text-encoding', 'TextEncoder']
        })
    ],
    mode: 'development'
};

安装指定的依赖。

npm install webpack --save-dev
npm install webpack-cli --save-dev
npm install webpack-dev-server --save-dev
npm install html-webpack-plugin --save-dev
npm install @wasm-tool/wasm-pack-plugin --save-dev
npm install text-encoding --save-dev

2.7 构建&运行程序

使用npm run build构建程序。

使用npm run serve运行Hello World程序

在浏览器中打开localhost:8080,我们将看到一个显示 Hello World! 的弹窗。

image.png

到目前为止,我们已经构建了一个wasm并且能够和js实现功能交互的项目。其实,到这里已经完成了,我们这篇文章的使命。但是,在这里戛然而止,感觉缺失点啥。所以,我们继续深挖上面的项目的实现原理。


3. 原理探析

在使用cargowasm_bindgen编译源代码时,会在pkg文件中自动生成以下文件:

  • "hello_world_bg.wasm"
  • "hello_world.js"
  • "hello_world.d.ts"
  • "package.json"

这些文件也可以通过使用以下wasm-bindgen命令手动生成

bash

复制代码

wasm-bindgen target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir ./pkg

浏览器调用顺序

以下显示了当我们在浏览器中访问localhost:8080时发生的函数调用序列。

  1. index.js
  2. hello_world.js (调用hello_world_bg.js)
  3. helloworld_bg.wasm

index.js

const rust = import('./pkg/hello_world.js');
rust
  .then(m => m.helloworld('World!'))
  .catch(console.error);

index.js 导入了 hello_world.js 并调用其中的 helloworld 函数。

hello_world.js

下面是hello_world.js的内容,在其中它调用了helloworld_bg.wasm

import * as wasm from "./hello_world_bg.wasm";
import { __wbg_set_wasm } from "./hello_world_bg.js";
__wbg_set_wasm(wasm);
export * from "./hello_world_bg.js";

hello_world_bg.js

// ...省去了部分代码
export function helloworld(name) {
    const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
    const len0 = WASM_VECTOR_LEN;
    wasm.helloworld(ptr0, len0);
}

hello_world_bg.js 文件是由wasm-bindgen自动生成的,它包含了用于将DOMJavaScript函数导入到Rust中的JavaScript粘合代码。它还在生成的WebAssembly函数上向JavaScript公开了API。

Rust WebAssembly专注于将WebAssembly与现有的JavaScript应用程序集成在一起。为了实现这一目标,我们需要在JavaScriptWebAssembly函数之间传递不同的值、对象或结构。这并不容易,因为需要协调两个不同系统的不同对象类型

更糟糕的是,当前WebAssembly仅支持整数浮点数,不支持字符串。这意味着我们不能简单地将字符串传递给WebAssembly函数。

要将字符串传递给WebAssembly,我们需要将字符串转换为数字(请注意在webpack.config.js中指定的TextEncoderAPI),将这些数字放入WebAssembly的内存空间中,最后返回一个指向字符串的指针WebAssembly函数,以便在JavaScript中使用它。在最后,我们需要释放WebAssembly使用的字符串内存空间。

如果我们查看上面的JavaScript代码,这正是自动执行的操作。helloworld函数首先调用passStringToWasm

  • 这个函数在WebAssembly创建一些内存空间,将我们的字符串转换为数字,将数字写入内存空间,并返回一个指向字符串的指针。

image.png

  • 然后将指针传递给wasm.helloworld来执行JavaScriptalert。最后,wasm.__wbindgen_free释放了内存。
  1. image.png
  2. image.png
  3. image.png
  4. image.png

如果只是传递一个简单的字符串,我们可能可以自己处理,但考虑到当涉及到更复杂的对象和结构时,这个工作会很快变得非常复杂。这说明了wasm-bindgenRust WebAssembly开发中的重要性。

反编译wasm到txt

在前面的步骤中,我们注意到wasm-bindgen生成了一个hello_world.js文件,其中的函数调用到我们生成的hello_world_bg.wasm中的WebAssembly代码。

基本上,hello_world.js充当其他JavaScript(如index.js)与生成的WebAssemblyhelloworld_bg.wasm之间的桥梁。

我们可以通过输入以下命令进一步探索helloworld_bg.wasm

wasm2wat hello_world_bg.wasm > hello_world.txt

这个命令使用wabtWebAssembly转换为WebAssembly文本格式,并将其保存到一个hello_world.txt文件中。打开helloworld.txt文件,然后查找$helloworld函数。这是我们在src/lib.rs中定义的helloworld函数的生成WebAssembly函数。

$helloworld函数

image.png

helloworld.txt中查找以下行:

(export "helloworld" (func $helloworld))

这一行导出了wasm.helloworld供宿主调用的WebAssembly函数。我们通过hello_world_bg.js中的wasm.helloworld来调用这个WebAssembly函数。

image.png

接下来,查找以下行:

(import "./hello_world_bg.js" "__wbg_alert_9ea5a791b0d4c7a3" (func $hello_world::alert::__wbg_alert_9ea5a791b0d4c7a3::h93c656ecd0e94e40 (type 4)))

这对应于在hello_world_bg.js中生成的以下JavaScript函数:

export function __wbg_alert_9ea5a791b0d4c7a3() { return logError(function (arg0, arg1) {
    alert(getStringFromWasm0(arg0, arg1));
}, arguments) };

这是wasm-bindgen提供的粘合部分,帮助我们在WebAssembly中使用JavaScript函数或DOM

最后,让我们看看wasm-bindgen生成的其他文件。

hello_world.d.ts

这个.d.ts文件包含JavaScript粘合的TypeScript类型声明,如果我们的现有JavaScript应用程序正在使用TypeScript,它会很有用。我们可以对调用WebAssembly函数进行类型检查,或者让我们的IDE提供自动完成。如果我们不使用TypeScript,可以安全地忽略这个文件。

package.json

package.json文件包含有关生成的JavaScriptWebAssembly包的元数据。它会自动从我们的Rust代码中填充所有npm依赖项,并使我们能够发布到npm


4. 内容拓展

再次看一下以下代码:

hello_world_bg.js

function helloworld(name) {
    const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
    const len0 = WASM_VECTOR_LEN;
    wasm.helloworld(ptr0, len0);
}

该代码用于分配和释放内存,这一切都是由程序自动处理的。不需要垃圾回收器或完整的框架引擎,使得使用Rust编写的WebAssembly应用程序或模块变得小巧且优化。其他需要垃圾回收器的语言将需要包含用于其底层框架引擎的wasm代码。因此,无论它们有多么优化,其大小都不会小于Rust提供的大小。这使得Rust WebAssembly成为一个不错的选择,如果我们需要将小型WebAssembly模块集成或注入到JavaScript Web应用程序中。

除了Hello World之外,还有一些其他需要注意的事项:

web-sys

使用wasm-bindgen,我们可以通过使用externRust WebAssembly中调用JavaScript函数。请记住src/lib.rs中的以下代码:

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

Web具有大量API,从DOM操作到WebGL再到Web Audio等等。因此,如果我们的Rust WebAssembly程序增长,并且我们需要对Web API进行多次不同的调用,我们将需要花时间编写大量的extern代码。

web-sys充当wasm-bindgen的前端,为所有Web API提供原始绑定。

这意味着如果我们使用web-sys,可以节省时间,而不必编写extern代码。

image.png

引入web-sys

web-sys添加为Cargo.toml的依赖项:

[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
features = [
]

为了保持构建速度非常快,web-sys将每个Web接口都封装在一个Cargo特性后面。在API文档中找到我们要使用的类型或方法;它将列出必须启用的特性才能访问该API。

例如,如果我们要查找window.resizeTo函数,我们会在API文档中搜索resizeTo。我们将找到web_sys::Window::resize_to函数,它需要启用Window特性。要访问该函数,我们在Cargo.toml中启用Window特性:

[dependencies.web-sys]
version = "0.3"
features = [
  "Window"
]

调用这个方法:

use wasm_bindgen::prelude::*;
use web_sys::Window;
#[wasm_bindgen]
pub fn make_the_window_small() {
    // 调整窗口大小为500px x 500px。
    let window = web_sys::window().unwrap();
    window.resize_to(500, 500)
        .expect("无法调整窗口大小");
}

这段代码的目的是调整浏览器窗口的大小为500x500像素,并演示了如何使用web-sys和启用的Cargo特性来调用Web API。


后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

相关文章
|
23天前
|
JavaScript 前端开发 Docker
前端全栈之路Deno篇(二):几行代码打包后接近100M?别慌,带你掌握Deno2.0的安装到项目构建全流程、剖析构建物并了解其好处
在使用 Deno 构建项目时,生成的可执行文件体积较大,通常接近 100 MB,而 Node.js 构建的项目体积则要小得多。这是由于 Deno 包含了完整的 V8 引擎和运行时,使其能够在目标设备上独立运行,无需额外安装依赖。尽管体积较大,但 Deno 提供了更好的安全性和部署便利性。通过裁剪功能、使用压缩工具等方法,可以优化可执行文件的体积。
前端全栈之路Deno篇(二):几行代码打包后接近100M?别慌,带你掌握Deno2.0的安装到项目构建全流程、剖析构建物并了解其好处
|
29天前
|
机器学习/深度学习 前端开发 JavaScript
WebAssembly:让前端性能突破极限的秘密武器
WebAssembly(简称 WASM)作为前端开发的性能加速器,能够让代码像 C++ 一样在浏览器中高速运行,突破了 JavaScript 的性能瓶颈。本文详细介绍了 WebAssembly 的概念、工作原理以及其在前端性能提升中的关键作用。通过与 JavaScript 的配合,WASM 让复杂运算如图像处理、3D 渲染、机器学习等在浏览器中流畅运行。文章还探讨了如何逐步集成 WASM,展示其在网页游戏、高计算任务中的实际应用。WebAssembly 为前端开发者提供了新的可能性,是提升网页性能、优化用户体验的关键工具。
62 2
WebAssembly:让前端性能突破极限的秘密武器
|
12天前
|
前端开发 Unix 测试技术
揭秘!前端大牛们如何高效管理项目,确保按时交付高质量作品!
【10月更文挑战第30天】前端开发项目涉及从需求分析到最终交付的多个环节。本文解答了如何制定合理项目计划、提高团队协作效率、确保代码质量和应对项目风险等问题,帮助你学习前端大牛们的项目管理技巧,确保按时交付高质量的作品。
26 2
|
16天前
|
Rust 前端开发 JavaScript
前端性能革命:WebAssembly在高性能计算中的应用探索
【10月更文挑战第26天】随着Web应用功能的日益复杂,传统JavaScript解释执行模式逐渐成为性能瓶颈。WebAssembly(Wasm)应运而生,作为一种二进制代码格式,支持C/C++、Rust等语言编写的代码在浏览器中高效运行。Wasm不仅提升了应用的执行速度,还具备跨平台兼容性和安全性,显著改善了Web应用的响应速度和用户体验。
31 4
|
24天前
|
缓存 前端开发 JavaScript
前端架构思考:代码复用带来的隐形耦合,可能让大模型造轮子是更好的选择-从 CDN 依赖包被删导致个站打不开到数年前因11 行代码导致上千项目崩溃谈谈npm黑洞 - 统计下你的项目有多少个依赖吧!
最近,我的个人网站因免费CDN上的Vue.js包路径变更导致无法访问,引发了我对前端依赖管理的深刻反思。文章探讨了NPM依赖陷阱、开源库所有权与维护压力、NPM生态问题,并提出减少不必要的依赖、重视模块设计等建议,以提升前端项目的稳定性和可控性。通过“left_pad”事件及个人经历,强调了依赖管理的重要性和让大模型代替人造轮子的潜在收益
|
30天前
|
前端开发 JavaScript 开发工具
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(三)
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(三)
33 0
|
30天前
|
Web App开发 前端开发 JavaScript
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(二)
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(二)
47 0
|
30天前
|
Web App开发 移动开发 前端开发
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(一)
前端代码规范和质量是确保项目可维护性、可读性和可扩展性的关键(一)
45 0
|
30天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
127 2
|
30天前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
37 0