URL Pattern Standard

repository·main·Indexed 41 days ago

https://github.com/whatwg/urlpattern

A WHATWG standard defining a mechanism for matching URLs using patterns. It provides the URLPattern and URLPatternList APIs to identify and extract information from URLs via the .test() and .exec() methods, with applications in JavaScript routing, Service Worker scope registration, and Web App Manifest scopePattern configuration.

Tokens
6.6K
Snippets
24
Records
27
Agent score
47%

What's inside URLPattern

  1. Understand Service Worker scope matching constraints

    main

    When using URLPattern for Service Worker scopes, there are specific design constraints to ensure performance and security:

    • Non-variable prefix: Scopes must have a non-variable prefix (e.g., /foo/*). This allows the browser to quickly exclude unrelated origins.
    • Complexity limits: To prevent performance abuse, registrations that result in too many nested, non-greedy scope patterns (e.g., more than 10) may be rejected.
    • Restricted syntax: Service worker scopes are restricted to exact matches and trailing wildcards. More complex regular expression groups are not supported for scopes to avoid unpredictable performance.
    • Ordering: Scopes are matched from most specific to least specific. For example, /foo/ is more specific than /foo/*, which is more specific than /foo/*?.
  2. Service-Worker-Allowed behavior with URLPattern

    main

    By default, a Service Worker script must be hosted at a path equal to or higher than its scope. When using URLPattern, the browser uses the pattern's "non-variable prefix" to maintain this security check.

    For example, a pattern like /foo/* has a non-variable prefix of /foo/. This allows scripts to be hosted at / or /foo/ levels. This behavior can be overridden using the Service-Worker-Allowed HTTP header.

    If using a URLPatternList for registration, the script location must be valid for every individual URLPattern in that list, unless overridden by the header.

  3. Register a Service Worker with a URLPattern scope

    main

    Instead of using a simple prefix-matching string, you can register a Service Worker with a URLPattern or URLPatternList as its scope. This allows for more precise control over which URLs are controlled by the Service Worker, preventing overlapping paths from interfering with each other.

    Restrictions for Service Worker scopes:

    • Variable components (like wildcards) are only allowed in pathname and search components.
    • No regular expressions are allowed except for (.*), which matches the * wildcard.
    • Variable components/wildcards must be in a trailing position.
    // Service worker controls '/foo' exactly, but not '/foo/bar'.
    navigator.serviceWorker.register(scriptURL, {
      scope: new URLPattern({
        baseURL: self.location,
        pathname: '/foo'
      })
    });
    
    // Service worker controls `/foo` and `/foo/*`, but does not control `/foobar`.
    navigator.serviceWorker.register(scriptURL, {
      scope: new URLPattern({
        baseURL: self.location,
        pathname: '/foo/*?'
      })
    });
  4. Make matching groups optional, repeated, or both

    main

    You can use modifiers after a group (named or unnamed) to control its matching behavior:

    • Optional (?): The group and any preceding / in the pathname become optional.
    • Repeated (+): The group and any preceding / in the pathname must appear at least once.
    • Optional and Repeated (*): The group and any preceding / in the pathname can appear zero or more times.
    // Optional group
    const patternOpt = new URLPattern({ pathname: "/product/:action?" });
    console.log(patternOpt.test("/product")); // true
    
    // Repeated group
    const patternRep = new URLPattern({ pathname: "/product/:action+" });
    console.log(patternRep.test("/product/do/some/thing")); // true
    
    // Optional and Repeated group
    const patternBoth = new URLPattern({ pathname: "/product/:action*" });
    console.log(patternBoth.test("/product")); // true
  5. Use base URLs for test() and exec()

    main

    When testing relative paths or component objects that lack a full URL context, you can provide a baseURL to resolve the input.

    • If the first argument to test() or exec() is a dictionary, you can include a baseURL property inside it.
    • Alternatively, you can pass the baseURL as the second argument to the method.
    • Note: You cannot pass both a dictionary input AND a second argument baseURL simultaneously; this will throw an error.
    const pattern = new URLPattern({ hostname: "example.com", pathname: "/foo/*" });
    
    // Option 1: Include baseURL in the input dictionary
    console.log(
        pattern.test({ pathname: "/foo/bar", baseURL: "https://example.com/baz" }));
    
    // Option 2: Pass baseURL as the second argument
    console.log(pattern.test("/foo/bar", "https://example.com/baz"));
    
    // The exec() method supports the same arguments
    const result = pattern.exec("/foo/bar", "https://example.com/baz");
  6. Escape characters to disambiguate constructor strings

    main

    If a character is ambiguous (e.g., : could be a protocol separator or the start of a named pattern group), the constructor favors pattern syntax. To force a character to be treated as part of the URL syntax, escape it with a backslash \\.

    // Constructs a URLPattern treating the `:` as the protocol suffix.
    // Using \\: escapes the colon.
    const pattern = new URLPattern("data\\:foo*");
    
    // Prints `data`
    console.log(pattern.protocol);
    
    // Prints `foo*`
    console.log(pattern.pathname);
    
    // Prints `true`
    console.log(pattern.test("data:foobar"));
  7. Use custom regular expressions in groups

    main

    You can restrict the characters matched by a group by providing a custom regular expression in parentheses immediately following the group name or wildcard. This works for both named and unnamed groups.

    // Custom regex in an unnamed group
    const pattern1 = new URLPattern({ pathname: "/(foo|bar)" });
    console.log(pattern1.test({ pathname: "/foo" })); // true
    
    // Custom regex in a named group
    const pattern2 = new URLPattern({ pathname: "/:type(foo|bar)" });
    const result = pattern2.exec({ pathname: "/foo" });
    console.log(result.pathname.groups.type); // "foo"
  8. Simplify URLPattern initialization with baseURL

    main

    Instead of defining every component, you can use the baseURL property to provide a fixed origin. This allows you to define only the parts of the URL that change, such as the pathname.

    You can also use relative pathnames by providing a baseURL that includes the base path.

    // Using baseURL to simplify origin patterns
    const p = new URLPattern({
      pathname: '/foo/:image.jpg',
      baseURL: 'https://example.com',
    });
    
    // Using relative pathnames
    const p2 = new URLPattern({
      pathname: ':image.jpg',
      baseURL: 'https://example.com/foo/',
    });
  9. Use custom prefixes or suffixes for modifiers

    main

    When using optional (?), repeated (+), or optional-and-repeated (*) modifiers, you can use curly braces {} to define a custom prefix or suffix that the modifier should operate on. This is particularly useful for complex hostname patterns.

    // The modifier * applies to the custom prefix {:subdomain.}
    const pattern = new URLPattern({ hostname: "{:subdomain.}*example.com" });
    
    console.log(pattern.test({ hostname: "example.com" })); // true
    console.log(pattern.test({ hostname: "foo.bar.example.com" })); // true
    
    const result = pattern.exec({ hostname: "foo.bar.example.com" });
    console.log(result.hostname.groups.subdomain); // "foo.bar"
  10. Use base URLs in the URLPattern Constructor

    main

    You can provide a baseURL during construction to resolve relative pattern strings.

    • When using a baseURL, the resulting URLPattern will have values for every component (even if they are empty strings), so you do not get the default wildcard * behavior.
    • The baseURL must be a valid URL and cannot contain pattern syntax.
    • A relative constructor string must have a baseURL provided, otherwise it throws an error.
    // Using baseURL in constructor
    const pattern1 = new URLPattern({ pathname: "/foo/*", 
                                      baseURL: "https://example.com" });
    
    // Equivalent to passing baseURL as second argument
    const pattern2 = new URLPattern("/foo/*", "https://example.com");
    
    // This will throw because the relative string has no base
    try {
      const pattern3 = new URLPattern("/foo/*");
    } catch (e) {}
  11. Configure Web App Manifest scopePattern

    main

    To provide advanced scope matching in a Web App Manifest, use the scopePattern JSON key. This allows you to define a URLPattern for the application scope. If both the legacy scope (string) and the new scopePattern are provided, scopePattern takes precedence. This ensures backward compatibility with older browsers.

    {
      "name": "FooApp",
      "scope": "https://foo.com/app/",
      "scopePattern": {
        "baseURL": "https://foo.com/",
        "pathname": "/app/*?"
      }
    }
  12. Implement JavaScript URL routing with URLPattern

    main

    You can use URLPattern.exec() to route URLs to specific business logic by extracting named parameters from the path. This follows the path-to-regexp syntax used by many web frameworks.

    Common patterns include:

    • Wildcards with extensions: /*.:imagetype(jpg|png|gif)
    • Named parameters: :product
    • Optional parameters: :param?
    const imagePattern = new URLPattern({
      pathname: '/*.:imagetype(jpg|png|gif)'
    });
    if (let result = imagePattern.exec(url)) {
      return handleImage(result.pathname.groups['imagetype']);
    }
    
    const apiPattern = new URLPattern({
      pathname: '/api/:product/:param?'
    });
    if (let result = apiPattern.exec(url)) {
      return handleAPI(result.pathname.groups['product'],
                       result.pathname.groups['param']);
    }