File System Access APIの使い方
Jitoinを開発しているitteと申します。Webエンジニア向けのサービスや記事を公開しています。
今回はJavaScriptのFile System Access APIの使い方をまとめてみました。
File System Access APIを使えば、ブラウザからローカルファイルもしくはディレクトリにに対してread/writeができます。
ユーザーが許可したファイル、もしくは許可したディレクトリ以下にしかアクセスできませんので安全です。
このAPIはFireFoxとスマホ未対応で、今後仕様変更の可能性もありますが、必要最低限の機能は揃っています。
File System Access APIの使い方
すべての使い方はMDNをご覧ください。
ディレクトリハンドルの取得
ディレクトリハンドルオブジェクトを使うとディレクトリに対して操作ができるようになります。
これを取得するには主に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です。
私はコードエディタとしてではなくメモ帳として使っています笑
ElectronやWebView2を使わなくても、色々なことがWebアプリでできるようになってきていますね。
気になること
便利なAPIなのですが、気になることがひとつ。
これは、ディレクトリの読み取りを許可するかどうか確認するEdgeのアラートなのですが、小さすぎます(ChromeもOperaも同じ)。
読み取りと書き込みの両方の確認のときは「表示」が「編集」になるだけです。
わかりにくい
これだと、よく理解せずに進んでしまう人が増えるので、悪意あるWebサイトが読み取りだけに見せかけて、こっそりウイルスを仕込むことが簡単にできそうです。各ブラウザにはぜひ改善して欲しいです。
Discussion