你可能不知道的 前端浏览器(window) 对本地文件操作(File System Access API)

4,907 阅读7分钟

前言

前一段时间,在做一套自定义换肤方案,突发奇想,换肤配置去后端化,是不是可以通过前端读取主题中的色卡相关的数据,可以在网页上来配置主题色和基础色,然后生成或者修改换肤文件。这时候就往浏览器怎么样可以对本地项目文件的访问、读取文件、写入或创建等方面思考。
color.gif demo在线体验地址: v-theme-colors
源码地址: v-theme-colors(ps 大部分功能暂未同步发布)

查阅了一下资料,惊奇的发现,File System Access API(下文简称: 文件系统访问API ) 是一种比较好的解决方案。它是 Web API,它允许对用户的本地文件进行读写访问。本文带大家一起探索一下 文件系统访问API 的正确打开方式吧~

一、使用 文件系统访问API 读取文件 :window.showOpenFilePicker()

在深入研究从用户系统读取文件所需的代码之前,需要记住的一个重要细节是调用文件系统访问 API 需要通过用户手动在安全上下文中完成。在下面的示例中,我们是之间在浏览器控制台上操作的

从单个文件中读取

从文件中读取数据只需不到 10 行代码即可完成。这是一个示例代码示例:


let fileHandle
const pickFile = async () => {
    [fileHandle] = await window.showOpenFilePicker()
    console.log('fileHandle',fileHandle)
    const file = await fileHandle.getFile()
    const content = await file.text()
    console.log('content',content)
    return content
};

我们可在浏览器控制台输入以上代码,测试一下,执行pickFile()函数,可以发现浏览器唤起了本地选择文件

image.png

我们通过调用启动文件选择器window.showOpenFilePicker(),并将此查询的结果存储在名为 的变量中fileHandle。 我们从调用中得到的是代表我们选择的每个文件showOpenFilePicker()的对象数组。FileSystemFileHandle由于此示例针对单个文件,因此我们对结果进行了解构。

我们操作一下选择,我本机的文件夹中的base-colors.js 可以看到在控制台上这个方法返回一个promise,并分别打印fileHandlecontent 可以获取到这个文件里面的内容:

image.png

fileHandle 对象包含一个kindandname属性

FileSystemFileHandle {kind: 'file', name: 'base-colors.js'}

kind可以是 file(文件) 或 directory(文件夹)

通过 fileHandle,我们可以调用该getFile()方法来获取有关我们文件的详细信息。调用此方法会返回一个具有一些属性的对象,包括文件上次修改时间的时间戳、文件名、文件大小和类型。 最后,我们可以调用text()文件来获取其内容。

从多个文件中读取

要从多个文件中读取,我们需要将一个options对象传递给showOpenFilePicker().

let fileHandles;
const options = { multiple: true, }; 
const pickFile  = async () => {
fileHandles = await window.showOpenFilePicker(options); 
};

默认情况下,该multiple属性设置为false。其他选项可用于指示可以选择的文件类型。例如,如果我们只想接受.jpeg文件,选项对象将包括以下内容

const options = { 
    types: [ 
        {
            description: "Images", 
            accept: { "image/jpeg": ".jpeg", },
        }, 
    ],
    excludeAcceptAllOption: true,
};

在这个例子中,fileHandles是一个包含多个文件的数组,因此获取它们的内容将通过以下方式完成:

let fileHandles; 
const options = { multiple: true, };

const pickFile = async () => { 
    fileHandles = await window.showOpenFilePicker(options);
    const allContent = await Promise.all( fileHandles.map(async (fileHandle) => { 
        const file = await fileHandle.getFile();
        const content = await file.text(); 
        return content; 
      })
    );
    console.log(allContent);
};

在浏览器中执行可以看到 image.png

二、使用 文件系统访问API 写入文件

文件系统访问 API 还允许您将内容写入文件。首先,让我们看看如何保存新文件。

写入新文件

写入新文件也可以用很短的代码完成!

