编程 Tauri 2.x 深度实战:用 3MB 安装包干掉 Electron——一次把跨平台桌面架构讲透(2026)

2026-07-14 04:44:39 +0800 CST views 10

Tauri 2.x 深度实战:用 3MB 安装包干掉 Electron——一次把跨平台桌面架构讲透(2026)

一句话结论:如果你还在用 Electron 打包一个「计算器级别」的工具却塞进去一整个 Chromium,那 2026 年真该停下来看看 Tauri 了。同样功能的应用,Electron 动辄 150MB 起步、常驻内存 300MB+,Tauri 能压到 3~8MB 安装包、常驻内存几十 MB,冷启动从数百毫秒降到毫秒级。这不是「快一点」,是「用户愿不愿意留在你磁盘里」的分水岭。

这篇文章不是入门教程的复读机。我会从架构本质讲起——为什么 Tauri 能这么小、这么快,它到底把什么东西「外包」出去了;然后深入 IPC 通信、状态管理、权限系统、Sidecar、自动更新、原生插件这些真正决定你能不能把 Demo 变成产品的硬骨头,全程配可运行代码。读完你应该能判断:你的下一个桌面项目,到底该不该上 Tauri。


一、背景:桌面开发的三次「代际战争」

桌面应用开发这些年经历了三个阶段,理解这个脉络才能明白 Tauri 站在哪。

第一代:原生 SDK。Win32/MFC、Cocoa、GTK。性能顶级、包体积小,但每个平台一套代码、一套 UI 框架、一套构建流程。一个三人小团队想同时做 Windows/macOS/Linux 三端,光维护成本就能拖垮项目。

第二代:Electron。2013 年横空出世,用「Chromium + Node.js」把 Web 技术栈搬到桌面。前端工程师瞬间能写桌面应用了,VS Code、Slack、Discord 都是它的产物。代价是什么?每个应用都自带一个完整浏览器。你的「记事本」和别人的「聊天软件」各自打包一个 Chromium,用户装十个 Electron 应用等于硬盘里躺着十个浏览器。

第三代:系统 WebView 复用。这是 Tauri 的核心洞察——操作系统本来就自带一个现代 WebView,为什么每个应用还要自己带一个?

  • Windows 有 WebView2(基于 Edge/Chromium,Win10/11 系统组件)
  • macOS 有 WKWebView(Safari 内核)
  • Linux 有 WebKitGTK

Tauri 不打包浏览器,而是调用系统已有的 WebView 渲染前端,后端逻辑用 Rust 编写。前端还是你熟悉的 React/Vue/Svelte,后端换成了内存安全、零 GC、接近 C 性能的 Rust。这一刀下去,安装包直接从「一个浏览器」缩水到「你自己的业务代码」。

Tauri 2.x 到底带来了什么

Tauri 1.x 已经验证了这条路,2.x(2024 年正式发布,2025-2026 持续迭代)则完成了几个关键跃迁:

  1. 移动端支持:一套 Rust 核心,除桌面三端外新增 iOS/Android。真正的「一次编写,五端部署」。
  2. 重构的权限系统(Capabilities + Permissions):从 1.x 粗粒度的 allowlist 升级为细粒度、可组合、可按窗口作用域的权限模型。
  3. 插件化架构:核心极致精简,文件系统、对话框、通知、HTTP 等全部拆成官方插件按需引入。
  4. IPC 性能重写:新的 IPC 通道支持原始字节流(raw payload),大数据传输不再走 JSON 序列化的性能黑洞。

二、核心架构:一个 Tauri 应用启动时到底发生了什么

先建立一张全局心智图。一个 Tauri 应用运行时由两个世界组成:

┌─────────────────────────────────────────────┐
│                  Tauri App                    │
│                                               │
│  ┌───────────────┐      IPC      ┌─────────┐ │
│  │  WebView       │◄────Bridge───►│  Rust    │ │
│  │  (前端世界)     │               │  Core    │ │
│  │                │               │ (后端世界)│ │
│  │ HTML/CSS/JS    │               │          │ │
│  │ React/Vue/...  │               │ Commands │ │
│  │ @tauri-apps/api│               │ State    │ │
│  └───────────────┘               │ Plugins  │ │
│                                   │ Window   │ │
│                                   └────┬─────┘ │
│                                        │        │
│                            ┌───────────▼──────┐ │
│                            │  OS Native APIs   │ │
│                            │ 文件/网络/托盘/通知 │ │
│                            └──────────────────┘ │
└─────────────────────────────────────────────┘

