@b9g/match-pattern
v0.2.1
Published
High-performance URLPattern-compatible implementation for web routing with enhanced search parameter handling.
Readme
MatchPattern
High-performance URLPattern-compatible implementation for web routing with enhanced search parameter handling.
Overview
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
paramsobject)
Both compile patterns directly to RegExp in a single pass, bypassing the multi-stage pipeline used by polyfill/native implementations.
Installation
npm install @b9g/match-patternBasic Usage
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' }
}Performance
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+) - Optional parameters:
:id? - Repeat modifiers:
:path+,:path* - Wildcards:
* - Regex groups:
(\d+) - Explicit delimiters:
{/old}? - Escaped characters:
\.
Key Differences from URLPattern
1. Order-Independent Search Parameters
URLPattern requires exact parameter order. MatchPattern allows any order:
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: false2. Non-Exhaustive Search Matching
URLPattern uses greedy capture that lumps extra params into the last parameter value. MatchPattern properly parses them:
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:
pattern.test('/search'); // false (q missing)
pattern.test('/search?q=hello'); // true
pattern.test('/search?q=hello&page=1&limit=10'); // true (extras captured)3. Unified Parameter Object
URLPattern separates pathname and search groups. MatchPattern merges everything:
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'}4. Enhanced String Pattern Syntax
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:
// 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'
})Trailing Slash Handling
MatchPattern does not automatically normalize trailing slashes. Use explicit patterns:
// 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/'); // trueImplementation Notes
Direct RegExp Compilation
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
URLPattern Spec Compliance
The URLPattern class passes 100% of the Web Platform Tests (755 tests). It implements the full URLPattern specification:
- Named parameters:
:id,:id(\d+) - Optional parameters:
:id? - Repeat modifiers:
:path+,:path* - Wildcards:
* - Regex groups:
(\d+) - Explicit delimiters:
{/old}? - 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
Exports
Classes
URLPattern- 100% WPT-compliant URLPattern implementationMatchPattern- URLPattern with routing enhancements (order-independent search params, unified params)
Types
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
Utility Functions
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
API Reference
URLPattern
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
}MatchPattern
class MatchPattern {
constructor(input: string | URLPatternInit, baseURL?: string)
test(input: string | URL): boolean
exec(input: string | URL): MatchPatternResult | null
}Utility Functions
Advanced functions for pattern inspection and compilation (used by router optimizations):
// 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 }
): CompiledPatternTypes
interface MatchPatternResult extends URLPatternResult {
params: Record<string, string>; // Unified parameters from pathname and search
}
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[];
}Compatibility
- Runtimes: Node, Deno, Bun, Cloudflare Workers, Edge Runtime, any JavaScript environment
- Browsers: All browsers (no polyfill required)
- TypeScript: 5.0+ recommended
Contributing
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
License
MIT
