URLPattern: маршруты, которые не нужно переписывать в регулярки
В любом приложении со своей маршрутизацией рано или поздно заводится файл с регулярками. /^\/users\/(\d+)\/posts\/([\w-]+)$/ работает, но читается тяжело, номера групп приходится помнить наизусть, а проверка домена и протокола живёт отдельным условием рядом. Обычный выход состоит в том, чтобы притащить path-to-regexp и получить ещё одну зависимость ради разбора адреса.
Разбирать адреса умеет сама платформа. URLPattern принимает шаблон в знакомом по роутерам синтаксисе и сопоставляет с ним URL целиком, а не только путь:
const route = new URLPattern({ pathname: "/users/:id/posts/:slug" });
route.test("https://app.example.com/users/42/posts/hello"); // true
const match = route.exec("https://app.example.com/users/42/posts/hello");
match.pathname.groups; // { id: "42", slug: "hello" }
Группы именованные, так что возиться с их порядком не нужно. Кроме :name, доступны звёздочка для любого остатка, модификаторы ? и +, фигурные скобки для необязательных кусков пути и собственные регулярки прямо внутри шаблона: /v:version(\\d+)/ поймает только числовую версию.
Шаблон покрывает все части адреса сразу, и на этом удобно строить таблицу маршрутов в Service Worker или в edge-функции:
const routes = [
[new URLPattern({ pathname: "/api/v:v(\\d+)/*" }), handleApi],
[new URLPattern({ hostname: "cdn.example.com" }), handleAssets],
];
self.addEventListener("fetch", (event) => {
const route = routes.find(([pattern]) => pattern.test(event.request.url));
if (route) event.respondWith(route[1](event.request));
});
Главная разница между двумя формами записи проявляется как раз тут. В объектной форме всё, что не указано, считается звёздочкой: шаблон выше совпадёт с любым протоколом и любой строкой запроса. В строковой форме, new URLPattern("https://example.com/books/:id"), пропущенные части приравниваются к пустой строке, поэтому адрес с параметрами мимо такого шаблона пройдёт, пока не дописать ?*#* вручную.
Дальше идёт мелочёвка. Двоеточие внутри литерала экранируется обратным слешем, иначе парсер примет его за начало группы. Юникод в регулярных группах придётся кодировать процентами самостоятельно: автоматически платформа этого не делает. Регистр по умолчанию учитывается, а выключается опцией ignoreCase.
С сентября 2025, когда поддержку закрыл Safari 26, API считается Baseline и работает в том числе в воркерах. Для старых браузеров есть официальный полифилл urlpattern-polyfill, он же закрывает Node без экспериментальных флагов.
Смысл переезжать есть там, где роутер только сопоставляет адреса и достаёт из них параметры: слой регулярок или path-to-regexp в этом случае удаляется целиком. Если поверх маршрутизации навешаны вложенные layout-компоненты, middleware и ленивые импорты, URLPattern занимает нижний уровень, а остальное остаётся вашим кодом.
#frontendvkhub #javascript #urlpattern
В любом приложении со своей маршрутизацией рано или поздно заводится файл с регулярками. /^\/users\/(\d+)\/posts\/([\w-]+)$/ работает, но читается тяжело, номера групп приходится помнить наизусть, а проверка домена и протокола живёт отдельным условием рядом. Обычный выход состоит в том, чтобы притащить path-to-regexp и получить ещё одну зависимость ради разбора адреса.
Разбирать адреса умеет сама платформа. URLPattern принимает шаблон в знакомом по роутерам синтаксисе и сопоставляет с ним URL целиком, а не только путь:
const route = new URLPattern({ pathname: "/users/:id/posts/:slug" });
route.test("https://app.example.com/users/42/posts/hello"); // true
const match = route.exec("https://app.example.com/users/42/posts/hello");
match.pathname.groups; // { id: "42", slug: "hello" }
Группы именованные, так что возиться с их порядком не нужно. Кроме :name, доступны звёздочка для любого остатка, модификаторы ? и +, фигурные скобки для необязательных кусков пути и собственные регулярки прямо внутри шаблона: /v:version(\\d+)/ поймает только числовую версию.
Шаблон покрывает все части адреса сразу, и на этом удобно строить таблицу маршрутов в Service Worker или в edge-функции:
const routes = [
[new URLPattern({ pathname: "/api/v:v(\\d+)/*" }), handleApi],
[new URLPattern({ hostname: "cdn.example.com" }), handleAssets],
];
self.addEventListener("fetch", (event) => {
const route = routes.find(([pattern]) => pattern.test(event.request.url));
if (route) event.respondWith(route[1](event.request));
});
Главная разница между двумя формами записи проявляется как раз тут. В объектной форме всё, что не указано, считается звёздочкой: шаблон выше совпадёт с любым протоколом и любой строкой запроса. В строковой форме, new URLPattern("https://example.com/books/:id"), пропущенные части приравниваются к пустой строке, поэтому адрес с параметрами мимо такого шаблона пройдёт, пока не дописать ?*#* вручную.
Дальше идёт мелочёвка. Двоеточие внутри литерала экранируется обратным слешем, иначе парсер примет его за начало группы. Юникод в регулярных группах придётся кодировать процентами самостоятельно: автоматически платформа этого не делает. Регистр по умолчанию учитывается, а выключается опцией ignoreCase.
С сентября 2025, когда поддержку закрыл Safari 26, API считается Baseline и работает в том числе в воркерах. Для старых браузеров есть официальный полифилл urlpattern-polyfill, он же закрывает Node без экспериментальных флагов.
Смысл переезжать есть там, где роутер только сопоставляет адреса и достаёт из них параметры: слой регулярок или path-to-regexp в этом случае удаляется целиком. Если поверх маршрутизации навешаны вложенные layout-компоненты, middleware и ленивые импорты, URLPattern занимает нижний уровень, а остальное остаётся вашим кодом.
#frontendvkhub #javascript #urlpattern