前端世界运行在系统 WebView 里,跟你在浏览器里写代码几乎一样,唯一多出来的是 @tauri-apps/api 这个桥——它让你能从 JS 调用 Rust。

后端世界是 Rust 编写的原生进程,掌管窗口生命周期、系统资源、真正的重活累活(文件批处理、加解密、本地数据库、调用系统 API)。

两个世界通过 IPC(进程间通信) 桥接。这个桥是整个 Tauri 的灵魂,后面会重点拆。

环境搭建

Tauri 2.x 的前置依赖:Rust 工具链 + Node.js(如果前端用 npm 生态)+ 各平台系统库。

# 1. 安装 Rust(如果还没有)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. 用官方脚手架创建项目(推荐 create-tauri-app)
npm create tauri-app@latest my-app
# 交互式选择:前端框架(Vue/React/Svelte/Vanilla)、包管理器、TS/JS

cd my-app
npm install

# 3. 开发模式(热重载前端 + 自动重编译 Rust)
npm run tauri dev

# 4. 生产构建(产出各平台安装包)
npm run tauri build

Linux 用户需要额外的系统依赖(以 Debian/Ubuntu 为例):

sudo apt update
sudo apt install libwebkit2gtk-4.1-dev \
  build-essential curl wget file \
  libxdo-dev libssl-dev \
  libayatana-appindicator3-dev librsvg2-dev

项目结构长这样:

my-app/
├── src/                # 前端代码(你熟悉的那套)
├── src-tauri/          # Rust 后端
│   ├── src/
│   │   ├── main.rs     # 入口
│   │   └── lib.rs      # 核心逻辑(2.x 推荐把逻辑放这,便于移动端复用)
│   ├── Cargo.toml      # Rust 依赖
│   ├── tauri.conf.json # Tauri 核心配置
│   └── capabilities/   # 权限配置(2.x 新增)
├── package.json
└── vite.config.ts

三、IPC 通信:前后端对话的四种姿势

前后端怎么说话,决定了你的应用架构上限。Tauri 提供了四种通信模式,从简单到复杂依次是:Command、Event、Channel、State。搞清楚它们的适用边界,是从「会用」到「用好」的关键。

3.1 Command:最常用的「前端调 Rust 函数」

Command 是同步/异步的请求-响应模型,适合「前端发起、后端处理、返回结果」的场景,占实际开发的 80%。

Rust 端src-tauri/src/lib.rs):

use serde::{Deserialize, Serialize};

// 简单命令:接收参数、返回字符串
#[tauri::command]
fn greet(name: &str) -> String {
    format!("你好, {}! 来自 Rust 后端的问候。", name)
}

// 带结构化参数和返回值的命令
#[derive(Debug, Serialize, Deserialize)]
struct FileStat {
    path: String,
    size_bytes: u64,
    is_dir: bool,
}

