一种对常见模式匹配用例进行标准化的方法。
背景
路由是每个 Web 应用的关键组成部分。从本质上讲,路由涉及获取一个网址,对该网址应用一些模式匹配或其他应用特定的逻辑,然后通常根据结果显示 Web 内容。路由可以通过多种方式实现:有时在服务器上运行的代码会将路径映射到磁盘上的文件,或者单页应用中的逻辑会等待对当前位置的更改并创建相应的 DOM 部分来显示。
虽然没有统一的标准,但 Web 开发者倾向于使用一种通用语法来表示网址路由模式,这种模式与 regular expressions
具有很多共同点,但添加了一些针对特定网域的额外功能,例如用于匹配路径段的令牌。常见的服务器端框架(如 Express 和 Ruby on Rails)使用了此语法(或非常类似的语法),而 JavaScript 开发者可以使用 path-to-regexp
或 regexpparam
等模块将该逻辑添加到自己的代码中。
URLPattern
是对 Web 平台的补充,基于这些框架创建的基础。其目标是对路由模式语法进行标准化,包括对通配符、命名令牌组、正则表达式组和组修饰符的支持。使用此语法创建的 URLPattern
实例可以执行常见的路由任务,例如匹配完整网址或网址 pathname
,以及返回有关令牌和组匹配项的信息。
直接在 Web 平台中提供网址匹配的另一个好处是,可以将通用语法与同样需要与网址进行匹配的其他 API 共享。
浏览器支持和 polyfill
在 Chrome 和 Edge 95 及更高版本中,URLPattern
默认处于启用状态。
urlpattern-polyfill
库提供了一种在缺乏内置支持的浏览器或环境(例如 Node)中使用 URLPattern
接口的方法。如果您使用 polyfill,请务必使用特征检测,以确保仅在当前环境缺乏支持时才加载它。否则,您将失去 URLPattern
的一个主要优势:支持环境无需下载和解析其他代码即可使用它。
if (!(globalThis && 'URLPattern' in globalThis)) {
// URLPattern is not available, so the polyfill is needed.
}
语法兼容性
URLPattern
的指导理念是避免重复创造。如果您已熟悉 Express 或 Ruby on Rails 中使用的路由语法,则无需学习任何新知识。但鉴于常用路由库中的语法之间存在细微差异,必须选择某些内容作为基本语法,URLPattern
的设计者决定从 path-to-regexp
(尽管不是其 API Surface)中的模式语法着手。
此决定是在与 path-to-regexp
的当前维护者密切协商后做出的。
熟悉受支持语法的核心的最佳方法是参阅 path-to-regexp
的文档。您可以阅读本文档,以在 GitHub 上的当前主页中的 MDN 上发布。
其他功能
URLPattern
的语法是 path-to-regexp
所支持语法的超集,因为 URLPattern
支持路由库中的一项不常见的功能:匹配源站,包括主机名中的通配符。大多数其他路由库仅处理路径名,偶尔还会处理网址的搜索或哈希部分。它们不必检查网址的来源部分,因为它们仅用于独立 Web 应用中的同源路由。
将源站纳入考虑范围为更多用例打开了大门,例如在 Service Worker 的 fetch
事件处理脚本内路由跨源请求。如果您只路由同源网址,则可以有效地忽略此附加功能,并像使用其他库一样使用 URLPattern
。
示例
构建模式
如需创建 URLPattern
,请向其构造函数传递字符串或一个对象,该对象的属性中包含要匹配的模式的相关信息。
通过传递对象,您可以最明确地控制用于匹配每个网址组成部分的格式。最详细的内容可能如下所示:
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
search: '*',
hash: '*',
});
仅当未设置网址的相应部分时,为属性提供空字符串才会匹配。通配符 *
将与网址给定部分的任何值匹配。
构造函数提供了多个快捷方式,以简化使用。完全省略 search
和 hash
或任何其他属性相当于将它们设置为 '*'
通配符。上面的示例可以简化为
const p = new URLPattern({
protocol: 'https',
username: '',
password: '',
hostname: 'example.com',
port: '',
pathname: '/foo/:image.jpg',
});
另一种快捷方式是,有关源站的所有信息都可以在单个属性 baseURL
中提供,从而
const p = new URLPattern({
pathname: '/foo/:image.jpg',
baseURL: 'https://example.com',
});
所有示例均假定您的用例涉及匹配源。如果您只想匹配网址的其他部分,不包括源站(就像许多“传统”单源路由场景一样),则可以完全省略源站信息,只需提供 pathname
、search
和 hash
属性的某种组合即可。与之前一样,系统会认为省略的属性已设为 *
通配符模式。
const p = new URLPattern({pathname: '/foo/:image.jpg'});
您可以提供一个或两个字符串,作为将对象传递给构造函数的替代方法。如果提供一个字符串,则该字符串应表示完整的网址格式,包括用于匹配来源的格式信息。如果您提供两个字符串,则第二个字符串将用作 baseURL
,并且系统会认为第一个字符串相对于该基底字符串。
无论提供一个还是两个字符串,URLPattern
构造函数都会解析完整的网址格式,将其分解为网址组成部分,并将较大格式的每个部分映射到相应的组成部分。这意味着,从本质上讲,使用字符串创建的每个 URLPattern
最终都表示为使用对象创建的等效 URLPattern
。对于更喜欢使用简单明了接口的用户来说,strings 构造函数只是一种快捷方式。
const p = new URLPattern('https://example.com/foo/:image.jpg?*#*');
使用字符串创建 URLPattern
时,需要注意以下几点。
在使用对象构建 URLPattern
时将属性留空等同于为该属性提供 *
通配符。解析完整的网址字符串格式时,如果其中一个网址组成部分缺少值,系统会将其视为该组成部分的属性已设置为 ''
,这样只有在该组成部分为空时才会匹配。
使用字符串时,如果您希望在构造的 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',
});
您还应注意,将字符串模式解析为其组成部分可能会不明确。虽然有些字符(例如 :
)可在网址中找到,但它们在模式匹配语法中也有特殊含义。为避免这种歧义,URLPattern
构造函数会假定所有这些特殊字符都是模式的一部分,而不是网址的一部分。如果您希望将某个模糊字符解读为网址的一部分,请务必使用 \` character. For example, the literal URL
about:blankshould be escaped as
'about\:blank'`(以字符串形式提供)对其进行转义。
使用模式
构建 URLPattern
后,您可以通过两种方式使用它。test()
和 exec()
方法采用相同的输入并使用相同的算法检查匹配项,只是返回值不同。如果找到与指定输入匹配的结果,test()
返回 true
,否则返回 false
。exec()
会返回有关匹配项的详细信息以及捕获组,如果没有匹配项,则返回 null
。以下示例演示了如何使用 exec()
,但如果您只需要简单的布尔值返回值,则可以将 test()
换成其中的任何一种。
使用 test()
和 exec()
方法的一种方法是传入字符串。与构造函数支持的内容类似,如果仅提供一个字符串,则应是一个包含源站的完整网址。如果提供了两个字符串,则第二个字符串将被视为 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.
或者,您也可以传递构造函数支持的同一类型的对象,并将属性设置为仅您关注的网址中的部分匹配。
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()
时,返回值将提供输入网址中相应值的相关信息。这样您就不必自行解析这些值。
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'
匿名群组和已命名群组
当您将网址字符串传递给 exec()
时,会返回一个值,告诉您哪些部分与模式的所有组匹配。
返回值具有与 URLPattern
的组成部分相对应的属性,如 pathname
。因此,如果某个组是作为 URLPattern
的 pathname
部分的一部分定义的,那么可以在返回值的 pathname.groups
中找到匹配项。根据对应的模式是匿名群组还是已命名群组,匹配项的表示方式会有所不同。
您可以使用数组索引来访问匿名模式匹配的值。如果有多种匿名模式,索引 0
将表示最左侧模式的匹配值,其中 1
为后续模式使用更多索引。
在模式中使用命名群组时,匹配项将作为属性公开,其名称对应于每个群组名称。
Unicode 支持和标准化
URLPattern
通过几种不同的方式支持 Unicode 字符。
已命名群组(如
:café
)可以包含 Unicode 字符。用于有效 JavaScript 标识符的规则适用于命名组。格式中的文本将根据该特定组件的网址编码所用的相同规则自动编码。
pathname
中的 Unicode 字符将进行百分比编码,因此/café
等pathname
模式自动标准化为/caf%C3%A9
。hostname
中的 Unicode 字符使用 Punycode(而不是百分号编码)自动编码。正则表达式组只能包含 ASCII 字符。正则表达式语法使得自动对这些组中的 Unicode 字符进行编码变得困难且不安全。如果要匹配正则表达式组中的某个 Unicode 字符,您需要手动对该字符进行百分号编码,例如使用
(caf%C3%A9)
来匹配café
。
除了对 Unicode 字符进行编码之外,URLPattern
还会执行网址标准化。例如,pathname
组件中的 /foo/./bar
会收起为等效的 /foo/bar
。
如果不确定给定输入模式的标准化方式,请使用浏览器的 DevTools 检查构造的 URLPattern
实例。
两种方式对比
下面嵌入的 Glitch 演示说明了在 Service Worker 的 fetch event handler
内使用 URLPattern
的核心使用情形,它可将特定模式映射到可以生成网络请求响应的异步函数。此示例中的概念也可以应用于其他路由场景,无论是服务器端还是客户端。
反馈和未来计划
虽然 Chrome 和 Edge 中已经引入了 URLPattern
的基本功能,但我们计划增加其他功能。URLPattern
的某些方面仍在开发中,有许多关于特定行为的开放式问题可能仍需优化。我们建议您试用 URLPattern
,并通过 GitHub 问题提供反馈意见。
支持模板
path-to-regexp
库提供了一个 compile() function
,它可以有效地逆转路由行为。compile()
接受一个格式和令牌占位符的值,并返回用这些值被替换的网址路径的字符串。
我们希望将来将其添加到 网址Pattern,但这些内容不在初始版本的范围内。
实现未来的网络平台功能
假设 URLPattern
已成为 Web 平台的一部分,那么可从路由或模式匹配中受益的其他功能可以在其上作为基元进行构建。
人们目前正在讨论如何使用 URLPattern
来实现 Service Worker 范围模式匹配、PWA 作为文件处理程序和推测性预提取等提议的功能。
致谢
如需查看致谢列表,请参阅原始铺垫文档。