从Pake源码看web应用如何转换为原生应用
2025 年 11 月 23 日
Pake 是我很久之前就看到的一个项目,项目是这样介绍自己的:Turn any webpage into a desktop app with one command, supports macOS, Windows, and Linux。即:用一条命令将任意网页转换为桌面应用,支持 macOS/Windows/Linux。
没看源码之前,这句话让我有点不解。我知道 Pake 是基于 Tauri 实现的,而 Tauri 的核心,就是用 rust 启动操作系统本身的 WebView,在这个 WebView 中运行一个 web 应用。也就是把 web 应用转换为原生应用。
既然用 Tauri 简单修改一下就可以达成把 web 应用转换为原生应用这个目的,那么为什么还要有 Pake 呢?除了一条命令这点,它比 Tauri 又多做了哪些事情呢?今天看了一下源码,总算看出来一点门道。
注:本文只是看源码过程中的一个副产品,并非一篇完整的源码分析,所以疏漏难免。
整体工程
先看工程结构,虽然看起来很像 Tauri 工程,但实际上,Pake 是一个 nodejs 工程。其 CLI 的源码是在 /bin 目录下的 ts 代码。
Pake 的工作可以简单理解为:根据输入的参数,生成一套 Tauri 应用的源码,然后编译,并得到 Tauri 应用的二进制文件或安装包。
因此,在 src-tauri 目录下的源码,可以理解为目标 Tauri 应用源码的模板。
目标 Tauri 应用源码入口分析
写过 Tauri 应用的应该不难理解,从 V3.5.1 的 /src-tauri/src/lib.rs 看进去,可以明显看出,相对于默认的 Tauri 应用模板,Pake 生成的应用在启动时,做了以下事情:
-
将 Pake 配置转换为 Tauri 配置文件
-
多进程/单进程开关
-
添加了一组 Tauri 的 plugin,包括 oauth / http / shell / notification
-
为后端添加了 3 个接口:download_file / download_file_by_binary / send_notification,其中:
- download_file 和 download_file_by_binary 的区别在于,要下载的数据到底是链接,还是前端的 blob 数据
- send_notification 的目的是调用系统原生的通知接口
-
调整窗口、系统托盘(system tray)以及快捷方式相关,在调整窗口时,主要还做了以下几件事:
- 根据设置选择打开外部 web 链接还是本地文件
- 注入 window.pakeConfig 对象,以及几个 js 脚本
- 调整浏览器参数,打开一些 feature
-
处理 window 关闭事件,根据配置选择是退出还是最小化到托盘
前端注入代码分析
调整窗口时注入了几个 js 文件,用途包括:
- component.js:添加了一个 pakeToast 接口,用于弹出 toast,显示一些提示
- 该接口仅在 rust 后端代码执行过程想要通知使用者时被调用,看起来目前主要是通知下载结果
- custom.js:允许项目使用者自定义的 js。默认为空
- event.js:所有事件处理,例如:
- 键盘快捷键,实现一些页面浏览的控制,如前进/后退/放大/缩小/滚动到顶部或底部,等等。
- 链接点击,对于下载链接,使用后端添加的接口完成下载,避免打开新浏览器页面。
- 右键上下文菜单。
- 特定区域(pake-top-dom)拖拽处理。
- style.js:注入 css,并根据先前注入的 pakeConfig 调整页面样式
结论
所以,回到最初的问题,Pake 比 Tauri 多做的事情,就是 Web 应用转换为原生应用时,需要处理的一些问题点:
- 窗口处理:web 应用在浏览器中运行,因此窗口样式是由浏览器控制。在 WebView 窗口中,可能需要做一些改动,比如拖拽区域。
- 通知处理,包括:
- 前端应用调用 window.Notification 接口的实现。
- 后端应用调用前端 Toast 接口的实现。
- 下载处理:启动后台下载线程,避免让浏览器直接下载或打开新窗口。
- UI 处理:样式调整等。
- 其他尚未注意到的前后端之间的事件处理。
这些改动可能并非必须,但肯定能够大幅改善 web 应用转换的效果,让转换后的应用更像一个原生应用。这就是我从 Pake 源码中学到的知识。