WHATWG URLPattern
Elide implements the WHATWG [URL Pattern Standard][spec] URLPattern global — a routing-oriented
URL matcher with named captures, per-component regex groups, and the full {...} grouping /
modifier grammar. The underlying regex engine is graal-js's TRegex, so user-provided
(regex) groups get full ECMAScript RegExp semantics (including Unicode property escapes and
variable-length lookbehind).
[spec]: https://urlpattern.spec.whatwg.org/
Construction
new URLPattern(input, baseURL?, options?) // string form
new URLPattern(init, options?) // init-dict form| Argument | Type | Notes | |
|---|---|---|---|
input | USVString \ | URLPatternInit | Full-URL pattern string (e.g. "https://*.example.com/api/:id") or an init dict with per-component patterns. |
baseURL | USVString | Only valid for the string form; resolves a relative pattern against the base URL's protocol / host / etc. | |
options | URLPatternOptions | { ignoreCase?: boolean = false } — when true, all component regexes compile with the i flag. |
URLPattern(...) without new throws TypeError — per WebIDL, interface objects with
constructors must be invoked with new.
URLPatternInit
interface URLPatternInit {
protocol?: string;
username?: string;
password?: string;
hostname?: string;
port?: string;
pathname?: string;
search?: string;
hash?: string;
baseURL?: string;
}Any component not specified defaults to the pattern "" (matches an empty component). The
optional baseURL field lays down defaults for unspecified components.
Readonly attributes
| Name | Type | Notes |
|---|---|---|
protocol | string | The compiled pattern source for this component (already canonicalised). |
username | string | — |
password | string | — |
hostname | string | — |
port | string | — |
pathname | string | — |
search | string | Without a leading ?. |
hash | string | Without a leading #. |
hasRegExpGroups | boolean | true if any component pattern uses an explicit (regex) group. |
Operations
pattern.test(input?, baseURL?) // → boolean
pattern.exec(input?, baseURL?) // → URLPatternResult | nullinputmay be a URL string or aURLPatternInitdict. When it's a string andbaseURLis provided, the string is resolved against the base.execreturnsnullon mismatch; otherwise aURLPatternResult:
interface URLPatternResult {
inputs: Array<string | URLPatternInit>;
protocol: { input: string; groups: Record<string, string | undefined> };
username: { input: string; groups: Record<string, string | undefined> };
// ... one entry per URL component
}Pattern grammar
The pattern source goes through a tokenizer, a component-aware parser, and a regex generator before being handed to the ECMAScript regex compiler:| Syntax | Meaning |
|---|---|
literal | Matches verbatim (canonicalised per component). |
:name | Named capture; matches the component's segment-wildcard regex. |
:name(regex) | Named capture using a custom ECMA regex. |
(regex) | Unnamed (auto-indexed) capture using a custom regex. |
* | Full-wildcard shorthand for (.*). |
{group} | Explicit grouping; prefix / suffix literals between braces are treated atomically. |
?, +, * | Modifiers after a part — optional, one-or-more, zero-or-more. |
\x | Escape — matches the literal character x even if it's a metacharacter. |
| Component | Prefix | Segment-wildcard regex |
|---|---|---|
protocol | — | [^:]+? |
username | — | [^:@/]+? |
password | — | [^:@/]+? |
hostname | — | [^.:/?#]+? |
port | — | [^/?#]+? |
pathname | / | [^/?#]+? |
search | — | [^#]+? |
hash | — | [^]+? |
Canonicalization
Pattern and input strings are both canonicalised per the URL Standard before matching, so a pattern written against a canonical form matches a loosely-typed user URL:
- protocol — ASCII-lowercased.
- hostname — IDNA
toASCII+ lowercased. IPv6 literals pass through verbatim. - port — leading zeros stripped, range-validated (
0..65535). - pathname / search / hash / userinfo — percent-encoded per the URL spec's per-component encode sets (fragment set for
hash, query set forsearch, path set forpathname, userinfo set forusername/password). Already-well-formed percent triplets pass through without double-encoding.
Pattern inputs with embedded metacharacters (: { } ( ) * ? + \) preserve
those characters verbatim during canonicalization so they're seen by the pattern tokenizer.
Examples
// Named path segment.
const p = new URLPattern({ pathname: "/users/:id" });
p.test({ pathname: "/users/42" }); // true
p.exec({ pathname: "/users/42" }).pathname.groups; // { id: "42" }
// Full URL with wildcard host.
const api = new URLPattern("https:<<>>
api.test(" https://v2.example.com/api/users"); // true
api.exec("https:<<>>
<<>>
const numeric = new URLPattern({ pathname: " /items/:id(\\d+)" });
numeric.test({ pathname: "/items/abc" }); <<>>
numeric.test({ pathname: " /items/123" }); <<>>
<<>>
const opt = new URLPattern({ pathname: " /users/:id?" });
opt.test({ pathname: "/users" }); <<>>
opt.test({ pathname: " /users/42" }); <<>>
<<>>
const multi = new URLPattern({ pathname: " /files/:path+" });
multi.exec({ pathname: "/files/a/b/c" }).pathname.groups.path; <<>>
<<>>
const rel = new URLPattern(" /items/:id", "https://example.com/");
rel.test("https://example.com/items/7"); // trueNotes on the implementation
- Plain-call
URLPattern(...)is routed toTypeErrorvia a manualconstruct?-ternary patch in the generatedJSURLPatternClassBase.java(same pattern as the other Phase 2 classes).elide-dev/bindgenemits this branch automatically on the next regenerate. - Compiled regexes are produced via
RegexCompilerInterface.compile(...)— graal-js's TRegex compiler — so custom(regex)groups get full ECMAScript semantics, not Java's near-compatible subset. - Capture names are tracked in a parallel list rather than baked into the regex as
(?. This is necessary because the spec's...) +/*modifier forms require an outer repeating group that ECMA doesn't allow to re-bind a named group on each repetition; the two-list design keeps group extraction a single array lookup. - The class is installed via
installAsGlobal(notputBuiltinClass) for the same reason asAbortSignal/AbortController: graal-js's per-realm shape-slot array is fixed atJSContextcreation and our non-ordinaryURLPatternObjectlayout collides with graal-js's ownJSOrdinaryObjectshapes at high indices. The custom installer usesJSObjectFactory.createBoundto keep the shape on the factory itself.