URLPattern ajoute le routage à la plate-forme Web

Une approche visant à normaliser les cas d'utilisation courants de la mise en correspondance de modèles.

Contexte

Le routage est un élément clé de toute application Web. En substance, le routage consiste à prendre une URL, à lui appliquer une correspondance de modèle ou une autre logique spécifique à l'application, puis à afficher généralement du contenu Web en fonction du résultat. Le routage peut être implémenté de plusieurs manières: il s'agit parfois de code exécuté sur un serveur qui mappe un chemin d'accès aux fichiers sur disque, ou de logique dans une application monopage qui attend des modifications de l'emplacement actuel et crée un élément DOM correspondant à afficher.

Bien qu'il n'existe pas de norme définitive, les développeurs Web ont tendance à utiliser une syntaxe commune pour exprimer des modèles de routage d'URL qui ont beaucoup en commun avec regular expressions, mais avec quelques ajouts spécifiques au domaine, comme des jetons pour faire correspondre des segments de chemin. Les frameworks côté serveur populaires tels que Express et Ruby on Rails utilisent cette syntaxe (ou quelque chose de très proche). Les développeurs JavaScript peuvent utiliser des modules tels que path-to-regexp ou regexpparam pour ajouter cette logique à leur propre code.

URLPattern est un ajout à la plate-forme Web qui s'appuie sur la base créée par ces frameworks. Son objectif est de normaliser une syntaxe de modèle de routage, y compris la prise en charge des caractères génériques, des groupes de jetons nommés, des groupes d'expressions régulières et des modificateurs de groupe. Les instances URLPattern créées avec cette syntaxe peuvent effectuer des tâches de routage courantes, comme la mise en correspondance avec des URL complètes ou une URL pathname, et renvoyer des informations sur les correspondances de jeton et de groupe.

Un autre avantage de la mise en correspondance des URL directement dans la plate-forme Web est qu'une syntaxe commune peut ensuite être partagée avec d'autres API qui doivent également faire correspondre des URL.

Compatibilité avec les navigateurs et polyfills

URLPattern est activé par défaut dans Chrome et Edge versions 95 et ultérieures.

La bibliothèque urlpattern-polyfill permet d'utiliser l'interface URLPattern dans les navigateurs ou les environnements tels que Node, qui ne sont pas compatibles avec cette interface. Si vous utilisez le polyfill, assurez-vous d'utiliser la détection de fonctionnalités pour vous assurer de ne le charger que si l'environnement actuel n'est pas compatible. Sinon, vous perdrez l'un des principaux avantages de URLPattern: le fait que les environnements d'assistance n'aient pas besoin de télécharger et d'analyser du code supplémentaire pour l'utiliser.

if (!(globalThis && 'URLPattern' in globalThis)) {
  // URLPattern is not available, so the polyfill is needed.
}

Compatibilité syntaxique

La philosophie d'URLPattern est d'éviter de réinventer. Si vous connaissez déjà la syntaxe de routage utilisée dans Express ou Ruby on Rails, vous n'aurez pas besoin d'apprendre quoi que ce soit. Toutefois, étant donné les légères divergences entre les syntaxes des bibliothèques de routage populaires, il a fallu choisir une syntaxe de base. Les concepteurs de URLPattern ont décidé d'utiliser la syntaxe de modèle de path-to-regexp (mais pas sa surface d'API) comme point de départ.

Cette décision a été prise après une consultation approfondie avec le responsable actuel de path-to-regexp.

Le meilleur moyen de vous familiariser avec le cœur de la syntaxe acceptée est de consulter la documentation sur path-to-regexp. Vous pouvez lire la documentation destinée à être publiée sur MDN dans son emplacement actuel sur GitHub.

Autres fonctionnalités

La syntaxe de URLPattern est un sur-ensemble de ce que path-to-regexp accepte, car URLPattern prend en charge une fonctionnalité inhabituelle parmi les bibliothèques de routage: la mise en correspondance des origines, y compris des caractères génériques dans les noms d'hôte. La plupart des autres bibliothèques de routage ne traitent que le pathname, et parfois la partie recherche ou hachage d'une URL. Ils n'ont jamais besoin de vérifier la partie d'origine d'une URL, car ils ne sont utilisés que pour le routage de même origine dans une application Web autonome.

Prendre en compte les origines ouvre la voie à d'autres cas d'utilisation, comme l'acheminement des requêtes inter-origines dans le gestionnaire d'événements fetch d'un service worker. Si vous ne routagez que des URL de même origine, vous pouvez ignorer cette fonctionnalité supplémentaire et utiliser URLPattern comme les autres bibliothèques.

Exemples

Créer le modèle

Pour créer un URLPattern, transmettez à son constructeur des chaînes ou un objet dont les propriétés contiennent des informations sur le format à faire correspondre.

Transmettre un objet offre le contrôle le plus explicite sur le format à utiliser pour faire correspondre chaque composant d'URL. Dans sa forme la plus détaillée, cela peut ressembler à

