公開日: 2021 年 7 月 22 日
ルーティングは、すべてのウェブ アプリケーションの重要な部分です。ルーティングは、URL を取得し、パターン マッチングやその他のアプリ固有のロジックを適用して、通常は結果に基づいてウェブ コンテンツを表示します。ルーティングはさまざまな方法で実装できます。
- ディスク上のファイルにパスをマッピングするサーバーコード
- 現在のロケーションの変更を待ってから、対応する DOM を作成して表示するシングルページ アプリのロジック。
明確な標準は存在しませんが、ウェブ デベロッパーは、regular expressions と共通点の多い URL ルーティング パターンを表現するための共通構文に傾倒しています。ただし、パスセグメントを照合するためのトークンなど、ドメイン固有の追加機能もいくつかあります。Express や Ruby on Rails などの一般的なサーバーサイド フレームワークでは、この構文(またはそれに近い構文)が使用されています。JavaScript デベロッパーは、path-to-regexp や regexpparam などのモジュールを使用して、このロジックを独自のコードに追加できます。
URLPattern
は、これらのフレームワークによって作成された基盤の上に構築されたウェブ プラットフォームの追加機能です。この提案の目的は、ワイルドカード、名前付きトークン グループ、正規表現グループ、グループ修飾子のサポートなど、ルーティング パターン構文を標準化することです。この構文で作成された URLPattern インスタンスは、完全な URL または URL pathname との照合、トークンとグループの一致に関する情報の返信など、一般的なルーティング タスクを実行できます。
ウェブ プラットフォームで URL マッチングを直接提供するもう 1 つのメリットは、URL と照合する必要がある他の API と共通の構文を共有できることです。
ブラウザのサポートとポリフィル
Chrome と Edge のバージョン 95 以降では、URLPattern がデフォルトで有効になっています。
urlpattern-polyfill ライブラリは、ブラウザや Node などの組み込みサポートがない環境で URLPattern インターフェースを使用する方法を提供します。ポリフィルを使用する場合は、機能検出を使用して、現在の環境でサポートされていない場合にのみ読み込むようにしてください。そうしないと、URLPattern の重要なメリットの 1 つである、サポート環境で追加のコードをダウンロードして解析しなくても使用できるという点が失われます。
if (!(globalThis && 'URLPattern' in globalThis)) {
// URLPattern is not available, so the polyfill is needed.
}
構文の互換性
URLPattern の基本理念は、再発明を避けることです。Express や Ruby on Rails で使用されるルーティング構文にすでに精通している場合は、新しいことを学ぶ必要はありません。ただし、一般的なルーティング ライブラリの構文には若干の相違があるため、基本構文として何かを選択する必要がありました。URLPattern の設計者は、path-to-regexp のパターン構文(API サーフェスは除く)を起点として使用することにしました。
この決定は、path-to-regexp の現在のメンテナーと緊密に協議したうえで行われました。
サポートされている構文のコアを理解するには、path-to-regexp のドキュメントを参照することをおすすめします。GitHub の現在の場所で、MDN で公開される予定のドキュメントを読むことができます。
その他の機能
URLPattern の構文は path-to-regexp がサポートする構文のスーパーセットです。URLPattern は、ルーティング ライブラリでは一般的ではない、ホスト名にワイルドカードを含むオリジンのマッチングをサポートしています。他のほとんどのルーティング ライブラリは、pathname のみ、または URL の search や hash の部分を処理します。自己完結型のウェブアプリ内の同一オリジン ルーティングにのみ使用されるため、URL のオリジン部分を確認する必要はありません。
オリジンを考慮することで、サービス ワーカーの fetch イベント ハンドラ内でクロスオリジン リクエストをルーティングするなど、ユースケースを増やすことができます。同一オリジンの URL のみをルーティングする場合は、この追加機能を無視して、他のライブラリと同様に URLPattern を使用できます。
例
パターンの構築
URLPattern を作成するには、そのコンストラクタに文字列または照合するパターンに関する情報を含むプロパティを持つオブジェクトを渡します。
オブジェクトを渡すと、各 URL コンポーネントのマッチングに使用するパターンを最も明示的に制御できます。最も詳細な出力は次のようになります。
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
search: '*',
hash: '*',
});
プロパティに空の文字列を指定すると、URL の対応する部分が設定されていない場合にのみ一致します。ワイルドカード * は、URL の指定された部分の任意の値と一致します。
コンストラクタには、より簡単に使用するためのショートカットがいくつか用意されています。search や hash などのプロパティを完全に省略することは、それらを '*' ワイルドカードに設定することと同じです。この例は次のように簡略化できます。
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
});
さらに、オリジンに関するすべての情報を 1 つのプロパティ baseURL で指定することもできます。
const p = new URLPattern({
pathname: '/foo/:image.jpg',
baseURL: 'https://example.com',
});
これらの例はすべて、ユースケースに一致するオリジンが含まれていることを前提としています。オリジンを除いた URL の他の部分のみを照合する場合は(多くの単一オリジン ルーティング シナリオがこれに該当します)、オリジン情報を完全に省略して、pathname、search、hash プロパティの組み合わせのみを指定できます。以前と同様に、省略されたプロパティは、* ワイルドカード パターンに設定されているかのように扱われます。
const p = new URLPattern({pathname: '/foo/:image.jpg'});
コンストラクタにオブジェクトを渡す代わりに、1 つまたは 2 つの文字列を指定することもできます。文字列が 1 つ指定されている場合は、オリジンの一致に使用されるパターン情報を含む完全な URL パターンを表す必要があります。2 つの文字列を指定すると、2 つ目の文字列が baseURL として使用され、1 つ目の文字列はそのベースに対する相対パスとみなされます。
1 つの文字列が指定された場合でも 2 つの文字列が指定された場合でも、URLPattern コンストラクタは完全な URL パターンを解析して URL コンポーネントに分割し、大きなパターンの各部分を対応するコンポーネントにマッピングします。つまり、内部的には、文字列で作成された各 URLPattern は、オブジェクトで作成された同等の URLPattern と同じように表現されます。文字列コンストラクタは、冗長性の少ないインターフェースを好むユーザー向けのショートカットです。
const p = new URLPattern('https://example.com/foo/:image.jpg?*#*');
文字列を使用して URLPattern を作成する際には、いくつかの注意点があります。
オブジェクトを使用して URLPattern を構築するときにプロパティを省略すると、そのプロパティに * ワイルドカードを指定した場合と同じになります。完全な URL 文字列パターンが解析されるときに、URL コンポーネントのいずれかに値がない場合、そのコンポーネントのプロパティが '' に設定されているかのように扱われます。これは、そのコンポーネントが空の場合にのみ一致します。
文字列を使用する場合は、作成された URLPattern でワイルドカードを使用する場合は、明示的にワイルドカードを含める必要があります。
// p1 and p2 are equivalent.
const p1 = new URLPattern('/foo', location.origin);
const p2 = new URLPattern({
protocol: location.protocol,
hostname: location.hostname,
pathname: '/foo',
search: '',
hash: '',
});
// p3 and p4 are equivalent.
const p3 = new URLPattern('/foo?*#*', location.origin);
const p4 = new URLPattern({
protocol: location.protocol,
hostname: location.hostname,
pathname: '/foo',
});
文字列パターンをコンポーネントに解析する際に、あいまいさが生じる可能性があることにも注意してください。URL に含まれる文字の中には、: のようにパターン マッチング構文で特別な意味を持つものがあります。この曖昧さを回避するため、URLPattern コンストラクタは、これらの特殊文字は URL の一部ではなく、パターンの 一部であると想定します。曖昧な文字を URL の一部として解釈する場合は、文字列として指定するときに、\` character. For example, the literal URLabout:blankshould be escaped as'about\:blank'` でエスケープしてください。
パターンを使用する
URLPattern を構築したら、次の 2 つの方法で使用できます。test() メソッドと exec() メソッドはどちらも同じ入力を受け取り、同じアルゴリズムを使用して一致を確認しますが、戻り値のみが異なります。test() は、指定された入力に一致するものがある場合は true を返し、それ以外の場合は false を返します。exec() は、一致に関する詳細情報とキャプチャ グループを返します。一致がない場合は null を返します。次の例では exec() を使用していますが、ブール値の戻り値のみが必要な場合は、いずれの例でも test() に置き換えることができます。
test() メソッドと exec() メソッドを使用する方法の一つは、文字列を渡すことです。コンストラクタがサポートしているものと同様に、単一の文字列が指定された場合は、オリジンを含む完全な URL である必要があります。2 つの文字列が指定されている場合、2 番目の文字列は baseURL 値として扱われ、最初の文字列はそのベースを基準として評価されます。
const p = new URLPattern({
pathname: '/foo/:image.jpg',
baseURL: 'https://example.com',
});
const result = p.exec('https://example.com/foo/cat.jpg');
// result will contain info about the successful match.
// const result = p.exec('/foo/cat.jpg', 'https://example.com')
// is equivalent, using the baseURL syntax.
const noMatchResult = p.exec('https://example.com/bar');
// noMatchResult will be null.
または、コンストラクタがサポートするのと同じ種類のオブジェクトを渡すこともできます。このオブジェクトのプロパティは、照合する URL の部分のみに設定されます。
const p = new URLPattern({pathname: '/foo/:image.jpg'});
const result = p.exec({pathname: '/foo/:image.jpg'});
// result will contain info about the successful match.
ワイルドカードまたはトークンを含む URLPattern で exec() を使用すると、戻り値で入力 URL の対応する値に関する情報が提供されます。これにより、これらの値を自分で解析する手間を省くことができます。
const p = new URLPattern({
hostname: ':subdomain.example.com',
pathname: '/*/:image.jpg'
});
const result = p.exec('https://imagecdn1.example.com/foo/cat.jpg');
// result.hostname.groups.subdomain will be 'imagecdn1'
// result.pathname.groups[0] will be 'foo', corresponding to *
// result.pathname.groups.image will be 'cat'
匿名グループと名前付きグループ
URL 文字列を exec() に渡すと、パターンのすべてのグループに一致した部分を示す値が返されます。
戻り値には、URLPattern のコンポーネントに対応するプロパティ(pathname など)があります。したがって、グループが URLPattern の pathname 部分の一部として定義されている場合、一致は戻り値の pathname.groups で見つかります。一致は、対応するパターンが匿名グループか名前付きグループかによって異なる方法で表されます。
配列インデックスを使用して、匿名パターン マッチングの値にアクセスできます。匿名パターンが複数ある場合、インデックス 0 は最も左側のパターンに一致する値を表し、1 とそれ以降のインデックスは後続のパターンに使用されます。
パターンで名前付きグループを使用すると、各グループ名に対応する名前のプロパティとして一致が公開されます。
Unicode のサポートと正規化
URLPattern は、いくつかの異なる方法で Unicode 文字をサポートしています。
:caféなどの名前付きグループには、Unicode 文字を含めることができます。有効な JavaScript 識別子に使用されるルールは、名前付きグループに適用されます。パターン内のテキストは、その特定のコンポーネントの URL エンコードに使用されるのと同じルールに従って自動的にエンコードされます。
pathname内の Unicode 文字はパーセント エンコードされるため、/caféなどのpathnameパターンは自動的に/caf%C3%A9に正規化されます。hostnameの Unicode 文字は、パーセント エンコーディングではなく、Punycode を使用して自動的にエンコードされます。正規表現グループには ASCII 文字のみを含める必要があります。正規表現の構文では、これらのグループの Unicode 文字を自動的にエンコードすることが困難で安全ではありません。正規表現グループで Unicode 文字を照合する場合は、
caféを照合するために(caf%C3%A9)のように手動でパーセント エンコードする必要があります。
URLPattern は、Unicode 文字のエンコードに加えて、URL の正規化も行います。たとえば、pathname コンポーネントの /foo/./bar は、同等の /foo/bar に折りたたまれます。
特定の入力パターンがどのように正規化されたか不明な場合は、ブラウザの DevTools を使用して、構築された URLPattern インスタンスを検査します。
すべてをまとめる
Glitch デモは、サービス ワーカーの fetch event handler 内の URLPattern のコア ユースケースを示しています。これは、特定のパターンをネットワーク リクエストへのレスポンスを生成する非同期関数にマッピングします。この例のコンセプトは、サーバーサイドまたはクライアントサイドの他のルーティング シナリオにも適用できます。
フィードバックと今後の計画
URLPattern の基本機能は Chrome と Edge に実装されていますが、追加機能も予定されています。URLPattern の一部はまだ開発中であり、特定の動作については未解決の質問がいくつかあり、今後も改善される可能性があります。URLPattern をお試しいただき、GitHub の問題を通じてフィードバックをお寄せください。
テンプレートのサポート
path-to-regexp ライブラリは、ルーティング動作を効果的に逆転させる compile() function を提供します。compile() は、パターンとトークン プレースホルダの値を受け取り、それらの値が代入された URL パスの文字列を返します。
今後 URLPattern に追加する予定ですが、初期リリースでは対象外です。
今後のウェブ プラットフォーム機能を有効にする
URLPattern がウェブ プラットフォームの確立された一部になると仮定すると、ルーティングやパターン マッチングのメリットを享受できる他の機能は、プリミティブとして URLPattern を基盤に構築できます。
サービス ワーカーのスコープ パターン マッチング、ファイル ハンドラとしての PWA、投機的プリフェッチなどの提案された機能に URLPattern を使用することについては、現在も議論が続いています。
謝辞の全リストについては、元の説明ドキュメントをご覧ください。