📂

File System Access APIの使い方

2022/11/26に公開約4,000字

Jitoinを開発しているitteと申します。Webエンジニア向けのサービスや記事を公開しています。

今回はJavaScriptのFile System Access APIの使い方をまとめてみました。

File System Access APIを使えば、ブラウザからローカルファイルもしくはディレクトリにに対してread/writeができます。
ユーザーが許可したファイル、もしくは許可したディレクトリ以下にしかアクセスできませんので安全です。

このAPIはFireFoxとスマホ未対応で、今後仕様変更の可能性もありますが、必要最低限の機能は揃っています。

File System Access APIの使い方

すべての使い方はMDNをご覧ください。

https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API

ディレクトリハンドルの取得

ディレクトリハンドルオブジェクトを使うとディレクトリに対して操作ができるようになります。
これを取得するには主に2つの方法があります。

showDirectoryPicker()関数

この関数を使うと、ダイアログが立ち上がり、ユーザーにディレクトリを選択してもらうことができます。
関数はPromiseを返します。ユーザーがディレクトリを選択するとPromiseが解決し、選択をキャンセルするとrejectになります。
ファイル操作はPromiseが多いためawaitを使ったほうが書きやすいです。

let dirHandle = await window.showDirectoryPicker()

もし、ディレクトリを開く時点で書き込みを許可する場合は、modeオプションを"readwrite"にします。ちなみに指定しなくても初めて書き込むときに、ブラウザがユーザーに許可をとってくれるため、指定しないほうがユーザーは安心すると思います。

書き込みも許可するとき
let dirHandle = await window.showDirectoryPicker({ mode: "readwrite" })

dropイベント

ブラウザで開いているページにディレクトリをドラッグ&ドロップしたときに、dropイベントからディレクトリハンドルを取得することができます(SafariとFirefoxは未対応)。

ディレクトリだけでなく、ファイルも取得してしまいますので、kindプロパティでディレクトリかどうか判別します。

document.addEventListener('dragover', event => event.preventDefault())
document.addEventListener('drop', async event => {
  event.preventDefault()

  for (let item of event.dataTransfer.items) {
    let handle = await item.getAsFileSystemHandle()
    if (handle.kind === 'directory') {
      let dirHandle = handle
      ...
    }
  }
})

ディレクトリハンドルの操作

ファイル一覧を取得

for await...of文を使えば、ディレクトリ内のファイル(と子ディレクトリ)の一覧を一つずつ取得できます。

for await (let [name, handle] of dirHandle) {
  if (handle.kind === 'file') { // ファイルのとき
    ...
  } else { // ディレクトリのとき
    ...
  }
}

dirHandle.entries()メソッドでも同じイテレータが取得できます。名前だけを一つずつ取得するkeys()メソッドや、ハンドルオブジェクトだけを一つずつ取得するvalues()メソッドもあります。

子ディレクトリを開く(作る)

ディレクトリの名前が分かっている場合はgetDirectoryHandle()を使って子ディレクトリのハンドルを取得できます。

let childDirHandle = await dirHandle.getDirectoryHandle('child')

なお、createオプションをtrueにするとディレクトリが無い時に新規作成します。

let childDirHandle = await dirHandle.getDirectoryHandle('child', { create: true })

ファイルを開く(作る)

ファイルの名前が分かっている場合はgetFileHandle()を使ってファイルのハンドルを取得できます。

let fileHandle = await dirHandle.getFileHandle('file.txt')

なお、createオプションをtrueにするとファイルが無い時に新規作成します。

let fileHandle = await dirHandle.getFileHandle('file.txt', { create: true })

ファイルハンドルの操作

Fileオブジェクトを取得

ファイルの情報を読み取るために、Fileオブジェクトを取得できます。
これにより<input type="file">を使ったときと同じようにファイルに対する処理が可能となります。

let file = await fileHandle.getFile()

Fileに書き込む

ファイルに書き込みを行うにはcreateWritable()メソッドで書き込みストリームを取得します。

上書き
let writableStream = await fileHandle.createWritable()
await writableStream.write('Hello\n')
await writableStream.close()

write()で書き込みできるのはArrayBuffer、TypedArray、DataView、Blob、そして文字列です。

上書きせずにファイルの末尾に追記するときはkeepExistingDataオプションをtrueにし、seek()で書き込み開始位置を末尾まで移動する必要があります。

追記
let writableStream = await fileHandle.createWritable({ keepExistingData: true })
let file = await fileHandle.getFile()
await writableStream.seek(file.size)
await writableStream.write('World\n')
await writableStream.close()

File System Access APIの用途

基本的な使い方はこんなところでしょうか。
これにより、次のようなことができるようになります。

  • ローカルファイルへの書き込み
  • ローカルディレクトリ単位でファイルの読み込み
  • ダウンロードボタンを押さずにファイルを保存

ゲームデータやペイントアプリの保存に活用できると思います。このAPIをフル活用しているものと言えばVSCode Onlineです。

https://vscode.dev/

私はコードエディタとしてではなくメモ帳として使っています笑

ElectronやWebView2を使わなくても、色々なことがWebアプリでできるようになってきていますね。

気になること

便利なAPIなのですが、気になることがひとつ。

確認アラート

これは、ディレクトリの読み取りを許可するかどうか確認するEdgeのアラートなのですが、小さすぎます(ChromeもOperaも同じ)。
読み取りと書き込みの両方の確認のときは「表示」が「編集」になるだけです。

わかりにくい

これだと、よく理解せずに進んでしまう人が増えるので、悪意あるWebサイトが読み取りだけに見せかけて、こっそりウイルスを仕込むことが簡単にできそうです。各ブラウザにはぜひ改善して欲しいです。

Discussion

ログインするとコメントできます