const p = new URLPattern({
  protocol: 'https',
  username: '',
  password: '',
  hostname: 'example.com',
  port: '',
  pathname: '/foo/:image.jpg',
  search: '*',
  hash: '*',
});

Si vous fournissez une chaîne vide pour une propriété, elle ne correspondra que si la partie correspondante de l'URL n'est pas définie. Le caractère générique * correspond à n'importe quelle valeur pour une partie donnée de l'URL.

Le constructeur propose plusieurs raccourcis pour une utilisation plus simple. Omettre complètement search et hash, ou toute autre propriété, revient à les définir sur l'espace réservé '*'. L'exemple ci-dessus peut être simplifié comme suit :

const p = new URLPattern({
  protocol: 'https',
  username: '',
  password: '',
  hostname: 'example.com',
  port: '',
  pathname: '/foo/:image.jpg',
});

En guise de raccourci supplémentaire, toutes les informations sur l'origine peuvent être fournies dans une seule propriété, baseURL, ce qui donne

const p = new URLPattern({
  pathname: '/foo/:image.jpg',
  baseURL: 'https://example.com',
});

Tous ces exemples supposent que votre cas d'utilisation implique des origines correspondantes. Si vous ne souhaitez faire correspondre que les autres parties de l'URL, en excluant l'origine (comme c'est le cas pour de nombreux scénarios de routage à origine unique "traditionnels"), vous pouvez omettre complètement les informations d'origine et simplement fournir une combinaison des propriétés pathname, search et hash. Comme précédemment, les propriétés omises seront traitées comme si elles étaient définies sur le modèle de caractère générique *.

const p = new URLPattern({pathname: '/foo/:image.jpg'});

Au lieu de transmettre un objet au constructeur, vous pouvez fournir une ou deux chaînes. Si une seule chaîne est fournie, elle doit représenter un format d'URL complet, y compris les informations de format utilisées pour faire correspondre l'origine. Si vous fournissez deux chaînes, la deuxième chaîne est utilisée comme baseURL, et la première chaîne est considérée comme relative à cette base.

Que vous fournissiez une ou deux chaînes, le constructeur URLPattern analysera le format d'URL complet, le divisant en composants d'URL et mappant chaque partie du format plus grand sur le composant correspondant. Cela signifie que sous le capot, chaque URLPattern créé avec des chaînes finit par être représenté de la même manière qu'un URLPattern équivalent créé avec un objet. Le constructeur de chaînes n'est qu'un raccourci pour ceux qui préfèrent une interface moins verbeuse.

const p = new URLPattern('https://example.com/foo/:image.jpg?*#*');

Lorsque vous utilisez des chaînes pour créer un URLPattern, gardez à l'esprit quelques points.

Omettre une propriété lorsque vous utilisez un objet pour créer URLPattern équivaut à fournir un caractère générique * pour cette propriété. Lorsque le modèle de chaîne d'URL complète est analysé, si une valeur est manquante dans l'un des composants de l'URL, il est traité comme si la propriété du composant était définie sur '', ce qui ne correspondra qu'à ce composant lorsqu'il sera vide.

Lorsque vous utilisez des chaînes, vous devez inclure explicitement les caractères génériques si vous souhaitez qu'ils soient utilisés dans le URLPattern créé.

// 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',
});

Sachez également que l'analyse d'un modèle de chaîne en ses composants est potentiellement ambiguë. Certains caractères, comme :, se trouvent dans les URL, mais ont également une signification particulière dans la syntaxe de mise en correspondance de format. Pour éviter cette ambiguïté, le constructeur URLPattern suppose que l'un de ces caractères spéciaux fait partie d'un format, et non de l'URL. Si vous souhaitez qu'un caractère ambigu soit interprété comme faisant partie de l'URL, veillez à l'échapper avec un \` character. For example, the literal URLabout:blankshould be escaped as'about\:blank'` lorsqu'il est fourni en tant que chaîne.

Utiliser le modèle

Une fois que vous avez construit un URLPattern, vous avez deux options pour l'utiliser. Les méthodes test() et exec() utilisent toutes les deux la même entrée et le même algorithme pour vérifier une correspondance. Elles ne diffèrent que par leur valeur de retour. test() renvoie true en cas de correspondance avec l'entrée donnée, et false dans le cas contraire. exec() renvoie des informations détaillées sur la correspondance, ainsi que des groupes de capture, ou null en cas de non-correspondance. Les exemples suivants montrent comment utiliser exec(), mais vous pouvez remplacer test() par l'un d'eux si vous ne souhaitez qu'une valeur de retour booléenne simple.

Pour utiliser les méthodes test() et exec(), vous pouvez transmettre des chaînes. Comme le constructeur, si une seule chaîne est fournie, il doit s'agir d'une URL complète, y compris l'origine. Si deux chaînes sont fournies, la deuxième chaîne est traitée comme une valeur baseURL, et la première chaîne est évaluée par rapport à cette base.

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.

