介绍
几十年来,Web 应用程序仅限于通过笨重的 <input type="file"> 元素读取文件,并且没有可靠的方法将更改保存回原始文件。文件系统访问 API 完全改变了这一点。它使 Web 应用程序能够直接在用户的本地文件系统上读取、写入和管理文件和目录——当然,需要用户许可。这为构建原生的文本编辑器、图像编辑器、IDE 和生产力工具打开了大门。
使用 showOpenFilePicker 打开文件
window.showOpenFilePicker() 方法返回一个解析为 FileSystemFileHandle 对象数组的承诺。您可以为其配置多项选择、文件类型过滤和 MIME 类型限制选项:
const [fileHandle] = await window.showOpenFilePicker({
types: [{
description: 'Markdown Files',
accept: { 'text/markdown': ['.md'] }
}],
multiple: false
});
const file = await fileHandle.getFile();
const content = await file.text();
document.querySelector('#editor').value = content;
FileSystemFileHandle 接口提供了两个关键方法:getFile() 用于读取,createWritable() 用于写入。根据数据类型,读取支持 file.text()、file.arrayBuffer() 和 file.stream()。优雅地处理错误 - 用户取消抛出 AbortError,而权限撤销抛出 NotAllowedError。
使用 showSaveFilePicker 保存文件
为了保存,window.showSaveFilePicker() 返回指向用户选择的保存位置的 FileSystemFileHandle。写入涉及创建可写流、写入内容和关闭流:
const fileHandle = await window.showSaveFilePicker({
suggestedName: 'document.md',
types: [{
description: 'Markdown Files',
accept: { 'text/markdown': ['.md'] }
}]
});
const writable = await fileHandle.createWritable();
await writable.write(editorContent);
await writable.close();
对于大文件,使用带有 WriteParams 对象的流式写入来分块写入,而无需将整个文件加载到内存中:
const writable = await fileHandle.createWritable();
const encoder = new TextEncoder();
const chunkSize = 1024 * 1024; // 1MB chunks
for (let offset = 0; offset < content.length; offset += chunkSize) {
const chunk = content.slice(offset, offset + chunkSize);
await writable.write({ type: 'write', data: encoder.encode(chunk), position: offset });
}
await writable.close();
WriteParams 对象还支持 type: 'truncate' 来调整文件大小,并支持 type: 'seek' 来重新定位写入光标,从而使您能够精确控制文件内容。
使用目录
window.showDirectoryPicker() 方法返回一个 FileSystemDirectoryHandle,从而实现递归遍历和修改目录结构:
const dirHandle = await window.showDirectoryPicker();
async function traverseDirectory(dirHandle, path = '') {
for await (const [name, handle] of dirHandle.entries()) {
const fullPath = `${path}/${name}`;
if (handle.kind === 'directory') {
console.log(`📁 ${fullPath}`);
await traverseDirectory(handle, fullPath);
} else {
console.log(`📄 ${fullPath}`);
}
}
}
await traverseDirectory(dirHandle);
创建新文件和目录很简单:
const newFile = await dirHandle.getFileHandle('notes.md', { create: true });
const newDir = await dirHandle.getDirectoryHandle('images', { create: true });
await dirHandle.removeEntry('temp.log', { recursive: true });
权限模型
文件系统访问 API 需要瞬时激活 - 必须调用选择器方法来响应用户手势(例如单击或按键)。文件和目录句柄上的权限将持续存在,直到重新加载页面或句柄被垃圾收集为止。对于长期访问,您可以明确检查并请求权限:
async function requestWritePermission(fileHandle) {
const options = { mode: 'readwrite' };
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
return (await fileHandle.requestPermission(options)) === 'granted';
}
文件句柄可以序列化并存储在 IndexedDB 中,以便跨会话进行持久访问 - 这就是类似 IDE 的应用程序记住以前打开的项目的方式。请注意,隐私浏览模式会清除存储的句柄,跨域 iframe 无法访问 API。
浏览器支持和后备
| 特色 | 铬 | 边缘 | 火狐 | 野生动物园 |
|---|---|---|---|---|
| 文件系统访问API | 86+ | 86+ | ❌ | ❌ |
| 文件处理 API | 102+ | 102+ | ❌ | ❌ |
| Origin 私有文件系统 | 86+ | 86+ | ❌ | ❌ |
始终使用功能检测并提供后备:
if ('showOpenFilePicker' in window) {
// Use File System Access API
} else {
// Fallback to <input type="file"> for reading
const input = document.createElement('input');
input.type = 'file';
input.click();
}
要在不受支持的浏览器中保存,请使用 Blob URL 回退到 <a download>。 文件处理 API (Chrome 102+) 允许您通过 Web 应用程序清单将 Web 应用程序注册为文件处理程序,使用户能够从操作系统直接使用您的应用程序打开文件。
高级用例
文件系统访问 API 支持以前为本机应用程序保留的模式:
- 类似 IDE 的应用程序:打开项目目录、读取和写入单个文件、创建新文件、重命名和删除条目。
- 具有实时预览功能的 Markdown 编辑器:打开
.md文件,使用实时预览进行编辑,然后保存回原始文件。 - 具有“保存”和“另存为”功能的图像编辑器:使用
showSaveFilePicker进行另存为,使用createWritable()进行就地保存。 - 批量文件处理:打开目录、迭代文件、应用转换并保存结果 - 所有这些都无需下载或上传。
对于无需用户手势即可工作的沙盒存储,请通过 navigator.storage.getDirectory() 使用 Origin 私有文件系统。这提供了一个范围仅限于您的源的虚拟文件系统,对于缓存和离线数据很有用。
性能考虑因素
处理大文件时,始终首选流式传输方法:
- 使用
file.stream()来读取大文件,而不是file.text()或file.arrayBuffer()。 - 分块写入(例如 1MB)以避免内存压力。
- 对于包含数千个文件的目录使用
entries()迭代器,而不是一次收集所有条目。 - 反跳自动保存操作 - 保持可写流打开并定期写入,但仅在完成后关闭它。
- Origin 私有文件系统 (
navigator.storage.getDirectory()) 提供沙盒存储,无需权限提示,非常适合应用程序内部数据。
结论
文件系统访问 API 将 Web 应用程序从只读内容消费者转变为功能齐全的创意工具。虽然目前仅限于基于 Chromium 的浏览器,但它填补的功能缺口是巨大的。始终使用特征检测并使用传统的 <input type="file"> 和 <a download> 模式提供后备。对于需要本机文件功能的生产力应用程序、创意工具和开发人员工具,此 API 消除了对 Electron 或其他本机包装器的需要。