// 异步命令:涉及 IO 一律用 async,避免阻塞主线程
#[tauri::command]
async fn stat_file(path: String) -> Result<FileStat, String> {
    // Result<T, E> 是 Tauri 命令的推荐返回类型
    // Ok 会 resolve,Err 会 reject 到前端的 catch
    let meta = tokio::fs::metadata(&path)
        .await
        .map_err(|e| format!("读取文件元信息失败: {}", e))?;

    Ok(FileStat {
        path,
        size_bytes: meta.len(),
        is_dir: meta.is_dir(),
    })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        // 注册命令,前端才能调用
        .invoke_handler(tauri::generate_handler![greet, stat_file])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

前端端(TypeScript):

import { invoke } from '@tauri-apps/api/core';

// 调用简单命令
const msg = await invoke<string>('greet', { name: '茄子' });
console.log(msg); // 你好, 茄子! 来自 Rust 后端的问候。

// 调用异步命令并处理错误
interface FileStat {
  path: string;
  size_bytes: number;
  is_dir: boolean;
}

try {
  const stat = await invoke<FileStat>('stat_file', {
    path: '/Users/me/data.csv',
  });
  console.log(`文件大小: ${stat.size_bytes} 字节`);
} catch (err) {
  // Rust 里的 Err(String) 会在这里被 catch
  console.error('调用失败:', err);
}

一个高频踩坑点:Rust 端参数命名用 snake_case,但前端传参默认要用 camelCase。比如 Rust 里 fn open_file(file_path: String),前端应该传 invoke('open_file', { filePath: '...' })。Tauri 会自动做转换。如果你想禁用这个转换,可以在命令上加 #[tauri::command(rename_all = "snake_case")]

3.2 Event:Rust 主动推送给前端

Command 是前端发起的,但很多场景需要后端主动通知前端:下载进度、后台任务完成、系统事件监听。这时用 Event。

Rust 端发射事件

use tauri::{AppHandle, Emitter};
use serde::Serialize;

#[derive(Clone, Serialize)]
struct DownloadProgress {
    downloaded: u64,
    total: u64,
    percent: f64,
}

#[tauri::command]
async fn start_download(app: AppHandle, url: String) -> Result<(), String> {
    let total: u64 = 1024 * 1024 * 50; // 假设 50MB
    let mut downloaded: u64 = 0;
    let chunk: u64 = 1024 * 512;

    while downloaded < total {
        downloaded = (downloaded + chunk).min(total);
        let percent = downloaded as f64 / total as f64 * 100.0;

        // 向所有窗口广播进度事件
        app.emit("download://progress", DownloadProgress {
            downloaded,
            total,
            percent,
        }).map_err(|e| e.to_string())?;

        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    }
    Ok(())
}

前端监听事件

import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';

// 注册监听器,返回一个取消函数
const unlisten = await listen<DownloadProgress>('download://progress', (event) => {
  const { percent } = event.payload;
  console.log(`下载进度: ${percent.toFixed(1)}%`);
  updateProgressBar(percent);
});

// 触发下载
await invoke('start_download', { url: 'https://example.com/big.zip' });

// 组件卸载时记得取消监听,否则内存泄漏
// unlisten();

3.3 Channel:高吞吐、有序的数据流

Event 是全局广播,适合低频通知。但如果你要传的是高频、大量、需要保序的数据(比如流式日志、音视频帧、逐块文件读取),Event 的开销就顶不住了。Tauri 2.x 引入了 Channel,专为这类场景设计,性能远超 Event。

use tauri::ipc::Channel;
use serde::Serialize;

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
enum StreamEvent {
    Started { total_lines: usize },
    Line { index: usize, content: String },
    Finished,
}

#[tauri::command]
async fn stream_log(path: String, on_event: Channel<StreamEvent>) -> Result<(), String> {
    let content = tokio::fs::read_to_string(&path)
        .await
        .map_err(|e| e.to_string())?;
    let lines: Vec<&str> = content.lines().collect();

    on_event.send(StreamEvent::Started { total_lines: lines.len() })
        .map_err(|e| e.to_string())?;

    for (index, line) in lines.iter().enumerate() {
        on_event.send(StreamEvent::Line {
            index,
            content: line.to_string(),
        }).map_err(|e| e.to_string())?;
    }

    on_event.send(StreamEvent::Finished).map_err(|e| e.to_string())?;
    Ok(())
}

前端:

import { Channel, invoke } from '@tauri-apps/api/core';

type StreamEvent =
  | { event: 'started'; data: { totalLines: number } }
  | { event: 'line'; data: { index: number; content: string } }
  | { event: 'finished' };

const channel = new Channel<StreamEvent>();
channel.onmessage = (message) => {
  switch (message.event) {
    case 'started':
      console.log(`共 ${message.data.totalLines} 行`);
      break;
    case 'line':
      appendLine(message.data.content);
      break;
    case 'finished':
      console.log('读取完成');
      break;
  }
};

await invoke('stream_log', { path: '/var/log/app.log', onEvent: channel });

性能对比要点:Event 每次都要经过全局事件总线并广播给所有监听者,Channel 是点对点的直连管道。在传输 10 万条日志的实测里,Channel 的吞吐能达到 Event 的数倍,且不会阻塞其他事件。规则很简单:低频通知用 Event,高频流用 Channel。

3.4 State:跨命令共享的后端状态

Rust 后端经常需要维护跨命令的共享状态:数据库连接池、配置对象、缓存。Tauri 用 .manage() 注入状态,命令里用 State 提取器拿到。

use std::sync::Mutex;
use tauri::State;

// 共享状态:一个内存计数器(真实场景可能是 DB pool)
struct AppState {
    counter: Mutex<i64>,
}

#[tauri::command]
fn increment(state: State<AppState>) -> i64 {
    let mut count = state.counter.lock().unwrap();
    *count += 1;
    *count
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(AppState { counter: Mutex::new(0) })
        .invoke_handler(tauri::generate_handler![increment])
        .run(tauri::generate_context!())
        .expect("error");
}

关键提醒:State 里的可变数据必须用 Mutex/RwLock 包裹,因为 Tauri 命令可能被多个 WebView 线程并发调用。异步场景下如果锁要跨 .await 持有,应该用 tokio::sync::Mutex 而非 std::sync::Mutex,否则会阻塞异步运行时。


四、权限系统:Tauri 2.x 最重要的架构升级

如果说 IPC 是灵魂,那权限系统就是 Tauri 2.x 相较 1.x 最值得单独拉出来讲的进化。很多从 1.x 迁移过来的人在这里翻车。

为什么需要权限系统

WebView 里跑的是 Web 代码,而 Web 代码可能来自你不完全信任的地方(第三方库、CDN 脚本、甚至 XSS 注入)。如果任何前端 JS 都能无限制调用 Rust 后端的文件读写、命令执行,那就等于把系统权限拱手让人。Tauri 的权限系统就是那道最小权限原则的防火墙。

2.x 的权限模型由三个概念组成:

  • Permission(权限):一条具体的能力,比如「允许读取文件」fs:allow-read
  • Scope(作用域):权限的边界,比如「只能读 $APPDATA 目录下的文件」。
  • Capability(能力集):把一组 Permission 打包,绑定到具体的窗口/WebView。

实战配置

假设你要让主窗口能读写应用数据目录、弹对话框、发通知。先在 Cargo.toml 引入插件:

[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-fs = "2"
tauri-plugin-dialog = "2"
tauri-plugin-notification = "2"

Rust 里注册插件:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_notification::init())
        .run(tauri::generate_context!())
        .expect("error");
}