Vous pouvez également transmettre le même type d'objet que le constructeur, avec des propriétés définies uniquement sur les parties de l'URL qui vous intéressent.

const p = new URLPattern({pathname: '/foo/:image.jpg'});

const result = p.exec({pathname: '/foo/:image.jpg'});
// result will contain info about the successful match.

Lorsque vous utilisez exec() sur un URLPattern contenant des caractères génériques ou des jetons, la valeur renvoyée vous indique quelles étaient les valeurs correspondantes dans l'URL d'entrée. Cela peut vous éviter d'avoir à analyser vous-même ces valeurs.

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'

Groupes anonymes et nommés

Lorsque vous transmettez une chaîne d'URL à exec(), vous obtenez une valeur indiquant les parties qui correspondent à tous les groupes du modèle.

La valeur renvoyée possède des propriétés qui correspondent aux composants de URLPattern, comme pathname. Par conséquent, si un groupe a été défini dans la partie pathname de URLPattern, les correspondances peuvent être trouvées dans la pathname.groups de la valeur renvoyée. Les correspondances sont représentées différemment selon que le modèle correspondant était un groupe anonyme ou nommé.

Vous pouvez utiliser des indices de tableau pour accéder aux valeurs d'une correspondance de modèle anonyme. S'il existe plusieurs modèles anonymes, l'index 0 représente la valeur correspondante pour celui le plus à gauche, tandis que 1 et d'autres indices sont utilisés pour les modèles suivants.

Lorsque vous utilisez des groupes nommés dans un modèle, les correspondances sont exposées en tant que propriétés dont les noms correspondent à chaque nom de groupe.

Compatibilité avec Unicode et normalisation

URLPattern est compatible avec les caractères Unicode de différentes manières.

  • Les groupes nommés, comme :café, peuvent contenir des caractères Unicode. Les règles utilisées pour les identifiants JavaScript valides s'appliquent aux groupes nommés.

  • Le texte d'un modèle est automatiquement encodé selon les mêmes règles que celles utilisées pour l'encodage d'URL de ce composant particulier. Les caractères Unicode dans pathname seront encodés en pourcentage. Par conséquent, un modèle pathname tel que /café est automatiquement normalisé en /caf%C3%A9. Les caractères Unicode de hostname sont automatiquement encodés à l'aide de Punycode, plutôt qu'encodés en pourcentage.

  • Les groupes d'expressions régulières ne doivent contenir que des caractères ASCII. La syntaxe des expressions régulières rend l'encodage automatique des caractères Unicode dans ces groupes difficile et dangereux. Si vous souhaitez faire correspondre un caractère Unicode dans un groupe d'expression régulière, vous devez l'encoder en pourcentage manuellement, par exemple (caf%C3%A9) pour correspondre à café.

En plus d'encoder les caractères Unicode, URLPattern effectue également la normalisation des URL. Par exemple, /foo/./bar dans le composant pathname est réduit à l'/foo/bar équivalent.

En cas de doute sur la façon dont un modèle d'entrée donné a été normalisé, inspectez l'instance URLPattern créée à l'aide des DevTools de votre navigateur.

Synthèse

La démonstration Glitch intégrée ci-dessous illustre un cas d'utilisation principal de URLPattern dans le fetch event handler d'un service worker, en mappant des modèles spécifiques à des fonctions asynchrones pouvant générer une réponse aux requêtes réseau. Les concepts de cet exemple peuvent également s'appliquer à d'autres scénarios d'acheminement, côté serveur ou côté client.

Commentaires et futurs projets

Bien que les fonctionnalités de base de URLPattern soient disponibles dans Chrome et Edge, des ajouts sont prévus. Certains aspects de URLPattern sont toujours en cours de développement, et un certain nombre de questions ouvertes sur des comportements spécifiques peuvent encore être affinées. Nous vous invitons à essayer URLPattern et à nous faire part de vos commentaires via un problème GitHub.

Prise en charge des modèles

La bibliothèque path-to-regexp fournit un compile() function qui inverse efficacement le comportement de routage. compile() prend un modèle et des valeurs pour les espaces réservés de jeton, puis renvoie une chaîne pour un chemin d'URL avec ces valeurs remplacées.

Nous espérons l'ajouter à URLPattern à l'avenir, mais cela n'entre pas dans le champ d'application de la version initiale.

Activer les futures fonctionnalités de la plate-forme Web

En supposant que URLPattern devienne une partie établie de la plate-forme Web, d'autres fonctionnalités pouvant bénéficier du routage ou de la mise en correspondance de modèles peuvent s'appuyer dessus en tant que primitive.

Des discussions sont en cours sur l'utilisation de URLPattern pour des fonctionnalités proposées telles que la mise en correspondance de modèles de portée de service worker, les PWA en tant que gestionnaires de fichiers et le préchargement spéculatif.

Remerciements

Pour obtenir la liste complète des remerciements, consultez le document explicatif d'origine.