ブラウザでファイルを同期的に扱う方法

Mar 20, 2026

4 min
💬

ブラウザで同期的なファイルI/Oが必要なときに知っておくべき3つの概念 — OPFS、Web Worker、FileSystemSyncAccessHandleを、それぞれの観点から整理しました。

1. 概要

ブラウザで同期的なファイルI/Oを扱うには、保存場所・実行コンテキスト・I/O方式という3つの問題を同時に解く必要があります。OPFS はブラウザ内部専用のストレージを提供し、Web Worker はブロッキング処理をメインスレッドの外で実行できるようにし、FileSystemSyncAccessHandle はOPFSのファイルを同期的に読み書きできるようにします。PyodideのブラウザファイルI/Oのissueでも、この3つの概念が一緒に登場します。

確認すべきポイントは次のとおりです。

  • OPFS がなぜ必要なのか、従来の方式とどう違うのか
  • Web Worker がファイルI/Oとどんな関係にあるのか
  • FileSystemSyncAccessHandle がどんな問題を解決するのか
  • 3つの概念がなぜいつも一緒に登場するのか

2. なぜOPFSが必要なのか

ブラウザのファイル処理は長い間、<input type="file">FileReader ベースの限定的なAPIに依存してきました。File APIは、ユーザーがfile inputやdrag and dropで提供したファイルを読み込んで処理する用途に近いものです。

その後 File System Access API が登場し、ユーザーの同意に基づいてローカルファイルを読み書きできるようになりましたが、ユーザーに見えるファイルシステムを扱うこととorigin内部専用ストレージは目的が異なります。OPFSはoriginごとのprivate storage endpointであり、ブラウザが管理するアプリ専用ストレージに近い存在です。

OPFSはこの問題を解決するために生まれました。 OPFS(Origin Private File System)は、origin(ウェブサイトのアドレス)単位で分離されたブラウザ内部専用のファイルシステムです。ユーザーに直接公開されないストレージなので、ユーザーに見えるファイルシステムより権限・セキュリティのフローがシンプルで、Worker内の同期アクセスハンドルと組み合わせれば、WASMベースのアプリでファイルI/Oモデルを合わせるのに適しています。

  • 容量制限があります — IndexedDBと同様にブラウザのストレージクォータに従い、navigator.storage.estimate() で使用量を確認できます。
  • サイトデータを消去すると削除されます。
  • ユーザーはファイルのパスを見つけられません。
  • 同期的なAPIを提供します — 他のメソッドはすべてasyncですが、OPFSだけは FileSystemSyncAccessHandle による同期アクセスをサポートします。ただし、Dedicated Web Worker内でのみ可能です。

NOTE

OPFSはorigin単位で分離されたブラウザ内部専用のファイルシステムです。セキュリティチェックや権限リクエストがないためasync overheadなしでアクセスでき、Dedicated Web Worker環境では同期的なファイルアクセスも可能です。

3. 3つの概念

OPFSWeb WorkerFileSystemSyncAccessHandle は、それぞれ異なる観点の概念です。

3.1. OPFS — どこに保存するか

OPFSはブラウザが管理するサンドボックス化されたファイルシステムです。originごとに独立したストレージを持ち、ファイルエクスプローラーには表示されません。アクセス方法は2つ — メインスレッド・Workerの両方で使えるasync APIと、Worker内でのみ使えるFileSystemSyncAccessHandleです。

JavaScript
// async API로 OPFS 접근 (메인 스레드에서도 가능)
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
 
// 쓰기
const writable = await fileHandle.createWritable();
await writable.write('hello');
await writable.close();
 
// 읽기
const file = await fileHandle.getFile();
const text = await file.text();

TIP

OPFSは「どこに保存するか」の問題です。origin単位で分離されたブラウザ内部のファイルシステムであり、アクセス方法はasync API(メイン/Worker)とFileSystemSyncAccessHandle(Worker専用)の2つです。

3.2. Web Worker — 誰が実行するか

JavaScriptは基本的にシングルスレッドです。すべてのコードがメインスレッドで実行され、このスレッドがUIレンダリング・イベント処理も担当するため、重い計算やブロッキングI/Oをメインスレッドで走らせると画面が固まります。Web Worker はメインスレッドから分離された別スレッドを作り、DOMに直接アクセスしない代わりにpostMessage() ベースのメッセージで通信します。

