本文档介绍了 AFFiNE 中原生 Rust 模块如何集成到 JavaScript 前端和后端。原生模块提供了性能关键的功能,例如媒体捕获和存储操作,这些功能如果仅用 JavaScript 实现将效率低下或不可能实现。
AFFiNE 通过 Rust 模块实现原生功能,这些模块被编译成特定于平台的二进制文件,并通过各种绑定机制暴露给 JavaScript。
来源: Cargo.toml2-76 packages/frontend/native/package.json1-48 packages/backend/native/package.json1-42
AFFiNE 实现了三个主要的原生模块包
| 包 | 目的 | 主要平台 |
|---|---|---|
| @affine/native | 用于 Electron 和 Web 的前端原生模块 | macOS、Windows、Linux |
| @affine/server-native | 用于 Node.js 服务器的后端原生模块 | Linux、macOS、Windows |
| @affine/mobile-native | 移动原生模块 | Android |
来源: Cargo.toml2-11 packages/frontend/native/package.json1-48
@affine/native 包提供了以下主要功能
媒体捕获功能允许应用程序在 macOS 上录制系统应用程序的音频
主要类
ShareableContent: 管理屏幕/音频捕获权限和设备访问TappableApplication: 表示其音频可被捕获的应用程序AudioTapStream: 管理音频捕获流Application: 表示一个系统应用程序来源: packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs98-526 packages/frontend/native/media_capture/src/macos/tap_audio.rs389-458
文档存储功能提供了到 SQLite 数据库的原生接口,用于存储文档数据
主要类
DocStorage: 管理工作区的文档存储DocStoragePool: 管理多个文档存储实例SqliteConnection: 提供对 SQLite 数据库的低级访问来源: packages/frontend/native/index.d.ts26-111
@affine/server-native 包为 Node.js 后端提供了原生功能
主要功能
来源: Cargo.lock155-173 packages/backend/native/package.json1-42
AFFiNE 的移动支持使用 UniFfi 绑定生成器进行 Rust 到 Kotlin 的绑定
Android 集成使用 Mozilla 的 Rust-Android Gradle 插件来编译 Rust 到 Android 目标,并使用 UniFfi 进行绑定生成。
来源: packages/frontend/apps/android/App/build.gradle142-187 packages/frontend/apps/android/App/gradle/libs.versions.toml33-100
原生模块使用 NAPI-RS 进行 Node.js 绑定构建,负责编译 Rust 代码和生成 JavaScript 绑定
构建配置定义在多个地方
Cargo.toml: 工作区定义和 Rust 依赖package.json: NAPI-RS 构建配置和目标平台.cargo/config.toml: 特定于平台的 Rust 标志.github/actions/build-rust/action.yml: CI 构建过程来源: Cargo.toml1-88 .cargo/config.toml1-15 packages/frontend/native/package.json6-16 .github/actions/build-rust/action.yml18-91
原生模块支持多个平台和架构
| 平台 | 架构 |
|---|---|
| macOS | x86_64 (Intel),aarch64 (Apple Silicon) |
| Windows | x86_64, aarch64, ia32 |
| Linux | x86_64, aarch64 (gnu/musl 变体) |
| Android | aarch64 |
来源: packages/frontend/native/package.json8-14 packages/backend/native/package.json20-27
在运行时,会根据平台和架构动态加载合适的原生二进制文件
index.js 中的加载逻辑会尝试多个文件路径来查找适合当前平台和架构的二进制文件。如果本地找不到二进制文件,它会回退尝试单独发布的、包含特定平台二进制文件的 npm 包。
来源: packages/frontend/native/index.js66-375
原生模块通过 TypeScript 定义暴露一致的 JavaScript API
来源: packages/frontend/native/index.d.ts1-210
媒体捕获功能是一个复杂的原生模块,它与 macOS API 交互
主要实现细节
来源: packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs98-347 packages/frontend/native/media_capture/src/macos/tap_audio.rs237-326 packages/frontend/native/media_capture/src/macos/audio_buffer.rs18-141
来源:.github/actions/build-rust/action.yml18-91 .cargo/config.toml1-15
原生模块集成通过利用 Rust 作为实现语言,以及各种用于 JavaScript 集成的绑定技术,为 AFFiNE 提供了关键的性能优势,例如在媒体捕获和文档存储等操作中。该架构在跨平台兼容性和原生性能之间取得了平衡。
模块化设计允许进行特定平台的优化,同时在不同环境中保持一致的 API,使 AFFiNE 能够在以 Web 为先的应用程序架构中提供类原生的性能。