KV ストレージ - ウェブ初の組み込みモジュール

ブラウザ ベンダーとウェブ パフォーマンスの専門家は、過去 10 年間のほとんどの間、localStorage は遅いため、ウェブ デベロッパーは使用を中止すべきだと主張してきました。

正直なところ、そう言っている人は間違っていません。localStorage はメインスレッドをブロックする同期 API であり、アクセスするたびにページが操作できなくなる可能性があります。

問題は、localStorage API が非常にシンプルで魅力的であることです。localStorage の非同期の代替手段は IndexedDB のみですが、これは(正直なところ)使いやすさや親しみやすい API として知られているわけではありません。

そのため、デベロッパーは、使いにくいものとパフォーマンスに悪影響を及ぼすもののどちらかを選択する必要があります。localStorage API のシンプルさを提供しながら、実際には非同期ストレージ API を使用するライブラリはありますが、アプリにそのようなライブラリを 1 つ含めるとファイルサイズのコストが発生し、パフォーマンス バジェットを消費する可能性があります。

ファイルサイズのコストをかけることなく、localStorage API のシンプルさで非同期ストレージ API のパフォーマンスを実現できたらどうでしょうか。

まもなく対応できるようになります。Chrome では、組み込みモジュールと呼ばれる新機能をテストしています。最初にリリースする予定の機能は、KV Storage という非同期のキー/値ストレージ モジュールです。

KV Storage モジュールの詳細に入る前に、組み込みモジュールについて説明します。

組み込みモジュールとは

組み込みモジュールは、通常の JavaScript モジュールと同じですが、ブラウザに付属しているためダウンロードする必要はありません。

従来のウェブ API と同様に、組み込みモジュールは標準化プロセスを経る必要があります。各モジュールには独自の仕様があり、出荷前に設計レビューと、ウェブ デベロッパーと他のブラウザ ベンダーの両方からのサポートの明確な兆候が必要です。(Chrome では、組み込みモジュールは、すべての新しい API の実装とリリースに使用される同じリリース プロセスに従います)。

従来の Web API とは異なり、組み込みモジュールはグローバル スコープで公開されず、インポートでのみ使用できます。

組み込みモジュールをグローバルに公開しないことには多くの利点があります。新しい JavaScript ランタイム コンテキスト(新しいタブ、ワーカー、Service Worker など)の起動にオーバーヘッドが追加されず、実際にインポートされない限り、メモリや CPU を消費しません。さらに、コードで定義されている他の変数との命名の競合が発生するリスクもありません。

組み込みモジュールをインポートするには、接頭辞 std: の後に組み込みモジュールの ID を指定します。たとえば、サポートされているブラウザでは、次のコードを使用して KV Storage モジュールをインポートできます(サポートされていないブラウザで KV Storage ポリフィルを使用する方法については、以下をご覧ください)。

import storage, {StorageArea} from 'std:kv-storage';

KV Storage モジュール

KV Storage モジュールは、localStorage API と同様にシンプルですが、API の形状は実際には JavaScript Map に近いです。getItem()setItem()removeItem() の代わりに、get()set()delete() が使用されます。また、localStorage では使用できない、keys()values()entries() などのマップのようなメソッドもあります。Map と同様に、キーは文字列である必要はありません。任意の構造化シリアル化可能型にできます。

Map とは異なり、すべての KV Storage メソッドは、Promise または非同期イテレータのいずれかを返します(このモジュールの主なポイントは、localStorage とは対照的に同期ではないことです)。API の詳細については、仕様をご覧ください。

上記のコード例からわかるように、KV Storage モジュールには、1 つのデフォルト エクスポート storage と 1 つの名前付きエクスポート StorageArea があります。

storage は、'default' という名前の StorageArea クラスのインスタンスであり、デベロッパーがアプリケーション コードで最も頻繁に使用するものです。StorageArea クラスは、追加の分離が必要な場合に使用します(データを保存し、デフォルトの storage インスタンス経由で保存されたデータとの競合を回避するサードパーティ ライブラリなど)。StorageArea データは、kv-storage:${name} という名前の IndexedDB データベースに保存されます。ここで、name は StorageArea インスタンスの名前です。

コードで KV Storage モジュールを使用する方法の例を次に示します。

import storage from 'std:kv-storage';

const main = async () => {
  const oldPreferences = await storage.get('preferences');

  document.querySelector('form').addEventListener('submit', async () => {
    const newPreferences = Object.assign({}, oldPreferences, {
      // Updated preferences go here...
    });

    await storage.set('preferences', newPreferences);
  });
};

main();

ブラウザが組み込みモジュールをサポートしていない場合はどうなりますか?

ブラウザでネイティブ JavaScript モジュールの使用に慣れている方は、(少なくともこれまでは)URL 以外のものをインポートするとエラーが発生することをご存じでしょう。std:kv-storage は有効な URL ではありません。

そのため、すべてのブラウザが組み込みモジュールをサポートするまで待ってから、コードで使用できるようになりますか?という疑問が生じます。幸い、答えは「いいえ」です。

