Featured image of post 文件系统访问API:强大的本地文件操作Featured image of post 文件系统访问API:强大的本地文件操作

文件系统访问API:强大的本地文件操作

探索文件系统访问 API:showOpenFilePicker、showSaveFilePicker、showDirectoryPicker、流式写入、权限和类似 IDE 的应用程序模式。

介绍

几十年来,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。


浏览器支持和后备

特色边缘火狐野生动物园
文件系统访问API86+86+
文件处理 API102+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 或其他本机包装器的需要。