Uma abordagem para padronizar casos de uso comuns de correspondência de padrões.
Contexto
O roteamento é uma parte fundamental de todo aplicativo da Web. Basicamente, o roteamento envolve pegar um URL, aplicar uma correspondência de padrões ou outra lógica específica do app a ele e, geralmente, mostrar conteúdo da Web com base no resultado. O roteamento pode ser implementado de várias maneiras: às vezes, é um código executado em um servidor que mapeia um caminho para arquivos no disco ou uma lógica em um app de página única que aguarda mudanças no local atual e cria um elemento DOM correspondente para exibição.
Embora não haja um padrão definitivo, os desenvolvedores da Web têm se inclinado
para uma sintaxe comum para expressar padrões de roteamento de URL que têm muito em
comum com
regular expressions
,
mas com algumas adições específicas do domínio, como tokens para corresponder a segmentos de caminho.
Frameworks populares do lado do servidor, como
Express e
Ruby on Rails, usam essa sintaxe (ou
algo muito parecido com ela), e os desenvolvedores de JavaScript podem usar módulos como
path-to-regexp
ou
regexpparam
para adicionar essa
lógica ao próprio código.
URLPattern
é uma adição à plataforma da Web que se baseia na base criada
por esses frameworks. O objetivo é padronizar uma sintaxe de padrão de roteamento,
incluindo suporte a caracteres curinga, grupos de tokens nomeados, grupos de expressões regulares
e modificadores de grupo. As instâncias de URLPattern
criadas com essa sintaxe
podem realizar tarefas de roteamento comuns, como a correspondência de URLs completos ou um URL
pathname
,
e retornar informações sobre as correspondências de token e grupo.
Outro benefício de fornecer a correspondência de URL diretamente na plataforma da Web é que uma sintaxe comum pode ser compartilhada com outras APIs que também precisam corresponder a URLs.
Suporte a navegadores e polyfills
O URLPattern
é ativado por padrão no Chrome e no Edge a partir da versão 95.
A biblioteca
urlpattern-polyfill
oferece uma maneira de usar a interface URLPattern
em navegadores
ou ambientes como o Node, que não têm suporte integrado. Se
você usar o polyfill, use a detecção de recursos para garantir que
ele só seja carregado se o ambiente atual não tiver suporte. Caso contrário,
você perderá um dos principais benefícios do URLPattern
: o fato de que
os ambientes de suporte não precisam fazer o download e analisar códigos adicionais para
usá-lo.
if (!(globalThis && 'URLPattern' in globalThis)) {
// URLPattern is not available, so the polyfill is needed.
}
Compatibilidade de sintaxe
Uma filosofia orientadora para URLPattern
é evitar a reinvenção. Se você já
conhece a sintaxe de roteamento usada no Express ou no Ruby on Rails, não
precisa aprender nada novo. No entanto, devido às pequenas divergências
entre as sintaxes em bibliotecas de roteamento conhecidas, algo precisava ser escolhido como a
sintaxe de base, e os designers de URLPattern
decidiram usar a sintaxe de padrão
de path-to-regexp
(mas não a superfície da API) como ponto de partida.
Tomamos essa decisão após uma consulta detalhada com o mantenedor atual de
path-to-regexp
.
A melhor maneira de se familiarizar com o núcleo da sintaxe com suporte é
consultar a
documentação do
path-to-regexp
. É possível
ler a documentação
destinada à publicação na MDN (link em inglês) no
GitHub.
Outros recursos
A sintaxe de URLPattern
é um superconjunto do que path-to-regexp
oferece suporte, já que
URLPattern
oferece suporte a um recurso incomum entre as bibliotecas de roteamento: correspondência de
origens,
incluindo caracteres curinga em nomes de host. A maioria das outras bibliotecas de roteamento lida apenas com o
pathname
e, ocasionalmente, com a parte de
pesquisa ou
hash de um
URL. Eles nunca precisam verificar a parte de origem de um URL, porque são usados apenas
para roteamento de mesma origem em um app da Web independente.
Considerar as origens abre portas para outros casos de uso, como
encaminhar solicitações entre origens dentro de um
manipulador de eventos fetch
de um
service worker. Se você estiver roteando apenas URLs de mesma origem, poderá
ignorar esse recurso extra e usar URLPattern
como
outras bibliotecas.
Exemplos
Como construir o padrão
Para criar um URLPattern
, transmita o construtor dele, strings ou um objeto com
propriedades que contenham informações sobre o padrão a ser comparado.
A transmissão de um objeto oferece o controle mais explícito sobre qual padrão usar para corresponder a cada componente do URL. O resultado mais detalhado pode ser parecido com este:
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
search: '*',
hash: '*',
});
Fornecer uma string vazia para uma propriedade só vai corresponder se a parte correspondente
do URL não estiver definida. O caractere curinga *
vai corresponder a qualquer valor para uma determinada
parte do URL.
O construtor oferece vários atalhos para facilitar o uso. Omitir completamente
search
e hash
, ou qualquer outra propriedade, é equivalente a defini-los como o
coringa '*'
. O exemplo acima pode ser simplificado para
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
});
Como atalho adicional, todas as informações sobre a origem podem ser
fornecidas em uma única propriedade, baseURL
, levando a
const p = new URLPattern({
pathname: '/foo/:image.jpg',
baseURL: 'https://example.com',
});
Todos esses exemplos presumem que seu caso de uso envolve origens correspondentes. Se
você só quiser fazer a correspondência em outras partes do URL, excluindo
a origem (como é o caso de muitos cenários de roteamento "tradicionais" de origem
única), omita as informações de origem e forneça
alguma combinação das propriedades pathname
, search
e hash
. Como antes,
as propriedades omitidas serão tratadas como se tivessem sido definidas para o padrão de curinga
*
.
const p = new URLPattern({pathname: '/foo/:image.jpg'});
Como alternativa para transmitir um objeto ao construtor, você pode fornecer
uma ou duas strings. Se uma string for fornecida, ela vai representar um padrão
de URL completo, incluindo informações de padrão usadas para corresponder à origem. Se você
informar duas strings, a segunda será usada como baseURL
, e a primeira
será considerada relativa a essa base.
Se uma ou duas strings forem fornecidas, o construtor URLPattern
vai analisar
o padrão de URL completo, dividindo-o em componentes de URL, e mapear cada parte
do padrão maior para o componente correspondente. Isso significa que,
por trás das cortinas, cada URLPattern
criado com strings acaba sendo representado
da mesma forma que um URLPattern
equivalente criado com um objeto. O
construtor de strings é apenas um atalho para quem prefere uma interface menos
detalhada.
const p = new URLPattern('https://example.com/foo/:image.jpg?*#*');
Ao usar strings para criar uma URLPattern
, é preciso considerar algumas ressalvas.
Deixar uma propriedade de fora ao usar um objeto para construir URLPattern
é
equivalente a fornecer um caractere curinga *
para essa propriedade. Quando o padrão de string de URL
completo é analisado, se um dos componentes do URL não tiver um valor, ele será
tratado como se a propriedade do componente fosse definida como ''
, que só vai corresponder
quando esse componente estiver vazio.
Ao usar strings, é necessário incluir explicitamente os caracteres curinga se você quiser
que eles sejam usados na URLPattern
construída.
// 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',
});
Também é importante saber que analisar um padrão de string nos componentes dele é
potencialmente ambíguo. Há caracteres, como :
, que são encontrados em URLs,
mas também têm um significado especial na sintaxe de correspondência de padrões. Para evitar essa
ambiguidade, o construtor URLPattern
pressupõe que qualquer um desses caracteres
especiais faz parte de um padrão, não do URL. Se você quiser que um caractere
ambíguo seja interpretado como parte do URL, use o escape com um
\` character. For example, the literal URL
about:blankshould be escaped as
'about\:blank'` quando fornecido como uma string.
Como usar o padrão
Depois de criar um URLPattern
, você tem duas opções para usá-lo. Os métodos
test()
e exec()
usam a mesma entrada e o mesmo
algoritmo para verificar uma correspondência e só diferem no valor de retorno. test()
retorna true
quando há uma correspondência para a entrada especificada e false
caso contrário.
exec()
retorna informações detalhadas sobre a correspondência com grupos de captura
ou null
se não houver correspondência. Os exemplos a seguir demonstram o uso de
exec()
, mas você pode trocar test()
por qualquer um deles se quiser apenas um
valor de retorno booleano simples.
Uma maneira de usar os métodos test()
e exec()
é transmitindo strings.
Assim como o construtor, se uma única string for fornecida, ela
precisa ser um URL completo, incluindo a origem. Se duas strings forem fornecidas, a
segunda será tratada como um valor baseURL
, e a primeira será avaliada
em relação a essa 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.
Como alternativa, é possível transmitir o mesmo tipo de objeto aceito pelo construtor, com propriedades definidas apenas para as partes do URL que você quer que sejam correspondentes.
const p = new URLPattern({pathname: '/foo/:image.jpg'});
const result = p.exec({pathname: '/foo/:image.jpg'});
// result will contain info about the successful match.
Ao usar exec()
em um URLPattern
que contém caracteres curinga ou tokens, o valor de retorno vai fornecer informações sobre quais eram os valores correspondentes no URL de entrada. Isso pode evitar que você precise analisar esses
valores.
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'
Grupos anônimos e nomeados
Quando você transmite uma string de URL para exec()
, recebe um valor informando quais partes correspondem a todos os grupos do padrão.
O valor de retorno tem propriedades que correspondem aos componentes do
URLPattern
, como pathname
. Portanto, se um grupo foi definido como parte da
parte pathname
do URLPattern
, as correspondências podem ser encontradas no
pathname.groups
do valor de retorno. As correspondências são representadas de maneira diferente,
dependendo se o padrão correspondente era um grupo anônimo ou nomeado.
É possível usar índices de matriz para acessar os valores de uma correspondência de padrão anônimo.
Se houver vários padrões anônimos, o índice 0
vai representar o valor correspondente
para o mais à esquerda, com 1
e outros índices usados para padrões
subsequentes.
Ao usar grupos nomeados em um padrão, as correspondências são expostas como propriedades cujos nomes correspondem a cada nome de grupo.
Suporte e normalização do Unicode
O URLPattern
oferece suporte a caracteres Unicode de várias maneiras.
Os grupos nomeados, como
:café
, podem conter caracteres Unicode. As regras usadas para identificadores válidos de JavaScript se aplicam a grupos nomeados.O texto em um padrão será codificado automaticamente de acordo com as mesmas regras usadas para codificação de URL desse componente específico. Os caracteres Unicode em
pathname
serão codificados com porcentagens, então um padrãopathname
como/café
é normalizado para/caf%C3%A9
automaticamente. Os caracteres Unicode nohostname
são codificados automaticamente usando Punycode, em vez de codificação por porcentagem.Os grupos de expressões regulares precisam conter apenas caracteres ASCII. A sintaxe de expressão regular dificulta e torna inseguro a codificação automática de caracteres Unicode nesses grupos. Se você quiser corresponder a um caractere Unicode em um grupo de expressão regular, será necessário codificar manualmente, como
(caf%C3%A9)
para corresponder acafé
.
Além de codificar caracteres Unicode, URLPattern
também realiza a normalização
de URLs. Por exemplo, /foo/./bar
no componente pathname
é
reduzido ao /foo/bar
equivalente.
Em caso de dúvida sobre como um determinado padrão de entrada foi normalizado, inspecione a
instância URLPattern
construída usando as
DevTools do navegador.
Como tudo funciona em conjunto
A demonstração do Glitch incorporada abaixo ilustra um caso de uso principal de URLPattern
dentro de um serviço worker
fetch event handler
,
mapeando padrões específicos para funções assíncronas que podem gerar uma
resposta às solicitações de rede. Os conceitos deste exemplo também podem ser aplicados a
outros cenários de roteamento, seja do lado do servidor ou do cliente.
Feedback e planos futuros
Embora a funcionalidade básica de URLPattern
tenha chegado ao Chrome e ao Edge,
há planos para adicionar mais recursos. Alguns aspectos do URLPattern
ainda estão em desenvolvimento,
e há várias
perguntas abertas
sobre comportamentos específicos que ainda podem ser refinados. Recomendamos que você teste o
URLPattern
e envie seu feedback por meio de um
problema do GitHub.
Suporte a modelos
A biblioteca path-to-regexp
fornece um
compile() function
que reverte o comportamento de roteamento. compile()
recebe um
padrão e valores para os marcadores de token e retorna uma string para um caminho
de URL com esses valores substituídos.
Esperamos adicionar isso ao URLPattern no futuro, mas ele não está no escopo da versão inicial.
Como ativar recursos futuros da plataforma da Web
Supondo que URLPattern
se torne uma parte estabelecida da plataforma da Web, outros
recursos que podem se beneficiar do roteamento ou da correspondência de padrões podem ser criados
como primitivos.
Há discussões em andamento sobre o uso de URLPattern
para recursos propostos,
como
correspondência de padrões de escopo de service worker,
PWAs como manipuladores de arquivos
e
pré-carregamento especulativo.
Agradecimentos
Consulte o documento explicativo original para conferir uma lista completa de reconhecimentos.