High-performance URLPattern-compatible implementation for web routing with enhanced search parameter handling.
High-performance URLPattern-compatible implementation for web routing with enhanced search parameter handling.
This package provides two classes:
- URLPattern: A 100% WPT-compliant implementation that's ~40-60x faster than the polyfill/native
- MatchPattern: Same performance with routing enhancements (order-independent search params, unified params object)
Both compile patterns directly to RegExp in a single pass, bypassing the multi-stage pipeline used by polyfill/native implementations.
``bash`
npm install @b9g/match-pattern
`javascript
import {MatchPattern, URLPattern} from '@b9g/match-pattern';
// URLPattern: 100% WPT-compliant, ~60x faster than polyfill/native
const strict = new URLPattern({ pathname: '/api/posts/:id' });
// MatchPattern: Same performance + DX improvements
const pattern = new MatchPattern('/api/posts/:id&format=:format');
const url = new URL('http://example.com/api/posts/123?format=json&page=1');
if (pattern.test(url)) {
const result = pattern.exec(url);
console.log(result.params);
// { id: '123', format: 'json', page: '1' }
}
`
MatchPattern compiles patterns directly to optimized RegExp in a single pass, while the URLPattern polyfill uses a multi-stage pipeline (lexer → parser → RegExp generator). This results in ~40-60x faster pattern matching:
| Benchmark | URLPattern | MatchPattern | Polyfill | Native |
|-----------|------------|--------------|----------|--------|
| Static test() | 37ns | 72ns | 3.02µs | 2.32µs |
| Dynamic exec() | 304ns | 483ns | 2.45µs | 2.42µs |
| Construction | 760ns | 634ns | 16.58µs | 16.17µs |
Benchmarks run on Apple M1, Bun 1.3.3. See bench/urlpattern.bench.js.
MatchPattern adds ~35ns overhead for order-independent search parameter matching - a feature the URLPattern spec explicitly doesn't support.
All URLPattern syntax is fully supported including:
- Named parameters with regex constraints: :id(\d+):id?
- Optional parameters: :path+
- Repeat modifiers: , :path**
- Wildcards: (\d+)
- Regex groups: {/old}?
- Explicit delimiters: \.
- Escaped characters:
URLPattern requires exact parameter order. MatchPattern allows any order:
`javascript
const pattern = new MatchPattern({search: 'type=:type&sort=:sort'});
// URLPattern: Only first URL matches
// MatchPattern: Both URLs match
pattern.test('/?type=blog&sort=date'); // Both: true
pattern.test('/?sort=date&type=blog'); // MatchPattern: true, URLPattern: false
`
URLPattern uses greedy capture that lumps extra params into the last parameter value. MatchPattern properly parses them:
`javascript
const pattern = new MatchPattern({search: 'q=:query'});
// URLPattern greedy capture issue
const urlPattern = new URLPattern({search: 'q=:query'});
urlPattern.exec('?q=hello&page=1').search.groups; // { query: "hello&page=1" }
// MatchPattern proper parsing
const result = pattern.exec('/?q=hello&page=1&limit=10');
console.log(result.params); // {q: 'hello', page: '1', limit: '10'}
`
Required parameters must be present, but extra parameters are allowed:
`javascript`
pattern.test('/search'); // false (q missing)
pattern.test('/search?q=hello'); // true
pattern.test('/search?q=hello&page=1&limit=10'); // true (extras captured)
URLPattern separates pathname and search groups. MatchPattern merges everything:
`javascript
const pattern = new MatchPattern('/api/:version/posts/:id&format=:format');
const result = pattern.exec('/api/v1/posts/123?format=json&page=1');
// URLPattern: result.pathname.groups + result.search.groups (separate)
// MatchPattern: result.params (unified)
console.log(result.params); // {version: 'v1', id: '123', format: 'json', page: '1'}
`
It's not possible to separate pathname from search with ? because the syntax is used to indicate optionality, so MatchPattern supports convenient string patterns with & separator:
`javascript
// Pathname only (URLPattern throws a TypeError when passing a relative path without a baseURL)
new MatchPattern('/api/posts/:id')
// Pathname with search parameters
new MatchPattern('/api/posts/:id&format=:format&page=:page')
// Search parameters only
new MatchPattern('&q=:query&sort=:sort')
// Full URL patterns
new MatchPattern('https://api.example.com/v1/posts/:id&format=:format')
// Object syntax (same as URLPattern, enhanced behavior)
new MatchPattern({
pathname: '/api/posts/:id',
search: 'format=:format'
})
`
MatchPattern does not automatically normalize trailing slashes. Use explicit patterns:
`javascript
// Exact matching
const exactPattern = new MatchPattern('/api/posts/:id');
exactPattern.test('/api/posts/123'); // true
exactPattern.test('/api/posts/123/'); // false
// Optional trailing slash
const flexiblePattern = new MatchPattern('/api/posts/:id{/}?');
flexiblePattern.test('/api/posts/123'); // true
flexiblePattern.test('/api/posts/123/'); // true
`
MatchPattern compiles URLPattern syntax directly to RegExp in a single pass, while the URLPattern polyfill uses a multi-stage pipeline (lexer → parser → RegExp generator). This approach provides:
- Performance: ~40-60x faster than the URLPattern polyfill and native implementations
- Consistency: Same behavior across all JavaScript runtimes
- Zero dependencies: No polyfill required
- Simplicity: Direct pattern-to-RegExp compilation with minimal overhead
The URLPattern class passes 100% of the Web Platform Tests (755 tests). It implements the full URLPattern specification:
- Named parameters: :id, :id(\d+):id?
- Optional parameters: :path+
- Repeat modifiers: , :path**
- Wildcards: (\d+)
- Regex groups: {/old}?
- Explicit delimiters: \.
- Escaped characters:
- Protocol, hostname, port, pathname, search, and hash matching
- baseURL parameter for relative pattern resolution
- ignoreCase option
MatchPattern intentionally deviates from strict spec compliance in two areas to provide better routing ergonomics:
- Allows relative patterns without baseURL (convenience for routing)
- Order-independent search parameter matching
- URLPattern - 100% WPT-compliant URLPattern implementationMatchPattern
- - URLPattern with routing enhancements (order-independent search params, unified params)
- MatchPatternResult - Result type for MatchPattern.exec()URLPatternOptions
- - Options for URLPattern constructor (ignoreCase, etc.)ParsedPattern
- - Parsed pattern structurePatternSegment
- - Individual segment of a parsed patternCompiledPattern
- - Internal compiled pattern representation
Advanced functions for pattern inspection and compilation for optimized routers
- isSimplePattern(pathname: string): boolean - Check if pathname is a simple pattern (no regex features)parseSimplePattern(pathname: string): ParsedPattern | null
- - Parse a simple pattern into segmentscompilePathname(pathname: string, options?: object): CompiledPattern
- - Compile a pathname pattern to RegExp
`typescript
class URLPattern {
constructor(input?: string | URLPatternInit, baseURL?: string, options?: URLPatternOptions)
test(input: string | URL | URLPatternInit, baseURL?: string): boolean
exec(input: string | URL | URLPatternInit, baseURL?: string): URLPatternResult | null
readonly protocol: string
readonly username: string
readonly password: string
readonly hostname: string
readonly port: string
readonly pathname: string
readonly search: string
readonly hash: string
}
`
`typescript
class MatchPattern {
constructor(input: string | URLPatternInit, baseURL?: string)
test(input: string | URL): boolean
exec(input: string | URL): MatchPatternResult | null
}
`
Advanced functions for pattern inspection and compilation (used by router optimizations):
`typescript
// Check if a pathname pattern contains only literal segments and named parameters
function isSimplePattern(pathname: string): boolean
// Parse a simple pattern into its component segments
function parseSimplePattern(pathname: string): ParsedPattern | null
// Compile a pathname pattern to optimized RegExp
function compilePathname(
pathname: string,
options?: { ignoreCase?: boolean }
): CompiledPattern
`
`typescript
interface MatchPatternResult extends URLPatternResult {
params: Record
}
interface URLPatternOptions {
ignoreCase?: boolean; // Case-insensitive matching
}
interface ParsedPattern {
segments: PatternSegment[];
paramNames: string[];
}
type PatternSegment =
| { type: 'literal'; value: string }
| { type: 'param'; name: string; pattern?: string }
| { type: 'wildcard' }
| { type: 'group'; segments: PatternSegment[] }
interface CompiledPattern {
regexp: RegExp;
paramNames: string[];
}
``
- Runtimes: Node, Deno, Bun, Cloudflare Workers, Edge Runtime, any JavaScript environment
- Browsers: All browsers (no polyfill required)
- TypeScript: 5.0+ recommended
MatchPattern follows the WHATWG URLPattern specification while extending it for routing use cases.
Report issues related to:
- URLPattern compatibility problems
- Performance issues with complex patterns
- Cross-runtime behavior differences
MIT