The problem#
While the web developer tools in Firefox and Chrome provide a Storage/Application tab for inspecting the local data stored by a web app, neither shows OPFS files there, making it difficult to tell what's going wrong when you have a bug (which was a problem when writting my recent blog posts about OPFS). There's open Firefox and Chromium bugs about the missing feature, so if it's been a while since this was posted when you're reading this, hopefully this is no longer a problem.
Additionally, the tools I did find all use recursion, resulting in them failing to work on the deeply nested directory tree I created by accident.
The solution#
If you don't have several hundred levels deep of nested directories, you can just use this Chrome extension or this script (or probably this web component, although I couldn't get it to install), all named "opfs-explorer".
The following AsyncIterator returns all of the files in OPFS without using recursion and adds properties to include their full path and parent directory:
async function* getFilesNonRecursively(dir) {
const stack = [[dir, "", undefined, 0]];
while (stack.length) {
const [current, prefix, parentDir] = stack.pop();
current.relativePath = prefix + current.name;
current.parentDir = parentDir;
current.depth = depth;
yield current;
if (current.kind === "directory") {
for await (const handle of current.values()) {
stack.push([handle,
prefix + current.name + "/",
current,
depth + 1]);
}
}
}
}
And here's the simple HTML display function I've been using that calls that (you will likely want to modify this to your preferences):
async function displayOPFSFileList() {
const existing = document.getElementById("opfs-file-list");
const l = document.createElement('ol');
l.id = "opfs-file-list";
if (existing) existing.replaceWith(l);
else document.body.appendChild(l);
const root = await navigator.storage.getDirectory();
for await (const fileHandle
of getFilesNonRecursively(root)) {
const i = document.createElement("li");
i.innerText = fileHandle.kind + ": "
+ (fileHandle.relativePath ?? "(root)");
if (fileHandle.kind === "file") {
const content = await fileHandle.getFile();
const contentStr = content.type.length === 0
|| content.type.startsWith("text/")
? ("\"" + (await content.slice(0, 100).text()).trim()
+ "\"")
: content.type;
i.innerText += ": (" + content.size + " bytes) "
+ contentStr;
}
l.appendChild(i);
}
}