然后在 src-tauri/capabilities/default.json 配置能力集:

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "主窗口的默认权限集",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "dialog:allow-open",
    "dialog:allow-save",
    "notification:default",
    {
      "identifier": "fs:allow-read-text-file",
      "allow": [{ "path": "$APPDATA/*" }]
    },
    {
      "identifier": "fs:allow-write-text-file",
      "allow": [{ "path": "$APPDATA/*" }],
      "deny": [{ "path": "$APPDATA/secrets/*" }]
    }
  ]
}

这段配置的含义:主窗口能弹开/存对话框、能发通知、只能读写 $APPDATA 目录下的文本文件,且明确禁止$APPDATA/secrets/(deny 优先级高于 allow)。前端如果试图读别的路径,Rust 会直接拒绝,抛出权限错误。

这套设计的精妙之处:你可以给不同窗口配不同的 Capability。比如一个「插件市场」窗口加载第三方内容,就给它一个极度受限的能力集;而主控制台窗口给完整权限。粒度细到单个窗口、单条路径。

常见迁移坑

从 1.x 升级最容易踩的坑是:1.x 的 allowlist 在 2.x 完全废弃了,所有能力都要通过插件 + Capability 声明。如果你 npm run tauri dev 后前端调 API 报「not allowed」或「forbidden」,99% 是 Capability 里没加对应权限。排查顺序:① 插件装了没 → ② Rust 注册了没 → ③ Capability 加了对应 permission 没 → ④ 窗口 label 匹配没。


五、Sidecar:把任意二进制/脚本变成你的后端

有时候你的核心逻辑不是 Rust 写的——可能是一个 Python 的 AI 推理脚本、一个 FFmpeg 二进制、一个 Go 编译的 CLI。Tauri 的 Sidecar 机制允许你把外部可执行文件打包进应用,并从 Rust 端调用它。这是「用 Rust 做壳、用其他语言做核」的关键能力。

tauri.conf.json 声明 sidecar:

{
  "bundle": {
    "externalBin": ["binaries/my-python-tool"]
  }
}