JavaScript
// 메인 스레드
const worker = new Worker('worker.js');
worker.postMessage({ type: 'start' });
worker.onmessage = (e) => console.log('결과:', e.data);
 
// worker.js
self.onmessage = async (e) => {
  // 여기서 블로킹 작업 수행
  self.postMessage({ result: '완료' });
};

TIP

Web Workerは「誰が実行するか」の問題です。メインスレッドのブロッキングを防ぐため、同期I/OはWorker内でのみ許可されています。

3.3. FileSystemSyncAccessHandle — どう読み書きするか

FileSystemSyncAccessHandle はOPFSファイルへのアクセス方法の1つで、名前のとおり同期的(Sync)に読み書きします。readwritegetSizeflushtruncateclose などのメソッドは同期的に動作します。ただし、このハンドルはOPFSファイルに対してのみ作成でき、Dedicated Web Worker内でのみアクセスできます。

JavaScript
// Worker 안에서
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('fast.bin', { create: true });
 
// 핸들 생성은 async (한 번만)
const accessHandle = await fileHandle.createSyncAccessHandle();
 
// 이 아래는 전부 동기 — await 없음
const encoder = new TextEncoder();
const buf = encoder.encode('Some data');
 
accessHandle.write(buf, { at: 0 });  // 동기 쓰기
accessHandle.flush();                // 디스크에 반영
const size = accessHandle.getSize(); // 동기 크기 조회
const view = new DataView(new ArrayBuffer(size));
accessHandle.read(view, { at: 0 });  // 동기 읽기
accessHandle.close();

read()/write() はブロッキングであり、メインスレッドを止めてしまう可能性があるため、仕様上Dedicated Web Worker内でのみ使用できます。createSyncAccessHandle() メソッド自体はPromiseを返す非同期呼び出しですが、返されたハンドルのファイル操作メソッドは同期的です。

WARNING

FileSystemSyncAccessHandle は仕様上、Dedicated Web Worker内でのみ使えます。メインスレッドで createSyncAccessHandle() を呼び出すとエラーになります。

4. 3つの概念が噛み合う構造

それぞれ異なる問題を解決しますが、「ブラウザで同期的なファイルI/Oを実装する」というゴールには3つが同時に必要です。

解決すべき問題担当する概念
速く、権限なしでファイルを永続化OPFS
同期的なread / writeの実装FileSystemSyncAccessHandle
ブロッキング呼び出しがUIを止めないようにWeb Worker

どれか1つでも欠けてはいけません。

  • OPFSの代わりにFile System Access API → ユーザーのローカルファイルへのアクセスはできるものの、セキュリティチェックで遅く、毎回権限ポップアップが出ます。(目的が異なる: File System Access API=ユーザーのファイル、OPFS=ブラウザ内部専用)
  • FileSystemSyncAccessHandle なしでasyncのみ → 同期的な f.read() パターンは不可能
  • Workerなしでメインスレッド → ブロッキング呼び出しがUIを停止

Sync I/O実行フローのダイアグラム

TIP

OPFSは保存場所、Web Workerは実行コンテキスト、FileSystemSyncAccessHandleはI/O方式です。互いに異なる観点であり、それぞれ別の問題を解決します。

5. OPFSアクセスAPIの比較

API同期かどうかOPFS専用読み/書き推奨コンテキスト
getFile()async読みのみメインスレッド / Worker
createWritable()async書きのみメインスレッド / Worker
createSyncAccessHandle()sync✅ OPFSのみ読み + 書きWorker推奨

6. まとめ

最初にPyodideのブラウザファイルI/Oの文脈を読んだときは3つの概念がひとまとまりに感じられましたが、観点ごとに分離すると明確になります。

  • OPFS — どこに保存するか(保存場所)
  • Web Worker — 誰が実行するか(実行コンテキスト)
  • FileSystemSyncAccessHandle — どう読み書きするか(I/O方式)

TIP

3つの概念はそれぞれ別の問題を解決しながら、「ブラウザで同期的なファイルI/O」という1つのゴールのために噛み合っています。

7. 参考文献