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

ブラウザ ベンダーやウェブ パフォーマンスの専門家は、この 10 年間で localStorage は遅いので、ウェブ デベロッパーは使用をやめるべきだと述べてきました。

公平を期すと、この意見は間違っていないということです。localStorage はメインスレッドをブロックする同期 API であり、アクセスするとページがインタラクティブに動作しなくなる可能性があります。

問題は、localStorage API が非常にシンプルで、localStorage に代わる唯一の非同期的な代替手段が IndexedDB であることです。これは、使いやすさや快適な API で知られていません。

そのため、デベロッパーは使いにくいものか、パフォーマンスが低いものかの選択肢を余儀なくされます。また、内部で非同期ストレージ API を実際に使用しながら、localStorage API のシンプルさを提供するライブラリもありますが、アプリ内にそのようなライブラリの 1 つがあると、ファイルサイズの費用がかかり、パフォーマンス バジェットを消費する可能性があります。

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

まもなく発生する可能性があります。Chrome では、組み込みモジュールという新機能のテストを行っています。最初のリリースを予定しているのは、KV Storage という非同期の Key-Value ストレージ モジュールです。

ただし、KV ストレージ モジュールの詳細に入る前に、組み込みモジュールの意味について説明します。

組み込みモジュールとは

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

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

従来のウェブ API とは異なり、組み込みモジュールはグローバル スコープでは公開されません。インポートを介してのみ利用可能です。

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

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

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

KV ストレージ モジュール

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

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

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

storage は、'default' という名前の StorageArea クラスのインスタンスで、デベロッパーがアプリコードで頻繁に使用します。StorageArea クラスは、さらに分離が必要な場合(データを保存し、デフォルトの storage インスタンスを介して保存されたデータとの競合を回避する必要があるサードパーティ ライブラリなど)に提供されます。StorageArea データは、kv-storage:${name} という名前の IndexedDB データベースに保存されます。ここで、名前は 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 つでもサポートされればすぐに使用できます。これは、インポート マップという別の機能によってテストされています。

地図をインポート

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

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

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

インポート マップを宣言して KV ストレージ モジュールでこれを行う方法は次のとおりです。

<!-- 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.mjs が 2 つの異なるリソース(std:kv-storage と元の URL /path/to/kv-storage-polyfill.mjs)にマッピングされていることです。

そのため、その URL(/path/to/kv-storage-polyfill.mjs)を参照する import ステートメントが見つかると、ブラウザはまず std:kv-storage を読み込もうとします。できない場合は、代わりに /path/to/kv-storage-polyfill.mjs を読み込みます。

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

モジュールをまったくサポートしていないブラウザの場合

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

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

KV ストレージのデモ

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

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

デモは Glitch でホストされているため、ソースを確認できます。 実装の詳細については、README をご覧ください。 どのように構築されているのか気になる方は、ぜひチェックしてみてください。

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

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

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

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

これで、組み込みモジュールで何が可能になるかを確認できたはずです。ご興味がおありでしたら、デベロッパーの皆様は、KV Storage モジュール(およびここで説明したすべての新機能)をお試しいただき、フィードバックをお寄せください。

次の GitHub リンクから、この記事で言及した各機能に関するフィードバックをお寄せください。

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