放置二进制时要遵守目标三元组命名规则my-python-tool-x86_64-apple-darwinmy-python-tool-x86_64-pc-windows-msvc.exe 等,Tauri 打包时会挑选对应平台的版本。

Rust 端调用(需要 tauri-plugin-shell):

use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;

#[tauri::command]
async fn run_ai_inference(app: tauri::AppHandle, input: String) -> Result<String, String> {
    let sidecar = app.shell()
        .sidecar("my-python-tool")
        .map_err(|e| e.to_string())?
        .args(["--mode", "infer", "--input", &input]);

    let (mut rx, _child) = sidecar.spawn().map_err(|e| e.to_string())?;

    let mut output = String::new();
    while let Some(event) = rx.recv().await {
        match event {
            CommandEvent::Stdout(line) => {
                output.push_str(&String::from_utf8_lossy(&line));
            }
            CommandEvent::Terminated(payload) => {
                if payload.code != Some(0) {
                    return Err(format!("Sidecar 退出码非零: {:?}", payload.code));
                }
                break;
            }
            _ => {}
        }
    }
    Ok(output)
}

生产建议:Sidecar 很强大但要注意跨平台二进制体积(Python 打包成单文件动辄几十 MB,会抵消 Tauri 的体积优势),以及不同平台的签名/公证问题(macOS 会检查嵌入二进制的签名)。能用 Rust 原生实现的,优先 Rust;只有生态确实无可替代(如某些 AI 模型只有 Python 实现)时才上 Sidecar。


六、自动更新:桌面应用的生命线

桌面应用不像 Web,用户不会自动拿到最新版。没有自动更新,你的 bug 修复永远推不下去。Tauri 官方的 tauri-plugin-updater 提供了完整方案。

配置 tauri.conf.json

{
  "plugins": {
    "updater": {
      "endpoints": [
        "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
      ],
      "pubkey": "你的公钥(tauri signer generate 生成)",
      "windows": {
        "installMode": "passive"
      }
    }
  }
}

签名机制是核心安全点:更新包必须用你的私钥签名,客户端用内置公钥验签。这防止了中间人攻击——即使 CDN 被攻破,攻击者没有你的私钥也无法伪造合法更新包。生成密钥对:

npm run tauri signer generate -- -w ~/.tauri/myapp.key

前端触发更新检查:

import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';

async function checkForUpdates() {
  const update = await check();
  if (update) {
    console.log(`发现新版本 ${update.version},正在下载...`);

    let downloaded = 0;
    let contentLength = 0;
    await update.downloadAndInstall((event) => {
      switch (event.event) {
        case 'Started':
          contentLength = event.data.contentLength ?? 0;
          break;
        case 'Progress':
          downloaded += event.data.chunkLength;
          console.log(`已下载 ${downloaded}/${contentLength}`);
          break;
        case 'Finished':
          console.log('下载完成');
          break;
      }
    });

    // 安装完成后重启应用
    await relaunch();
  }
}

服务端只需返回一个 JSON 描述最新版本、下载地址和签名。整套机制生产可用,比自己搓一套差分更新靠谱得多。


七、性能优化:把体积和内存榨到极致

Tauri 天生小,但要做到极致还有几个杠杆。

7.1 Cargo 编译优化

src-tauri/Cargo.toml 加 release profile 优化:

[profile.release]
opt-level = "z"      # 优化体积("s" 也可,速度与体积折中)
lto = true           # 链接时优化,跨 crate 内联
codegen-units = 1    # 单编译单元,编译慢但产物更优
panic = "abort"      # 移除 panic 展开代码,减小体积
strip = true         # 剥离调试符号

这套配置能把 Rust 二进制体积再压 30%~50%,代价是编译变慢(LTO + codegen-units=1 会显著拉长编译时间),所以只在 release 用。

7.2 前端资源优化

WebView 加载的还是 Web 资源,前端那套优化全部适用:代码分割、Tree Shaking、图片压缩、Gzip/Brotli 预压缩。用 Vite 的话开启 build.minify 和现代构建目标即可。特别注意:不要引入体积巨大的前端组件库然后只用一个按钮——这些都会进最终包。

7.3 减少 IPC 往返

