Modern browsers can do a lot more than we commonly know. Today, I’m going to introduce a topic that is familiar in server-side programming, but lesser-known in frontend development: accessing the file system and managing files and directories.
Yes, you’re not misread. File system management is possible in modern browsers by JavaScript, via the File System API. However, this API is very verbose and low-level, it would be much easier if we could manipulate files as we do in Node.js. In other words, we need a higher-level API to operate on the file system. And we’ll introduce it in the following examples.
Most browsers support the Origin Private File System, which is a virtual file system in the browser. But today I’m not going to talk about it. Instead, I choose to operate on the real file system of the physical device. Currently, only Chrome allows us to access the device’s file system. So we’ll be using Chrome to demonstrate its ability.
If you’re already in the desktop Chrome browser, you can press F12
to open the Chrome Devtools and switch to the Console
tab, because the following examples can be directly run in the Chrome Console, you can just copy and paste the code to the console and see the actual effect immediately yourself. If you’re not in a desktop version of Chrome, you may just read the examples, they’re easy to understand as well.
Import the fs module
Since we’re in the Chrome console, we’ll use the import()
function to import the fs
module.
const fs = await import("https://ayonli.github.io/jsext/esm/fs.js")
TypeScriptIf we’re writing a JavaScript module file, we should use the import
statement instead.
import * as fs from "https://ayonli.github.io/jsext/esm/fs.js"
// or with a package manager
import * as fs from "@ayonli/jsext/fs"
TypeScriptObtain the root directory
In the browser, the file system APIs by default use Origin Private File System, we don’t need to do anything to use it, just like in Node.js. However, as I’ve said earlier, in this article, we’re going to operate on the real file system of the computer, so we need to obtain a directory handle and pass it into the functions we’re going to use.
const root = await window.showDirectoryPicker()
TypeScriptThis code will open a dialog allowing us to choose a directory to operate in. Here, I choose a folder in the ~/Public
directory named demo
, for demonstration. It will be used as the root directory for the rest examples of this article.
The browser will pop up a message box asking for permission to view files in the selected folder, just click View files
to allow.
Read the entries of the root directory
Now we have a root directory handle, we can use the familiar file system APIs to access its contents. First, let’s see what’s inside the root directory now.
const entries = await Array.fromAsync(fs.readDir("/", { root }))
console.log(entries)
TypeScriptThe above code will print all the files and folders in the root
directory. Since the directory I chose is empty now, the code prints an empty array in the console.
Additionally, we can use the readTree
function to retrieve a tree structure of the directory instead.
const tree = await fs.readTree("/", { root })
console.log(tree)
TypeScriptMy console printed something like this:
Create a sub-directory
For demonstration, we’ll create a folder in the root
directory named foo
, and place other files inside it.
await fs.mkdir("/foo", { root })
TypeScriptThe browser will pop up another message box asking for permission to save changes to the root
directory, just click Save changes
to allow.
Now if I run the readTree
function again, we’ll see something like this:
Now let’s see the real effect through the file browser.
The new folder is written to the local file system as expected.
Create a file and read/write the content of it
Now let’s explore the ability to read and write files in the file system.
await fs.writeFile("/foo/hello.txt", "Hello, World!", { root })
TypeScriptconst content = await fs.readFileAsText("/foo/hello.txt", { root })
console.log(content)
TypeScriptWe will see the following output in the console:
And we can see the file in the file browser as well:
Final result
Now let’s run readTree
again, we’ll see something like this, showing all the entries in the root
directory in a tree structure.
console.log(await fs.readTree("/", { root }))
TypeScriptThat’s it. Managing files in the browser is as easy as in Node.js (actually easier since this fs
module provides more handy functions than Node.js).
Feel free to check the @ayonli/jsext/fs module for more functions to work with, or explore the @ayonli/jsext library for other features it provides.
Comments