# Pake：用 Rust 和 Tauri 把网页打包成桌面应用

> Pake 用 Rust 和 Tauri 把网页打包成体积小、速度快的桌面应用。这篇来自一次跨端技术分享，记录 Pake 开源过程中的思考和经验，以及我在前端、Rust 和技术产品化上的一些想法。

- 发布日期: 2023-08-03
- Author: Tw93
- HTML: https://tw93.fun/2023-08-03/pake.html
- English translation: https://tw93.fun/en/2023-08-03/pake.md
- 全部文章: https://tw93.fun/posts.md

---

上周四在掘金的跨端技术分享上的一个主题，将 [**Pake**](https://github.com/tw93/pake) 开源过程中的一些思考和经验分享给大家，同时也聊了聊前端、Rust、开源、技术产品化的一些自己想法，欢迎感兴趣的同学一起交流，有没有讲清晰的地方辛苦直接指出。

本文分成两部分，第一部分是分享的 Keynote 以及视频，第二部分是我的分享学习笔记，里面会有一些关于 Rust 更详细的知识。

## 分享文档

> 键盘左右可切换，PC 可右下角全屏按钮可全屏查看，鼠标移动到左侧边栏可查看目录，视频可见 [Youtube](https://www.youtube.com/watch?v=LRMdcW48_mA)。

{::nomarkdown}

<div class="keynote-container">
<iframe id="postIframe" src="/images/keynote/2023-08-03-pake/index.html" title="Pake-利用 Rust 轻松构建轻量级应用" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<button id="fullscreenBtn" class="fullscreen-btn">全屏</button>
</div>
{:/nomarkdown}

## 自问自答

### Rust 中的内存管理原理

- 在 Rust 中，内存管理是通过所有权（Ownership）、借用（Borrowing）和生命周期（Lifetimes）这三个概念来实现的。这种机制使得 Rust 在没有垃圾回收的情况下也能保证内存安全。

- **所有权**：在 Rust 中，每一个值都有一个被称为其所有者（owner）的变量。值在任何时候都只能有一个所有者。当所有者（变量）离开作用域，这个值将被丢弃（drop）。

- **借用**：在 Rust 中，借用是一种让你访问数据但不获取其所有权的机制。当你创建一个引用时，你就是在借用数据，Rust 有两种类型的借用：可变借用和不可变借用。不可变借用用 `&` 符号表示，可变借用用 `&mut` 表示。不可变借用允许同时有多个引用，但他们只能读取数据，不能修改。可变借用只允许有一个引用，但是可以修改数据。

### Rust 中的内存安全和线程安全怎么理解？

- **内存安全**：Rust 使用所有权系统和借用检查来确保内存安全。每个值在 Rust 中都有一个称为其“所有者”的变量。值只能有一个所有者，当所有者离开作用域时，值会被自动清理。这可以避免诸如空指针解引用、双重释放、悬垂指针等问题。另外，Rust 的借用检查器确保了引用的有效性。在任何给定时间，要么只能有一个可变引用（用于读写数据），要么只能有多个不可变引用（用于读取数据），但不能同时有这两种引用。这个规则阻止了数据竞争和其他并发相关的内存错误。

- **线程安全**：Rust 的所有权和借用规则同时也保证了线程安全。由于在任何时刻，一个值要么只有一个可变引用，要么只有多个不可变引用，这确保了在没有加锁的情况下，也不会出现数据竞争的问题。另外，Rust 的类型系统和标准库中还提供了一些并发抽象，如`Arc`（原子引用计数类型）和`Mutex`（互斥锁），它们提供了线程间共享状态的安全方式。

- 通过这些规则和工具，Rust 能够在编译期就捕捉到许多常见的错误，使得开发者可以编写出更安全、更可靠的代码。

### 介绍一下 Rustc

- Rustc 是 Rust 编程语言的官方编译器，它把 Rust 源代码编译成可执行文件或库，用 Rust 语言自身编写的，它使用了一种称为 LLVM 的底层技术来生成机器代码。

- 在实际的 Rust 开发中，通过 cargo 这个包管理器和构建工具来使用，会自动调用 rustc 来编译你的代码，并处理一些其他的事情，如下载依赖库、运行测试和生成文档等。

### Tauri 里面的浏览器引擎

- 没有使用直接为每个应用单独集成一个 chromium 环境，而是直接使用操作系统内置的浏览器引擎执行 Web APP，从趋势上来看，操作系统内置的浏览器生态是在不断进步的，tauri 自然也能享受到这些红利。

- Windows 中使用 WebView2，基于 Microsoft Edge 和 Chromium 的 WebView2，从 windows7 开始内置在里面。

- MacOS 中使用 WKWebView，跟着 MacOS 系统的版本走，类比 safari 里面的效果。

- Linux 中使用 WebKitGTK，由于发行版很多，其实最后还是挺乱的。

- **产生的问题点**：1、存在很多 bug，这些 bug 大多不直接来自于 tauri，而是来自于各平台下的 webview 接口，因此 bug 的解决周期也都相当长，目前很难直接用于生产环境；2、文档和社区还是不够完善，大多数时间还需要自己扒代码。3、在一些旧的设备上，窗口和 webview 创建的耗时仍然不可忽视。4、对于前端网页而言，实际上并没有完全抹平平台差异，前端开发时仍需要考虑平台兼容性。

### Tauri 里面的通信解释

- **优势**：这种通信方式使得你可以利用 Rust 的性能和安全性，同时享受 JavaScript 带来的便利和灵活性。你可以将计算密集或需要访问底层 API 的任务交给 Rust 处理，而将界面和交互逻辑等任务交给 JavaScript 处理。

- **指令调用**：这种通信方式是同步的或者异步的。在 JavaScript 端，你可以通过 window.tauri.invoke 函数调用在 Rust 端定义的函数。这个函数接收两个参数：第一个参数是 Rust 函数的名称，第二个参数是传递给 Rust 函数的参数。这个函数会返回一个 Promise，当 Rust 函数处理完成后，Promise 会被 resolve。这种通信方式通常用于处理需要立即返回结果的情况。

- **事件通信**：这种通信方式是异步的。在 JavaScript 端，你可以通过 window.tauri.listen 函数监听在 Rust 端发出的事件。这个函数接收一个参数，即事件的名称。当对应的事件被触发时，你可以在 JavaScript 端处理这个事件。相应地，在 Rust 端，你可以通过 tauri::event::emit 函数发出事件。这种通信方式通常用于处理需要在后台运行或需要等待一段时间的任务。

### 为什么 WebAssembly 和 Rust 结合？

- **性能强**：Rust 是一种系统级别的编程语言，它提供了很多低级的控制，包括内存管理和线程管理等，这使得 Rust 代码能够运行得很快。此外，Rust 语言的设计还强调了零成本抽象，意味着你可以写出高级和抽象的代码，但编译器会将其优化为低级的、效率最高的机器代码。当这个代码被编译为 WebAssembly 后，这种性能优势就可以被传递到 Web 浏览器。

- **体积小**：Rust 编译器（rustc）使用了一种称为 LLVM 的编译器基础架构，它能够生成高效的、体积小的代码。当你把 Rust 编译为 WebAssembly 时，你可以使用一些特定的编译选项和工具（例如`wasm-opt`）来进一步压缩和优化 WebAssembly 二进制文件，使其更适合在网络上分发。

- **互操作**：Rust 和 WebAssembly 都是设计为与其他语言和技术互操作的。Rust 有一套完善的 C FFI，可以轻松地和 C 语言库一起工作，相当于最后可以在前端调用 C。而 WebAssembly 被设计为可以和 JavaScript 一起工作，甚至可以直接在 Web 浏览器中访问 DOM。因此，你可以在 Rust 中编写高性能的算法，然后在 JavaScript 中调用这个算法。

- **工具链**：Rust 有一套非常强大的工具链，包括`cargo`包管理器和构建系统，以及`rustup` Rust 版本管理器。对于 WebAssembly，Rust 团队和社区创建了一些专门的工具和库，如`wasm-bindgen`和`wasm-pack`，它们可以让你更容易地把 Rust 编译为 WebAssembly，以及在 JavaScript 中调用 Rust 函数。此外，还有一些其他工具，例如`wasm-opt`和`wasm-gc`，可以帮助你优化和减小 WebAssembly 文件的体积。

### Rust & WebAssembly 周边工具

- **wasm-bindgen**：这是一个 Rust 库和命令行工具，用于在 Rust 和 JavaScript 之间进行互操作。它可以生成所需的胶水代码，这样你就可以在 JavaScript 中直接调用 Rust 函数，或者在 Rust 中直接调用 JavaScript API。

- **wasm-pack**：这是一个命令行工具，用于构建、测试和发布 Rust 生成的 WebAssembly 包。它会自动调用`wasm-bindgen`生成 glue 代码，并处理一些其他的事情，如优化 WebAssembly 二进制、生成包描述文件（如`package.json`）等。

- **wasm-opt**：这是`Binaryen`工具套件的一部分，用于优化 WebAssembly 二进制。它可以通过删除无用的代码、重排指令、合并同类项等方式来减小 WebAssembly 文件的体积，提高运行速度。

- **wasm-gc**：这是一个命令行工具，用于收集并删除 WebAssembly 二进制中未使用的函数和数据。这可以帮助你减小 WebAssembly 文件的体积。

### 火狐用 Rust 和 WebAssembly 提升 Source Maps 的性能

- 使用 Rust 语言进行编码，然后用 WebAssembly 进行编译打包后替换原有 JavaScript 中性能敏感的部分（解析和查找功能），相比已有的快 6 倍，

- WebAssembly 以二进制形式运行在 Web 浏览器底层，可以直接操作一大块连续的储存 buffer 字节，目标是获得或者逼近原始指令的运行速度，跟原始指令相比 只相差 1.5x 了

- 因为缺乏垃圾收集器，要编译成 WebAssembly 语言仅限那些没有运行时和垃圾采集器的编程语言，除非把控制器和运行时也编译成 WebAssembly，Rust 是一种更加安全和高效的系统编程语言。它的内存管理更加安全，不依赖于垃圾回收机制，而是允许你通过静态追踪函数 ownership 和 borrowing 这两个方法来申请和释放内存，不用为了编译成 WebAssembly 做额外的工作

- 相比于 C 和 C++，Rust 库更加容易构建、容易共享、打包简单和容易提取公共部分，而且自成文档。Rust 有诸如 rustup，cargo 和 crates.io 的完整生态系统，类比 Node 体系 npm；

### Cloudflare 将 Rust 编译成 WASM 在 Cloudflare Workers 中调用它

- 相当于在 Cloudflare 的 Workers 中支持 WebAssembly，然后对于 Rust 开发者而言可以将代码编译成 WASM，上传到他们的服务数据中心，并像调用 JavaScript 函数一样轻松地调用这些函数。

- 此外 wasm-pack 允许你将 Rust 编译为 WebAssembly，并且生成 JavaScript 对象与 Rust 对象之间的绑定

### Dropbox 用 Rust 编译成 WASM 轻松地 DivANS 编解码器嵌入到网页中

- DivANS 可以理解成 Dropbox 核心的一个压缩技术，压缩比例相对于 zlib 节省了 12%，相对于其他算法在最大设置下节省了超过 2.5%。

- Rust 程序可以很好地嵌入到任何支持 C 外部函数接口 FFI 的编程语言中，甚至可以通过该 C FFI 在运行时选择内存分配器。这些特性使得将 DivANS 编解码器嵌入到使用 WASM 的网页中变得非常容易，就像上面展示的那样。

- 外部函数接口（Foreign Function Interface，FFI）是一种编程机制，它允许一个编程语言调用另一个编程语言编写的函数或方法。

### Shopify 用 Rust 实现模板引擎，编译成 WASM，提高运行效率

- 原先 Liquid 模板引擎是用 Ruby 编写的，然后在服务器端运行。随着 Shopify 平台的扩大，这个方案的性能和安全性问题开始显现出来。

- 为了解决这个问题，Shopify 决定用 Rust 重新实现 Liquid 模板引擎，并将其编译为 WebAssembly。这样，Liquid 模板就可以在浏览器中运行，而不需要在服务器端运行。这不仅减少了服务器的负载，还大大提高了模板渲染的速度，对于需要处理大量用户交互和动态内容的 Web 应用来说，是一个非常有价值的特性。

### Linux 内核增加对 Rust 作为第二语言的支持

- Linux 内核是一个庞大且复杂的软件项目，自诞生以来，一直以 C 语言作为主要的编程语言。然而，C 语言的一些特性，比如手动内存管理和缺乏类型安全，使得编写正确、安全的代码变得更加困难。

- Rust 是一种系统编程语言，其设计目标之一就是提供内存安全而无需垃圾收集，这使得 Rust 非常适合系统编程和嵌入式编程。Rust 的所有权模型、借用检查和其他一些特性能够在编译时捕获许多常见的错误，如空指针解引用、缓冲区溢出等。

- 在 Linux 内核中引入 Rust 编程语言的想法，旨在通过 Rust 的这些特性，来提高内核代码的安全性和可靠性。这个想法得到了许多开发者的支持，包括 Linux 的创始人 Linus Torvalds。

- 实际上，Linux 内核已经有一些组件开始使用 Rust 编写。例如，Google 的 Project Zero 的一些成员已经开始尝试使用 Rust 重写内核组件，以解决一些潜在的内存安全问题。并且，Rust 编程语言已经被包含在 Linux 内核的源代码树中，作为一个可选的编程语言。
