前端文件系统
次访问
前言
前端文件系统api(The File System Access API)让web应用可以读写用户本地的文件或文件夹。使我们可以开发出能够和用户本地文件交互的web应用,比如IDE,图片和视频编辑器,文字编辑器等等。
在用户开启web应用权限后,这些api可以直接在用户本地读写文件或文件夹,打开一个目录并显示里面的内容,在用户本地创建或删除文件夹和文件。
打开
比如页面中有个打开按钮:
1 | <div id="open">打开</div> |
点击后,调用showOpenFilePicker就可以弹出文件选择窗,我们选择文件,再调用getFile即可获取文件的file数据,这个file数据和获取的file数据一样。
1 | const openElm = document.getElementById('open') |
上面的例子中,我们选择了一个文件,showOpenFilePicker返回了FileSystemFileHandle类型的数组:
这里的fileHandle将会很有用,后面的保存等操作都需要它。
区分 file pickers
有时应用会有多个不同的picker,比如富文本编辑器可以打开文本,也可以打开图片,默认情况下,每个file picker会记住上次的路径,我们可以通过id来区分不同的file picker,让它们记住不同的最近一次打开的路径。
1 | const fileHandle1 = await window.showSaveFilePicker({ |
不过这个功能,我在windows上Chrome版本 96.0.4664.45(正式版本) (64 位)上试验失败了,file picker没有记住上次打开的路径。
保存
保存会重写原文件。
页面中,我们放一个打开和保存按钮,还有一个文本框:
1 | <div id="open">打开</div> |
点击打开按钮,我们选择文件,比如test.txt,并把文本内容显示到textArea:
1 | openElm.addEventListener('click', async () => { |
点击保存时调用writeFile:
1 | saveElm.addEventListener('click', () => { |
writeFile函数中创建可写数据流,把textArea的内容写进去:
1 | async function writeFile(fileHandle, contents) { |
写数据用到FileSystemWritableFileStream对象,它本质上是一个可写的流,调用fileHandle的createWritable就可以创建,调用createWritable时,浏览器会先检查是否有写的权限,没有的话浏览器就会弹对话框,让用户选择是否开启写权限:
用户拒绝时createWritable会抛出DOMException的错误:
这样,应用就不会保存更改。
上面的writeFile方法在写数据时用的contents是字符串,我们也可以用其它格式的数据,比如BufferSource,或者Blob:
1 | async function writeURLToFile(fileHandle, url) { |
我们还可以在打开时手动申请写的权限,用户打开文件时看到一个对话框,然后我们对打开的文件就有了读写权限,在保存时就不会再弹对话框。
通过下面的verifyPermission函数来判断fileHandle是否有读写权限,结果是true则开启了,false则是用户拒绝了。
1 | async function verifyPermission(fileHandle, readWrite) { |
另存
另存会创建一个新的文件。
调用showSaveFilePicker会弹出保存弹窗:
1 | await window.showSaveFilePicker() |
保存类型
types参数控制保存类型:
1 | await window.showSaveFilePicker({ |
默认目录
同样的,我们也可以设置启动目录。如果你是编辑文本,会希望打开或保存时的文件夹路径是文档,如果是编辑图片,则默认图片文件夹,这个路径可以通过配置startIn来实现:
1 | startIn: 'pictures' |
还有其他目录可以配置:
- desktop: 桌面
- documents: 文档
- downloads: 下载
- music: 音乐
- pictures: 图片
- videos: 视频
windows中对应文件夹的这些目录:
除了上面这些通用的目录,你还可以设置为存在的文件或目录地址:
1 | const openElm = document.getElementById('open') |
上面的例子中,我们showOpenFilePicker打开了文件,在showSaveFilePicker保存时startIn传打开的fileHandle,即可将保存弹窗的路径设置为和打开时选择的文件路径一致。
文件夹
打开文件夹
showDirectoryPicker可以打开文件夹并获取其中的内容:
1 | openElm.addEventListener('click', async () => { |
如果没有权限,浏览器会弹对话框:
创建文件和文件夹
在文件夹中,你可以用getFileHandle读取文件,用getDirectoryHandle读取文件夹,在可选参数中传create来控制当新文件和文件夹不存在时是否需要创建。
1 | // 打开文件夹 |
解析路径
上面的例子在打开的文件夹中创建文件夹并在新文件夹中新建文件,我们可以解析新建文件的路径:
1 | const path = await dirHandle.resolve(newFileHandle) |
删除
删除上面新建的My Notes.txt文件:
1 | await newDirectoryHandle.removeEntry('My Notes.txt') |
删除上面的My Documents文件夹:
1 | await dirHandle.removeEntry('My Documents', { recursive: true }) |
拖拽
1 | window.addEventListener('dragover', async (e) => { |
补丁
还不能给File System Access API打完整的补丁。
- showOpenFilePicker可以用
<input type="file">
代替 - showSaveFilePicker可以用
<a download="file_name">
代替,尽管这能触发下载,但不能覆盖现有文件 - showDirectoryPicker可以用
<input type="file" webkitdirectory>
替代
browser-fs-access封装了前端文件系统api,会尽量使用File System Access API,不支持的浏览器使用其他方案。
安全
Chrome团队设计并实现了文件系统访问API,使用了控制访问强大Web平台功能的核心原则。
打开或另存文件
打开文件时,用户通过 file picker 提供读取文件的权限,用来打开的file picker只能用户手动点开,如果用户改变主意了,可以点取消,然后应用不会得到任何用户数据,表现和一样。
同样的,当应用要另存时,浏览器会弹出保存窗口,让用户指定文件名和位置。
文件夹限制
为了保护用户及其数据,浏览器不允许用户保存到一些文件夹,比如核心操作系统文件夹。如果要保存到这些位置,浏览器会弹窗让用户选择其他路径。
保存
保存时会覆盖源文件,web应用必须得到用户明确同意后才能保存。
如果用户要保存对开启了读取权限文件的更改,浏览器就会弹对话框,问用户是否要保存。
另外,可以编辑多个文件的web应用程序(比如IDE)也可以在打开时请求保存更改的权限。
如果在对话框中用户点击取消,不给写的权限,那么应用就不能保存更改。这时就需要提供保存的替代方案,让用户可以保存数据,比如提供下载途径,或者保存到云端等等。
透明
用户开启应用保存权限后,浏览器会在地址栏显示一个图标,点击图标可以显示开启权限了的文件列表,也可以很方便的取消保存权限。
有效期
同一个域名下所有的页面都关闭后,保存权限就没了,用户下一次访问时,会再次弹对话框来询问是否开启权限。