📢 我正在写 Eidos,一个离线版的 Notion,是个 local-first 的 Web App。这一系列文章记录实现 Eidos 过程用到的一些技术和踩的坑。
OPFS 这个提案出来了很久。2022年年中, Chrome 开始支持这个 API,随着 firefox 今年3月的支持,算是逐渐可用。虽然各个浏览器的支持不是很统一,有些规范 API 也没有实现,但是大差不差可以用了。目前资料较少,也有一些坑,本篇文章挑重点记录一下。
什么是 OPFS
这篇文章介绍的很全面,不再复述。
https://web.dev/articles/origin-private-file-system?hl=ja
https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system
简单来说,OPFS 是浏览器虚拟出来的一套文件系统,它解决了浏览器存储大量数据的问题。你可以像使用系统级别的文件系统一样,在 web 中存储大量数据。这带来了很多可能性。
文件系统 API
之前 Web 确实提供了一些 API 让我们处理文件。它就是文件系统 API,这个 API 让 web 具备了处理本地文件的能力。
此 API 允许程序与用户本地设备上的或是用户能够访问的网络文件系统上的文件进行交互。此 API 的核心功能包括读取文件、写入或保存文件以及访问目录结构。
但是它有个缺点,每次都需要用户授权,在你每次重新打开站点时,就需要重新授权。你可以理解为这是一个一次性的授权。这个处理很麻烦,没有用户喜欢一直点那个授权。所以 logseq 的官网称之为 live demo。web 版本的 logseq 只是个 demo。正真的 logseq 是 web 套壳的客户端。
如上所示,logseq-demo 是我可以通过系统的文件浏览器查看一个真实的文件夹。存储在用户本机的硬盘上。文件系统的 API 只不过是获取了读取和写入权限,存储还是在 web 之外的。 因为会实打实的修改用户系统上的文件,所以这个 API 做了很多安全检查,导致性能不足。在获得用户的写入授权后,实际文件是先写到临时文件中,经过安全检查后才会写入用户系统的文件中。
OPFS 的区别
看了之前的文件系统 API,我们来看看 OPFS 做了啥,OPFS 实际上也划分在文件系统 API 中。不同的是 OPFS 在 Web 内部虚拟出来一套文件系统,对用户并不可见。以文件块的形式分散存储,用户是看不到原始数据的,只能通过 Web 的 API 访问。下图是 OPFS 实际存储的内容。
因为不走系统的安全检测,统统在 Web 内部处理,因此性能会好很多。每个站点的数据都是隔离的,可以像使用系统的文件系统一样,按照目录存储大量数据。开发者可以通过 OPFS Explorer 查看站点存储在 OPFS 中的数据。
有哪些使用 OPFS 的案例
- Wordpress 直接跑在浏览器中
- Web 版的 Photoshop
- sqlite-wasm。sqlite 有一层 vfs 的设计,sqlite-wasm 使用 OPFS 作为 vfs。
WASM + OPFS 把胖客户端带到了浏览器中。wasm 解决了计算性能的问题,opfs 解决了大量数据存储的问题。Eidos 也是这样的实现,期待看到越来越多 local-first 的 web app。
数据持久化的坑
我写了2个月,搭完基础架子就开始 dogfooding 自己用了。8月份开始正式使用,9月下旬出现了一次数据丢失的问题。还好有个备份,但是也丢了差不多半个月的数据。
然后10月底数据又丢了一次,找了一圈找不到相关信息。跑去 sqlite 论坛发帖问了下,终于找到问题所在。因为我没有向用户请求数据持久化权限。如果你没有向用户请求持久化权限。数据还是会给你存储,但是浏览器可能会把数据清理掉。 这一点很隐晦,MDN 只给了代码,在代码里面提了下,但是文档里面没怎么说。
如果运行下面的代码,persistent 是 false 表示你的应用没有拿到浏览器的数据持久化权限。结果就是 app 的数据是会被浏览器清理掉的。至于什么时候清理是个黑盒机制。大概是在你机器硬盘存储到了一定的比例,会把非持久化的数据清掉。这就是我的数据丢了2次的原因。
if (navigator.storage && navigator.storage.persist) {
navigator.storage.persist().then((persistent) => {
if (persistent) {
console.log("Storage will not be cleared except by explicit user action");
} else {
console.log("Storage may be cleared by the UA under storage pressure.");
}
});
}
如何获取数据持久化权限
但是如何获取数据持久化权限呢?
navigator.storage.persist()
上面这句就是在向浏览器请求数据持久化的权限,如果你用 chromium-base 的浏览器,不会看到任何提示,它不会像之前的文件系统 API 一样,弹出一个框框出来,让你授权。而是浏览器自主判断你的站点是否有持久化的权限。在这一点上,firefox 把选择权显式地交给了用户,比 chrome 的默认处理要好很多。
简单概括下:
- Chromium-based 的浏览器会自动判断
- 网站的互动程度如何?访问频次?
- 是否安装了 PWA或加入书签?
- 是否已获得授权以显示通知?
- firefox 会显式地把授权传递给用户选择
关于持久化存储的详细细节可以看下面这篇:
https://web.dev/articles/persistent-storage?hl=ja
如果你在实现一个 local-first 的 web app,想要稳定持久化的存储数据,下面有些建议:
- 显式地提示用户当前 App 是否有数据持久化权限,数据安全放在第一位,否则丢了数据没你好果子吃。
- 如果没有持久化权限,则引导用户安装 PWA,并且获取通知权限,然后请求一遍持久化权限,chrome/edge 是可以拿到授权的,firefox 在请求时会弹窗也能拿到权限。
- 最后,把这篇文章中提到 MDN 和 web.dev 的文章好好看一遍,以确保真的理解了 OPFS 的玩法。