文件系统
Codeblitz 需要读取和操作文件,因为浏览器层没有标准的文件读取 API,因此文件系统也有别于标准 IDE 下的文件系统。
一、通过插件实现
插件提供了 registerFileSystemProvider 的能力,详见文档,通过 filesystem provider 可以自由定义文件系统的读取和写入,自定义 scheme,同时根据 kaitian 的 ide-components 中的 tree 来实现文件树,适合有比较强自定义的场景
二、通过 BrowserFS 实现
codeblitz 内置了一套 BrowserFS,在开源库的基础上优化了些性能,并增加了一些更简单使用的文件系统,通过 BrowserFS,可配置工作空间的文件读取行为,这样只需要实现几个接口就能直接使用基于 file scheme 的文件系统了,因为一般场景推荐使用这个
目前支持的文件系统类型如下:
- IndexedDB 文件数据缓存在 indexedDB 上
- InMemory 文件数据缓存在内存中,刷新浏览器时数据即被销毁
- FileIndexSystem 文件索引文件系统,可提供全量的文件树和文件数据
- DynamicRequest 动态请求文件系统,文件树和文件动态加载
- ZipFS 基于 zip 的文件系统
- FolderAdapter 减少文件嵌套,一般配合其它文件系统一起使用
- OverlayFS 读写分离,读写可用不同的文件系统
在组件的 props 上 的 runtimeConfig.workspace
来配置文件系统,下面一一来举例,以下例子都可以在 https://github.com/opensumi/codeblitz-sample/blob/main/filesystem.tsx 中找到。
所有的文件系统都会挂载到 /workspace/{workspaceDir}
这个路径下,{workspaceDir}
可以通过 appConfig.workspaceDir
来配置。
1、IndexedDB
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'IndexedDB',
options: {
storeName: 'my_db'; // indexedDB store name,可选
}
}
}
}}
/>
2、InMemory
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: { fs: 'InMemory' },
},
}}
/>
3、FileIndexSystem
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'FileIndexSystem',
options: {
// 初始全量文件索引
requestFileIndex() {
return Promise.resolve({
'main.html': '<div id="root"></div>',
'main.css': 'body {}',
'main.js': 'console.log("main")',
'package.json': '{\n "name": "Riddle"\n}',
});
},
},
},
},
}}
/>
4、DynamicRequest
type FileEntry = [string, BrowserFSFileType, any?];
// 根据实际接口以 path 作为参数分步请求目录数据,下面为 mock 的数据
// 数组中第三项为自定义的数据,比如接口中会返回额外的数据信息,此时内部会将对应的数据和路径节点绑定
// 在 readDirectory 和 readFile 会把当前路径下的节点额外数据直接透出作为第二个参数,这样方便使用
const dirMap: Record<string, FileEntry[]> = {
'/': [
['lib', BrowserFSFileType.DIRECTORY, { custom: null }],
['Readme.md', BrowserFSFileType.FILE, { custom: null }],
['LICENSE', BrowserFSFileType.FILE, { custom: null }],
['package.json', BrowserFSFileType.FILE, { custom: null }],
],
'/lib': [
['application.js', BrowserFSFileType.FILE, { custom: null }],
['context.js', BrowserFSFileType.FILE, { custom: null }],
['request.js', BrowserFSFileType.FILE, { custom: null }],
['response.js', BrowserFSFileType.FILE, { custom: null }],
],
};
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'DynamicRequest',
options: {
// 第二个参数为额外传递的节点数据,在 readDirecotry 返回的数组中定义
// 根节点为首次请求,因此第二个参数首次为 undefined,其它节点为上述自定义的数据
readDirectory(p: string, data?) {
return dirMap[p];
},
async readFile(p, data) {
const res = await fetch(
`http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/a87fb80d-3028-4b19-93a9-2da6f871f369/koa${p}`,
);
return new Uint8Array(await res.arrayBuffer());
},
},
},
},
}}
/>;
5、ZipFS
zipData 为 zip 文件 Buffer
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'ZipFS',
options: {
zipData,
},
},
},
}}
/>
6、FolderAdapter
减少文件嵌套,如 zip 文件外层文件夹为 demo,实际 demo 无需展示,直接展示 demo 下的文件,可通过此方式将 demo 这层目录去除
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'FolderAdapter',
options: {
folder: '/demo',
wrapped: {
fs: 'ZipFS',
options: {
zipData,
},
},
},
},
},
}}
/>
7、OverlayFS
联合挂载,即在读系统上加一层写系统,读文件是优先从写系统上读取,如果没有再从读系统上读取,写文件时直接在写系统上写,这样对于只读系统可实现写操作,此种方式的好处是可将读写分离,分别实现,还可以结合 scm 做更多的控制
<AppRenderer
runtimeConfig={{
workspace: {
filesystem: {
fs: 'OverlayFS',
options: {
writable: { fs: 'InMemory' },
readable: {
fs: 'DynamicRequest',
options: {
readDirectory(p: string) {
return dirMap[p];
},
async readFile(p) {
const res = await fetch(
`http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/a87fb80d-3028-4b19-93a9-2da6f871f369/koa${p}`,
);
return Buffer.from(await res.arrayBuffer());
},
},
},
},
},
},
}}
/>
文件系统 API
加载好文件系统之后,你可以通过文件系统的 API 来操作文件:
import { requireModule } from '@codeblitzjs/ide-core';
const fse = requireModule('fs-extra');
function updateFile() {
const targetFile = path.join('/workspace', workspaceDir, 'readme.md');
const content = await fse.readFile(targetFile);
await fse.writeFile(targetFile, content.toString() +'\n\nThis is a demo file.', { encoding: 'utf8' });
}