IPC 虽快但不是零成本。常见反模式是在循环里疯狂 invoke

// ❌ 反模式:1000 次 IPC 往返
for (const id of ids) {
  await invoke('fetch_item', { id });
}

// ✅ 批量:1 次 IPC
await invoke('fetch_items_batch', { ids });

Rust 端做成批处理接口,一次把 1000 个请求处理完返回。这个优化在数据密集型应用里能带来数量级的体验提升。

7.4 把重活留在 Rust

这是 Tauri 相对 Electron 的核心优势——别在前端 JS 里做计算密集型工作。加解密、图像处理、大文件解析、正则批处理,全丢给 Rust。Rust 的多线程(rayon)+ 零成本抽象能把这些任务的性能拉到 JS 的数倍甚至数十倍,而且不占 WebView 的主线程,UI 永远丝滑。

use rayon::prelude::*;

#[tauri::command]
async fn hash_files_parallel(paths: Vec<String>) -> Result<Vec<String>, String> {
    // rayon 自动把任务分配到所有 CPU 核心
    let results: Vec<String> = paths
        .par_iter()
        .map(|path| {
            let content = std::fs::read(path).unwrap_or_default();
            format!("{:x}", md5::compute(&content))
        })
        .collect();
    Ok(results)
}

八、Tauri vs Electron:一张务实的决策表

抛开信仰,用工程视角对比:

维度Tauri 2.xElectron
安装包体积3~10MB80~200MB
常驻内存30~80MB200~500MB
冷启动毫秒级数百毫秒~秒级
渲染引擎系统 WebView(各平台不同)打包 Chromium(统一)
后端语言RustNode.js
跨端一致性WebView 有平台差异完全一致
生态成熟度快速增长极其成熟
学习曲线需要 Rust纯前端即可

什么时候选 Tauri:轻量工具、对体积/内存/启动敏感、需要原生性能(本地计算、系统集成)、团队愿意投入 Rust。

什么时候留在 Electron:需要跨平台像素级一致的复杂渲染、重度依赖 Node.js 生态、团队没有 Rust 储备且工期紧、需要某些 Chromium 独有特性。

最大的坑要说清楚:Tauri 用系统 WebView 意味着你的应用在 Windows 上是 Chromium 内核,在 macOS 上是 WebKit,在 Linux 上是 WebKitGTK。这三者的 CSS/JS 兼容性有差异,尤其 WebKitGTK 在 Linux 上有时会有渲染问题。这是用系统 WebView 换体积和内存必须付出的代价。做复杂 UI 前,务必三端都测。


九、总结与展望

回到开头那个问题:你的下一个桌面项目该不该上 Tauri?

我的判断是:2026 年,对绝大多数「工具类」桌面应用,Tauri 应该是默认选项,而不是备选项。 体积和内存的优势对用户体验的影响是实打实的,Rust 后端带来的性能和安全性是 Electron 给不了的。而 2.x 的移动端支持更是让「一套代码打五端」从口号变成现实。

但要清醒地认识到它的代价:Rust 学习曲线跨平台 WebView 差异是两座必须翻的山。前者随着团队熟悉会摊薄,后者需要你在 UI 设计上保持克制、坚持三端测试。

Tauri 的技术路线代表了一个更朴素的工程哲学——不重复造轮子,复用操作系统已有的能力。当整个行业都在给每个应用塞一个浏览器时,Tauri 问了一句「凭什么」,然后给出了答案。这种「减法」思维,往往比「加法」更需要洞察力。

展望未来,随着 WebView 标准的进一步统一、Rust 生态的持续繁荣、以及移动端能力的成熟,Tauri 有机会成为跨端应用开发的新基准。至少在「小而美」这个赛道上,它已经赢了。

如果你手上正好有个 Electron 应用嫌它太胖,不妨花一个周末用 Tauri 重写核心功能,然后对比一下安装包大小——那个数字的落差,会比任何文档都更有说服力。


本文代码基于 Tauri 2.x 稳定版编写,实际使用请以官方最新文档为准。技术选型无绝对,适合自己团队的才是最好的。

推荐文章

jQuery `$.extend()` 用法总结
2024-11-19 02:12:45 +0800 CST
Golang 中你应该知道的 Range 知识
2024-11-19 04:01:21 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
程序员茄子在线接单