File System Access API:读写本地文件
一句话: File System Access API 让 Web 应用通过 showOpenFilePicker、
showSaveFilePicker 与 showDirectoryPicker 打开、编辑并保存用户设备上的真实文件与
文件夹。它需要安全上下文与用户手势,每次写入都受显式权限提示约束,且仅在 Chromium
浏览器中提供——所以 Safari 与 Firefox 需要回退方案。
选择器与句柄
Section titled “选择器与句柄”showOpenFilePicker()返回一组FileSystemFileHandle。调用handle.getFile()读取File(一个Blob)——用.text()、.arrayBuffer()或.stream()获取内容。showSaveFilePicker()返回一个指向新建或选定文件的FileSystemFileHandle。调用handle.createWritable()获取FileSystemWritableFileStream,向其write(),再close()提交。写入先进临时文件,关闭时原子替换目标文件。showDirectoryPicker()返回一个FileSystemDirectoryHandle。用for await (const [name, handle] of dir.entries())遍历条目,并通过getFileHandle(name, { create })/getDirectoryHandle(name, { create })创建或获取子项。
三个选择器都是异步的,用户取消时以 AbortError 拒绝,且必须在安全上下文(HTTPS 或
localhost)中由用户手势(点击、按键)触发。
- 用户选择文件或文件夹时即授予读取权限。写入会再次提示——首次
createWritable()(或显式requestPermission({ mode: 'readwrite' }))会触发权限对话框。 - 用
handle.queryPermission({ mode })在不提示的情况下查询当前状态;用handle.requestPermission({ mode })(必须在用户手势内)请求权限。两者都解析为'granted'、'denied'或'prompt'。 - 授权按源(origin)限定且并非永久——通常只在标签页/会话期间有效,页面完全关闭后 重置,因此再次访问时重新提示是预期行为。
- 某些敏感位置(系统文件夹、某些情况下的下载目录)会被直接禁止访问。
跨会话持久化句柄
Section titled “跨会话持久化句柄”FileSystemFileHandle 与 FileSystemDirectoryHandle 是可结构化克隆的,因此你可以
把它们存入 IndexedDB,并在之后的访问中取回——用户无需重新选择同一文件。句柄会持久化,
但权限不会:恢复句柄后,需调用 queryPermission(),并在必要时由新的用户手势触发
requestPermission() 再进行读写。这一模式正是 Web 端编辑器与 IDE 中“最近文件”列表的
实现基础。
源私有文件系统(OPFS)
Section titled “源私有文件系统(OPFS)”OPFS 是本 API 中广泛可用的子集。navigator.storage.getDirectory() 返回一个根植于私有、
按源限定沙箱的 FileSystemDirectoryHandle——无选择器、无权限提示,且不在用户文件管理器
中可见。它在包括 Safari 与 Firefox 在内的所有现代引擎中均可用,支持通过
createSyncAccessHandle() 在 Web Worker 中进行高性能同步访问,非常适合浏览器内 SQLite、
缓存与大型工作数据。需要快速本地存储时用 OPFS;需要用户看见并拥有真实文件时用选择器。
浏览器与生态支持
Section titled “浏览器与生态支持”| Browser / Platform | Support | Since | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | ❌ no | — | high | ref | showOpenFilePicker/showSaveFilePicker are not exposed on Android. |
| Chrome (Desktop) | ✅ yes | 86 | high | ref | — |
| Edge (Desktop) | ✅ yes | 86 | high | ref | — |
| Safari (iOS) | ❌ no | — | medium | ref | Only the origin-private file system (OPFS) is available, not the user-visible picker. |
| Safari (macOS) | ❌ no | — | medium | ref | No showOpenFilePicker; OPFS only. |
| Firefox (Desktop) | ❌ no | — | medium | ref | OPFS only; no user-visible file picker access. |
| Samsung Internet | ❌ no | — | medium | ref | — |
决策判定框架
Section titled “决策判定框架”| 决策问题 | 建议行为 | 理由 |
|---|---|---|
| 需要用户打开并重新保存自己的文件(编辑器、IDE)? | 用选择器 + 把句柄存入 IndexedDB。 | 真正的就地编辑;“最近文件”可跨会话工作。 |
| 同时面向 Safari 或 Firefox? | 用 <input type="file"> 打开、用 <a download> blob URL 保存作为回退。 |
选择器仅限 Chromium;此方案覆盖所有引擎且体验平滑。 |
| 只需快速私有存储(缓存、数据库、暂存空间)? | 用 navigator.storage.getDirectory() 走 OPFS。 |
随处可用、无提示、Worker 同步访问保证性能。 |
| 写入大文件或流式输出? | createWritable() 分块 write(),再 close()。 |
关闭时原子替换;避免把整个文件缓冲进内存。 |
| 在之后的访问中恢复已保存句柄? | 在点击处理器内 queryPermission(),再 requestPermission()。 |
句柄持久但授权不持久;必须重新提示。 |
- 仅在用户手势内、且仅在 HTTPS/
localhost下调用选择器。 - 捕获
AbortError——用户取消选择器是正常现象,不是错误。 - 始终
close()可写流,以便原子写入提交。 - 把句柄存入 IndexedDB,但返回时用
query/requestPermission重新核验权限。 - 为 Safari 与 Firefox 提供
<input type="file">+ 下载链接回退。 - 需要私有、跨引擎、高性能存储而非用户可见文件时,选用 OPFS。