Elide implements the WHATWG URL Pattern Standard 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).
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. |
Calling 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 | null
inputmay 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. |
Per-component defaults (segment-wildcard and separator):
| 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://*.example.com/api/:resource");
api.test("https://v2.example.com/api/users"); // true
api.exec("https://v2.example.com/api/widgets").pathname.groups.resource; // "widgets"
// Custom regex for numeric IDs.
const numeric = new URLPattern({ pathname: "/items/:id(\\d+)" });
numeric.test({ pathname: "/items/abc" }); // false
numeric.test({ pathname: "/items/123" }); // true
// Optional segment with modifier.
const opt = new URLPattern({ pathname: "/users/:id?" });
opt.test({ pathname: "/users" }); // true
opt.test({ pathname: "/users/42" }); // true
// One-or-more repeating segment.
const multi = new URLPattern({ pathname: "/files/:path+" });
multi.exec({ pathname: "/files/a/b/c" }).pathname.groups.path; // "a/b/c"
// baseURL resolution for a relative pattern.
const rel = new URLPattern("/items/:id", "https://example.com/");
rel.test("https://example.com/items/7"); // true
Notes 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
(?<name>...). 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.