在我的前一篇文章《在浏览器中用 JS 管理文件》中,我演示了我们可以通过 JS 在浏览器中访问文件系统,并且浏览器自身也存在域私有文件系统。今天,我将更深入地探索 JavaScript 的能力,展示它自身具备处理 tarball 文件的能力,而我们也不需要一个服务器运行时来实现。
然而,我也不会从非常底层的地方开始,因为处理 tarball 文件在底层是一件非常复杂的事。我已经创建了一个 Tarball API 的抽象,它在 JsExt 扩展库中,我们可以通过它来很容易地操作 tarball 文件。
这个 Tarball
API 被设计为通用的,它并不是一个将文件存档为 .tar
文件的工具,或者一个从 .tar
文件中解压文件的工具。相反,一个 Tarball
实例表示一个 tarball 存档自身,我们可以往它里面增加新文件,移除旧文件,查看它包含哪些文件,和许多 Linux 系统中的存档管理器类似。
Tarball
API 并不依赖任何服务器运行时所特有的 API,它只依赖现代的 Web API,主要是 ReadableStream
API,所有的现代浏览器以及服务器运行时如 Node.js,Deno 和 Bun,以及一些边缘运行时,如 Cloudflare Workers 中都是可用的。因此,这个 API 可以被用于任何 JavaScript 环境中。
导入 Tarball API
要导入 Tarball
API,我们可以使用下面的方式,取决于我们应用程序中的设置。
// In Node.js, Deno (JSR), Bun, Cloudflare Workers, Browsers (with bundler or import map)
import { Tarball } from "@ayonli/jsext/archive";
// Or in Deno with URL import
import { Tarball } from "https://ayonli.github.io/jsext/archive.ts";
// Or in the browser with URL import
import { Tarball } from "https://ayonli.github.io/jsext/esm/archive.js";
TypeScript创建一个 Tarball 实例
有两种方法可以用来创建 Tarball
实例,一个是使用 new
关键字初始化一个空 tarball,另一种是通过 Tarball.load
方法来载入一个现有的 .tar
文件。
初始化一个空 tarball
const tarball = new Tarball();
TypeScript载入一个 tar 文件
Tarball.load
方法接受一个 ReadableStream
实例,而不是一个文件地址或者 URL,这意味着它可以从任何地方载入 tar 文件。例如,从文件系统中:
// Load from the file system (even the browser's OPFS)
import { createReadableStream } from "@ayonli/jsext/fs";
const input = createReadableStream("/file/to/archive.tar");
const tarball = await Tarball.load(stream);
TypeScript或者从一个 HTTP 响应中:
// From the response of fetch
const res = await fetch("https://example.com/file/to/archive.tar");
const tarball = await Tarball.load(res.body!);
TypeScript我们也可以载入一个压缩的 .tar.gz
文件:
const res = await fetch("https://example.com/file/to/archive.tar.gz");
const tarball = await Tarball.load(res.body!, { gzip: true });
TypeScript往 Tarball 中添加新文件
现在我们已经有一个 Tarball
实例,我们可以向它添加新的文件,即时这个实例是从一个已有文件的 tar 文件载入的。
const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
tarball.append(file);
TypeScript我们也可以将文件夹或者一个包含文件夹路径的文件增加到 tarball 中。并且当我们增加包含文件夹路径的文件而文件夹不存在于 tarball 中时,这个文件夹将会被自动创建。例如,下面的代码将会追加一个 foo/bar.txt
到 tarball 中,并且会自动在追加文件前新增 foo
文件夹。
const file = new Blob(["This is some content"], { type: "text/plain" });
tarball.append(file, { relativePath: "foo/bar.txt" });
// Now the tarball will have both the `foo` directory and the `bar.txt` file in the directory.
TypeScript从 tarball 中取回文件
我们也可以从 tarball 中取回之前存入的文件,例如:
import { readAsText } from "@ayonli/jsext/reader";
const entry = tarball.retrieve("hello.txt")!;
const content = await readAsText(entry.stream);
console.log(content); // Hello, World!
TypeScript列出 tarball 中所有的条目
有两种方式可以用来显示 tarball 中的条目,一个是遍历 tarball 实例本身,而另一个则是使用 treeView
方法创建一个树状试图,我们可以浏览所有的条目以及它们的子条目,并以目录等级来排序。
遍历 tarball
for (const entry of tarball) {
if (entry.kind === "directory")
console.log(`Directory: ${entry.name}; Path: ${entry.relativePath}`);
} else {
console.log(`File: ${entry.name}; Path: ${entry.relativePath}; Size: ${entry.size}; Last-Modified: ${entry.mtime}`);
}
}
TypeScript以树状试图展示 tarball 内容
const tree = tarball.treeView();
console.log(tree);
TypeScript将 tarball 保存为文件
和初始化过程类似,Tarball
API 并不提供受限于文件系统的方法来保存为文件,而是,它提供了一个 stream
方法返回一个 ReadableStream
,我们可以用管道方法将它存储到文件中:
// Save the tarball to the file system (even the browser's OPFS)
import { createWritableStream } from "@ayonli/jsext/fs";
const output = createWritableStream("/file/to/archive.tar");
await tarball.stream().pipeTo(output);
TypeScript或者我们也可以将 tarball 上传到一个 URL 中:
const res = await fetch("https://example.com/path/to/archive.tar", {
method: "PUT",
body: tarball.stream(),
headers: {
"Content-Type": "application/x-tar",
},
});
TypeScript另外,我们也可以在保存之前压缩 tarball:
const res = await fetch("https://example.com/path/to/archive.tar.gz", {
method: "PUT",
body: tarball.stream({ gzip: true }),
headers: {
"Content-Type": "application/gzip",
},
});
TypeScript写在最后
Tarball
API 也提供了其他方法,例如 remove
方法用于将文件从 tarball 中移除,replace
方法用于替换 tarball 中的条目。
除 Tarball
类之外,还有一个 tar
函数和一个 untar
函数,它们简化了在文件系统中处理 .tar
文件的过程,并且反映了 Unix/Linux 系统中 tar -c
和 tar -x
命令的行为,在特定的场景中,它们显得更加有用。
如果你感兴趣,可以访问 JsExt 库的 GitHub 页面,这个库旨在成为 JavaScript 语言的扩展,它使用现代 Web 标准编写并能工作于几乎任何运行时中。谁知道呢,可能它其中的一些模块正好符合你的特殊需求。
它所突出的一些特性包括:
- 各种用于处理内置数据类型但并不内置的函数
- 各种用于扩展流程控制能力的函数
- 多线程的 JavaScript,使用并行线程
- 统一文件系统 API,同时适用于服务器与浏览器环境
- 在 CLI 和 Web 应用中打开对话框
- 使用相同的方式处理文件路径和 URL
- 轻松地处理字节数组和可读流
- 在任何运行时中创建、解压和预览归档文件
- 以及更多其他特性…