const saveFile = async () => { 
    const options = { 
        types: [ 
            { 
            description: "Test files",
            accept: { 
                "text/plain": [".txt"], 
                },
            }, 
        ],
    }; 
    const handle = await window.showSaveFilePicker(options); 
    const writable = await handle.createWritable(); 
    await writable.write("Hello World"); 
    await writable.close(); 
    return handle; 
};

在执行saveFile函数时,我们使用打开文件选择器,showSaveFilePicker()并传入一个option包含要保存的文件类型的对象,这里是一个.txt文件。

调用此方法还将返回一个FileSystemFileHandle对象,如第一部分中所示。在这个对象上,我们可以调用createWritable()将返回一个FileSystemWritableFileStream对象的方法。write()然后,我们可以使用我们需要传递内容的方法将一些内容写入此流。

最后,我们需要调用该close()方法来关闭文件并完成将内容写入磁盘。

image.png

例如,如果您想将一些 HTML 代码写入文件,您只需更改options对象中的内容以接受"text/html": [".html"]  并将一些 HTML 内容传递给该write()方法。

三、编辑现有文件

如果您想导入文件并使用文件系统访问 API 对其进行编辑,示例代码示例如下所示:

let fileHandle;
const writeFile = async () => { 
    [fileHandle] = await window.showOpenFilePicker();
    const file = await fileHandle.getFile(); 
    const writable = await fileHandle.createWritable(); 
    await writable.write("这是新写入的内容"); 
    await writable.close(); 
};

如果您一直在关注本文的其余部分,您可能会认识到我们从showOpenFilePicker()getFile()方法开始读取文件,然后使用createWritable(),write()close()写入同一个文件。

如果您要导入的文件已有内容,则此代码示例将用传递给write()方法的新内容替换当前内容

image.png 弹框确认后,打开文件夹发现,已经更改了文件里面的内容

image.png

四、其他文件系统访问 API 功能

无需过多介绍,文件系统访问 API 还允许您列出目录中的文件并删除文件或目录。

4.1 读取目录

读取目录可以用一点点代码完成:

const readDir = async () => { 
    const directoryHandle = await window.showDirectoryPicker(); 
    for await (const entry of directoryHandle.values()) {
    console.log(entry.kind, entry.name); 
    } 
}

执行readDir() 方法,调用该showDirectoryPicker()方法将打开文件选择器,并且在计算机上选择目录时,该代码将列出该目录中的文件,我们测试一下,选中我本地的theme文件夹

image.png 发现控制台可以准确的打印出文件目录

image.png

4.2 删除文件

可以使用以下代码示例删除目录中的文件:

const removeFile = async () => {
    const [fileHandle] = await window.showOpenFilePicker();
    await fileHandle.remove();
};

如果你想删除一个文件夹,你只需要对上面的代码示例做一个小的改动:

const removeDirectory =  async () => { 
    const directoryHandle = await window.showDirectoryPicker();
    await directoryHandle.remove(); 
};

最后,如果要在选择文件夹时删除特定文件,可以这样写:

// 删除选定文件夹中名为data.txt的单个文件
const removeSelfFile = async () => { 
    const directoryHandle = await window.showDirectoryPicker();
    await directoryHandle.removeEntry("data.txt"); 
};

如果要删除整个文件夹,则需要以下行:

// 递归删除名为“data”的文件夹
const removeAllDirectory = async () => {
    const directoryHandle = await window.showDirectoryPicker();
    await directoryHandle.removeEntry('data', { recursive: true }); 
};

五、文件系统访问API 浏览器支持

看到上面 文件系统访问API 的使用,各位肯定会问它的浏览器支持情况是怎么样的,正如下图基本上涵盖大多数浏览器的使用,此浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器支持该版本及更高版本的功能

image.png

最后

文件系统访问API,它解锁了构建强大 Web 应用程序的新功能,例如文本编辑器或IDE、图像编辑工具、改进的导入/导出等等,所有这些都可以在前端进行操作,你也可以做更多有趣的应用。

如果您想试用文件系统访问API,可以查看 Google构建的现场演示文本编辑器。,如果想更深入了解 File System Access API 及其所有功能的更多信息,可以参阅以下资源: