1. 概要 — 何を解決するか
KASUGAI Canvas は描画ライブラリや WASM ランタイムを CDN(jsdelivr 等)から読み込む構成のため、なにもしなければ セッションごとに重い資産を再ダウンロードします。とりわけ DuckDB-WASM は本体・拡張あわせて約60MBあり、ブラウザ任せの HTTP キャッシュでは失効のタイミングを制御できません。
そこで Service Worker が CDN への GET を横取りし、CacheStorage へ永続保存します。一度取得した資産はブラウザのキャッシュ失効ポリシーに左右されず、再読み込み・ブラウザ再起動後もキャッシュから即座に供給されます。
2本の Service Worker
本ドキュメントではこの2本を 「DuckDB-WASM 永続キャッシュ」(sw-duckdb.js) と 「vendor 同一オリジン中継」(vendor-proxy-sw.js) と呼びます。
| SW(呼称) | ファイル | 役割 | CacheStorage |
|---|---|---|---|
| DuckDB-WASM 永続キャッシュ | web/sw-duckdb.js | DuckDB-WASM の CDN 資産(約60MB)を永続キャッシュ | kasugai-duckdb-wasm |
| vendor 同一オリジン中継 | web/vendor-proxy-sw.js | vendor/* を上流オリジナルへ中継し同一オリジンとして供給。成功応答は保険としてキャッシュ | kasugai-vendor-v1 |
ブラウザから見た構成図
flowchart TB
subgraph BR["ブラウザ(ページ=web/ スコープ配下)"]
ENG["エンジンページ
cesium / gsimaps / navara 等"]
PROBE["duckDbAssetsCached()
キャッシュ済みなら非表示の
クエリ系レイヤー分も DuckDB を
先行初期化"]
end
subgraph SW["Service Worker(スコープ web/ ・常に1本のみ active)"]
DSW["sw-duckdb.js
=「DuckDB-WASM 永続キャッシュ」"]
VSW["vendor-proxy-sw.js
=「vendor 同一オリジン中継」"]
end
ENG -->|"cesium 起動時 register"| DSW
ENG -->|"vendor-guard.js 経由 register
(gsimaps / navara)"| VSW
DSW <-.->|"エンジン切替で制御交代
controllerchange 待機+1回リロード"| VSW
ENG -->|"ページ・Worker 内の全 fetch"| ACTIVE{"active SW の
fetch ハンドラ"}
SW --- ACTIVE
ACTIVE -->|"@duckdb/duckdb-wasm@*
extensions.duckdb.org"| HITQ{"kasugai-duckdb-wasm
ヒット?"}
HITQ -->|"hit"| RESP1["キャッシュから即応答
(ネットワーク不使用)"]
HITQ -->|"miss → 取得後 cache.put"| EXT1[("jsdelivr /
extensions.duckdb.org")]
PROBE -.->|"keys() で本体 wasm の有無を確認"| HITQ
ACTIVE -->|"vendor/*(同一オリジン)"| PINQ["vendor.json のピンへ解決
npm/gh → jsdelivr
失敗時 raw.githubusercontent
(?u= でミラーモード)"]
PINQ --> EXT2[("上流オリジナル /
?u= ミラー")]
EXT2 -->|"Content-Type 包み直し
+kasugai-vendor-v1 に保険保存"| RESP2["同一オリジンとして応答"]
EXT2 -.->|"上流不通時"| VCACHE[("kasugai-vendor-v1")]
VCACHE -.-> RESP2
ACTIVE -->|"対象外のリクエスト"| PASS["ネットワークへ素通し
(タイル・レイヤーデータ等)"]
classDef duckdb fill:#eef0ff,stroke:#667eea,color:#1a2230
classDef vendor fill:#e9f7ee,stroke:#2e9e56,color:#1a2230
classDef neutral fill:#f6f8fa,stroke:#9aa5b1,color:#5a6577
class DSW,HITQ,RESP1,EXT1,PROBE duckdb
class VSW,PINQ,EXT2,VCACHE,RESP2 vendor
class ENG,ACTIVE,PASS neutral
2. sw-duckdb.js — DuckDB-WASM 永続キャッシュ
web/cesium/index.html が起動時に navigator.serviceWorker.register("../sw-duckdb.js") で登録します(スコープは web/ ルート)。登録は Cesium エンジンのみが行います。
対象資産(約60MB)
cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@*— WASM 本体(約33MB)・ワーカースクリプト・+esm/dist 配下すべてextensions.duckdb.org—INSTALL spatial(約22MB)・httpfs・parquet拡張の内部取得
Worker 内部のフェッチも対象です。DuckDB が内部で行う importScripts や拡張 INSTALL の取得も SW スコープ配下の fetch として横取りされるため、利用者側で意識する必要はありません。
キャッシュ戦略
- キャッシュキーは URL そのまま。
@duckdb/duckdb-wasm@1.32.0・拡張v1.4.3のようにバージョンが URL に含まれるため、キーの衝突は起きません - cache-first:キャッシュにあればネットワークに行かず返す。無ければ取得して
cache.put(成功応答のみ・GET 要求のみ対象) - activate 時に旧バージョンを掃除:
CURRENT_ASSET_MARKERS(現行は/npm/@duckdb/duckdb-wasm@1.32.0と/v1.4.3/)に一致しないエントリだけを削除します。DuckDB のバージョンを上げると新資産が自動的にダウンロードされ、旧資産は次回起動時に消えます
キャッシュ有無で起動動作が変わります。ページ側も duckDbAssetsCached()(app.js)で同じ CacheStorage を読み、本体 wasm の存在を確認します。キャッシュ済みなら非表示のクエリ系レイヤーぶんまで含めて DuckDB を先行初期化します(ディスク読み込みで実質無料のため選択をほぼ即応答にできる)。未キャッシュ=実質初回は ~60MB の実コストがかかるため、表示対象のクエリ系レイヤーがあるときだけ先行します。
バージョンを上げるときは sw-duckdb.js の CURRENT_ASSET_MARKERS と web/cesium/app.js の DUCKDB_WASM_VERSION を同時に更新してください(片方だけだと掃除対象がずれます)。
3. vendor-proxy-sw.js — vendor 同一オリジン中継
gsimaps・navara エンジンが web/shared/vendor-guard.js 経由で登録します。主目的はキャッシュではなく同一オリジン中継です:web/vendor/ は配布物・CDN どこにも実体を置かず、この SW が vendor/<name>/* への GET/HEAD を拾い、各エンジンの vendor.json がピン留めする上流オリジナルから供給します。
上流解決(vendor.json のピン)
- npm ピン(navara):
https://cdn.jsdelivr.net/npm/<pkg>@<ver>/<tail> - github ピン(gsimaps):
https://cdn.jsdelivr.net/gh/<repo>@<sha>/<rest>→ 失敗時raw.githubusercontent.comへ再試行 - SW スクリプト URL の
?u=<base>で自前ミラーモード(?hosted=/ localStoragekasugai_hosted_baseで指定。ミラー失敗時はオリジナルピンへフォールバック)
応答の包み直しとキャッシュ
- 上流応答は
new Responseで包み直し、Content-Type を拡張子から決め打ちします(jsdelivr/gh は text/plain を返すため、module script・Worker・iframe の MIME 厳格判定で拒否されるのを回避) - 成功応答は
kasugai-vendor-v1に保存し、上流不通時の保険として使います。転送はcache: "no-store"で毎回上流を見るため、ピン更新後の陳腐化は起きません - Range / 条件付きヘッダ(if-none-match 等)は上流へ転送します
4. スコープ共有と制御の交代(重要)
同一スコープで active になれる Service Worker は1つだけです。web/ スコープを sw-duckdb.js と vendor-proxy-sw.js が共用するため、Cesium ↔ vendor エンジン(gsimaps・navara)の切替では制御の取り合いが起きます。
制御交代の流れ
- vendor エンジンのページを開く →
vendor-guard.jsがvendor-proxy-sw.jsを register → SW 更新が走り install→activate→clients.claimで制御を取る - ガードは
controllerchangeとcontroller.scriptURLの一致で制御確定を待ってからエンジンを起動します(確定前に進むとvendor/*が中継されず 404 で起動不能) - 制御が確定できない場合は sessionStorage フラグで1回だけリロードして SW 制御下で読み直します(無限リロード防止)
- Cesium に戻ると
sw-duckdb.js側が register され、再び制御が交代します
sw-duckdb.js の fetch ハンドラは対象外 URL を無視するだけで、制御中に vendor/* を中継することはありません。そのため vendor エンジンでは必ずガード経由で vendor-proxy-sw.js への交代を待つ必要があります。
5. キャッシュ対象外の資産
永続キャッシュの対象は上記2系統だけです。以下は通常の HTTP キャッシュ(ブラウザ任せ・失効あり)に留まります。
| 資産 | 扱い |
|---|---|
sql.js(GPKG 読込用 SQLite WASM、gpkg-worker.js) | jsdelivr から都度取得。Worker 内でモジュールは1回だけ初期化(セッション内再利用)されるが、CacheStorage 永続化の対象外のためブラウザ再起動後は再取得され得る |
| CesiumJS / MapLibre / OpenLayers / Leaflet 等の描画ライブラリ | HTTP キャッシュのみ(navara は vendor-proxy-sw.js 経由で永続化される) |
| hyparquet / flatgeobuf / fflate 等のデコーダ | HTTP キャッシュのみ |
| レイヤーデータ本体(.gpkg / .parquet / .geojson 等) | 永続キャッシュしない。DuckDB/GPKG は開いた DB を Worker が保持するためセッション内の再クエリは速いが、ファイルの再ダウンロードはセッションごとに発生する |
DuckDB 系の拡張 INSTALL は初回利用時(duckdb:/sql: レイヤーが表示ON・属性・検索で選択された時点)に発生します。起動直後に 60MB を取得するわけではありません。
6. 前提条件・制限
- セキュアコンテキスト必須:Service Worker は HTTPS または localhost でのみ有効。静的ホスティング(GitHub Pages 等)でも動作します
- サイトデータの削除でキャッシュも消えます。ブラウザの「閲覧データ削除」やストレージ枯渇時の強制 eviction 後は、次回利用時に再ダウンロードが発生します
- GET のみ対象(vendor-proxy は HEAD も)。POST 等は素通りです
- sw-duckdb.js は対象資産以外の通信に介入しません。タイル・GeoJSON 等の地図データ取得は通常経路のままです(vendor-proxy-sw.js は
vendor/*のみを対象とします) - オフライン化は目的としていません。永続キャッシュは「再ダウンロード削減」であって、オフライン動作を保証するものではありません
7. 動作確認方法
ブラウザ DevTools での確認
- DevTools → Application → Service Workers:現在有効な SW(
sw-duckdb.jsまたはvendor-proxy-sw.js)とスコープを確認 - Application → Cache Storage:
kasugai-duckdb-wasm・kasugai-vendor-v1のエントリを確認 - 2回目の起動で Network パネルの duckdb 資産が
(ServiceWorker)またはディスク/メモリキャッシュ扱いで即座に返れば永続キャッシュが効いています
8. 関連リンク
- .kasc 設定仕様 — duckdb:/sql:/gpkg: レイヤーの設定とランタイム読み込み動作
- データ形式 — DuckDB-WASM・sql.js 等のデコード経路
- V6: マルチエンジン — vendor 依存エンジン(gsimaps・navara)の構成
- システム構成 — Web Worker によるロード・変換層の位置づけ
- ← ドキュメントトップへ