インポート マップという別の機能(試験運用中)のおかげで、1 つのブラウザがサポートし始めるとすぐに、組み込みモジュールを実際に使用できるようになります。

地図をインポートする

インポートマップは、基本的に、デベロッパーがインポート ID を 1 つ以上の代替 ID にエイリアスするメカニズムです。

これは、ブラウザがアプリケーション全体で特定のインポート ID を解決する方法を(実行時に)変更できるため、強力です。

組み込みモジュールの場合、アプリケーション コードでモジュールのポリフィルを参照できますが、組み込みモジュールをサポートするブラウザでは、そのバージョンが代わりに読み込まれます。

KV Storage モジュールで動作するようにインポートマップを宣言する方法は次のとおりです。

<!-- The import map is inlined into your page -->
<script type="importmap">
{
  "imports": {
    "/path/to/kv-storage-polyfill.mjs": [
      "std:kv-storage",
      "/path/to/kv-storage-polyfill.mjs"
    ]
  }
}
</script>

<!-- Then any module scripts with import statements use the above map -->
<script type="module">
  import storage from '/path/to/kv-storage-polyfill.mjs';

  // Use `storage` ...
</script>

上記のコードのポイントは、URL /path/to/kv-storage-polyfill.mjs2 つの異なるリソース(std:kv-storage と元の URL /path/to/kv-storage-polyfill.mjs)にマッピングされていることです。

そのため、ブラウザがその URL(/path/to/kv-storage-polyfill.mjs)を参照するインポート ステートメントを見つけると、まず std:kv-storage の読み込みを試みます。読み込めない場合は、/path/to/kv-storage-polyfill.mjs の読み込みにフォールバックします。

ここでも、この手法が機能するために、ブラウザがインポート マップまたは組み込みモジュールをサポートする必要がない点がポイントです。これは、インポート ステートメントに渡される URL がポリフィルの URL であるためです。ポリフィルは実際にはフォールバックではなく、デフォルトです。組み込みモジュールは段階的な機能強化です。

モジュールをまったくサポートしていないブラウザの場合はどうなりますか?

インポートマップを使用して組み込みモジュールを条件付きで読み込むには、実際に import ステートメントを使用する必要があります。つまり、モジュール スクリプト<script type="module">)を使用する必要があります。

現在、80% 以上のブラウザがモジュールをサポートしています。サポートしていないブラウザの場合は、module/nomodule 手法を使用して従来のバンドルを配信できます。nomodule ビルドを生成するときに、すべてのポリフィルを含める必要があります。これは、モジュールをサポートしていないブラウザは、組み込みモジュールもサポートしていないことが確実だからです。

KV Storage のデモ

古いブラウザをサポートしながら組み込みモジュールを使用できることを示すため、上記のすべての手法を取り入れ、現在すべてのブラウザで動作するデモを作成しました。

  • モジュール、インポート マップ、組み込みモジュールをサポートするブラウザでは、不要なコードは読み込まれません。
  • モジュールとインポート マップをサポートするが、組み込みモジュールをサポートしていないブラウザは、(ブラウザのモジュール ローダーを介して)KV Storage ポリフィルを読み込みます。
  • モジュールをサポートしているがインポートマップをサポートしていないブラウザでも、KV Storage polyfill が(ブラウザのモジュール ローダーを介して)読み込まれます。
  • モジュールをまったくサポートしていないブラウザは、レガシー バンドル(<script nomodule> 経由で読み込まれる)で KV Storage ポリフィルを取得します。

このデモは Glitch でホストされているため、ソースを表示できます。実装の詳細については、README をご覧ください。どのように構築されているか興味がある方は、ぜひご覧ください。

ネイティブ組み込みモジュールを実際に動作させるには、Chrome 74 以降でデモを読み込み、試験運用版のウェブ プラットフォーム機能フラグをオンにする必要があります(chrome://flags/#enable-experimental-web-platform-features)。

組み込みモジュールが読み込まれていることを確認できます。DevTools のソースパネルにポリフィル スクリプトが表示されず、代わりに組み込みモジュールのバージョンが表示されます(モジュールのソースコードを実際に検査したり、ブレークポイントを設定したりすることもできます)。

Chrome DevTools の KV Storage モジュールのソース

フィードバックをお寄せください

この概要では、組み込みモジュールでできることを簡単に説明しました。ご期待ください。デベロッパーの皆様には、KV Storage モジュール(およびここで説明したすべての新機能)をお試しいただき、フィードバックをお寄せいただければ幸いです。

この記事で説明した各機能についてフィードバックを送信できる GitHub リンクは次のとおりです。

サイトで現在 localStorage を使用している場合は、KV Storage API に切り替えて、すべてのニーズを満たしているかどうかを確認する必要があります。KV Storage オリジンのトライアルに登録すると、これらの機能を今すぐデプロイできます。すべてのユーザーがストレージ パフォーマンスの向上を享受できます。Chrome 74 以降のユーザーは、追加のダウンロード料金を支払う必要はありません。