path-to-regexp vs query-string vs url-template vs uri-template
JavaScript 中的 URL 处理、路由匹配与模板展开方案对比
path-to-regexpquery-stringurl-templateuri-template类似的npm包:

JavaScript 中的 URL 处理、路由匹配与模板展开方案对比

path-to-regexp、query-string、uri-template 和 url-template 都是处理 URL 相关逻辑的核心工具库,但侧重点不同。path-to-regexp 是路由匹配的事实标准,擅长将 URL 路径映射到处理函数;query-string 专注于解析和序列化 URL 查询参数;uri-template 和 url-template 则实现了 RFC 6570 标准,用于根据模板动态生成 URL。在现代前端架构中,它们通常组合使用,但在特定场景下(如纯路由或纯参数处理)需要做出明确的技术选型。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
path-to-regexp215,208,4118,60259.7 kB125 个月前MIT
query-string24,993,2636,90359.1 kB220 天前MIT
url-template16,695,4721957.99 kB33 年前BSD-3-Clause
uri-template044-25 年前MIT

JavaScript URL 处理工具深度对比:路由、查询与模板

在构建复杂的前端或 Node.js 应用时,URL 处理是基础设施中的关键一环。path-to-regexp、query-string、uri-template 和 url-template 分别解决了 URL 处理的不同切片。本文将深入对比它们在路径匹配、参数解析和模板生成方面的技术细节,帮助架构师做出准确决策。

🛣️ 路径匹配与参数提取

这是路由系统的核心功能。我们需要从 URL 中提取动态段(如 /user/123 中的 123)。

path-to-regexp 是此领域的王者。它支持强大的匹配语法(如正则、可选参数)。

import { match } from 'path-to-regexp';

const matchUser = match('/user/:id');
const result = matchUser('/user/123');
// result.params.id === '123'

query-string 专注于查询字符串,不支持 路径段匹配。强行使用它处理路径会导致逻辑错误。

import queryString from 'query-string';

// 仅能解析 ? 之后的内容,无法处理 /user/:id
const parsed = queryString.parse('?id=123');
// parsed.id === '123',但无法从路径中提取

uri-template 主要用于模板展开,虽然理论上可解析,但 API 设计侧重于生成而非匹配路径。

import { UriTemplate } from 'uri-template';

// 主要用于 expand,match 功能有限或非核心设计
const template = new UriTemplate('/user/{id}');
// 通常用于生成 URL,而非路由匹配

url-template 同样遵循 RFC 6570,侧重于根据变量生成 URL,而非作为路由器解析传入请求。

import urlTemplate from 'url-template';

// 侧重于 expand 方法
const template = urlTemplate.parse('/user/{id}');
// 设计初衷是生成链接,而非匹配路由

🔍 查询参数解析与序列化

处理 ?key=value&list=1&list=2 这种逻辑是前端常见需求。

path-to-regexp 不处理查询参数。它只关注路径名(pathname)。

import { match } from 'path-to-regexp';

// 无法解析 ?q=1
const matchFn = match('/search');
matchFn('/search?q=1'); 
// 忽略查询字符串,仅匹配路径

query-string 在此场景下表现最佳。支持数组、对象嵌套及自定义编码。

import queryString from 'query-string';

const parsed = queryString.parse('?tags=js&tags=web');
// parsed.tags === ['js', 'web']

const str = queryString.stringify({ q: 'hello' });
// str === 'q=hello'

uri-template 可以通过模板变量处理查询参数,但语法较复杂(RFC 6570 语法)。

import { UriTemplate } from 'uri-template';

// 使用 RFC 6570 语法处理查询
const template = new UriTemplate('/search{?q,page}');
const url = template.expand({ q: 'hello', page: 1 });
// url === '/search?q=hello&page=1'

url-template 同样支持 RFC 6570 的查询参数语法,适合标准化场景。

import urlTemplate from 'url-template';

const template = urlTemplate.parse('/search{?q,page}');
const url = template.expand({ q: 'hello', page: 1 });
// url === '/search?q=hello&page=1'

🏗️ URL 模板生成与展开

当需要根据变量动态构建 URL(例如 API 客户端或文档生成)时,模板引擎至关重要。

path-to-regexp 提供 compile 功能,可以反向生成路径,但语法是私有的,非标准。

import { compile } from 'path-to-regexp';

const toPath = compile('/user/:id');
const url = toPath({ id: 123 });
// url === '/user/123'

query-string 可以拼接查询字符串,但需要手动处理路径部分。

import queryString from 'query-string';

// 需手动拼接路径和查询
const path = '/search';
const query = queryString.stringify({ q: 'hello' });
const url = `${path}?${query}`;
// url === '/search?q=hello'

uri-template 支持完整的 RFC 6570 标准,适合需要标准互操作性的场景。

import { UriTemplate } from 'uri-template';

// 支持复杂的表达式如 {+var} 或 {.ext}
const template = new UriTemplate('/files/{name}{.ext}');
const url = template.expand({ name: 'report', ext: 'pdf' });
// url === '/files/report.pdf'

url-template 同样支持 RFC 6570,且 API 设计更为现代简洁。

import urlTemplate from 'url-template';

const template = urlTemplate.parse('/files/{name}{.ext}');
const url = template.expand({ name: 'report', ext: 'pdf' });
// url === '/files/report.pdf'

⚠️ 维护状态与生态风险

在选择底层基础设施库时,维护活跃度是架构决策的关键指标。

path-to-regexp 维护极佳。它是 Express、React Router 等主流框架的依赖,更新频繁,安全性高。

// 生态广泛,TypeScript 类型定义完善
import { match } from 'path-to-regexp'; 

query-string 维护良好。由知名开发者维护,广泛用于前端生态,API 稳定。

// 社区信任度高
import queryString from 'query-string';

uri-template 维护频率较低。npm 上的 uri-template 包更新较慢,可能存在长期未修复的 Issue。

// 建议在新项目中谨慎评估,考虑替代方案
import { UriTemplate } from 'uri-template';

url-template 维护相对活跃。bramus/url-template 是 RFC 6570 实现中较受推荐的一个,适合替代 uri-template。

// 更推荐的 RFC 6570 实现
import urlTemplate from 'url-template';

📊 核心特性对比表

特性path-to-regexpquery-stringuri-templateurl-template
核心用途路由匹配与路径编译查询参数解析与序列化RFC 6570 模板展开RFC 6570 模板展开
路径参数提取✅ 强大 (支持正则)❌ 不支持⚠️ 非主要功能⚠️ 非主要功能
查询参数处理❌ 不支持✅ 专业 (支持数组)✅ 通过模板语法✅ 通过模板语法
URL 生成✅ 路径编译⚠️ 仅查询部分✅ 完整模板展开✅ 完整模板展开
标准遵循私有语法事实标准RFC 6570RFC 6570
维护状态🟢 非常活跃🟢 活跃🟡 较低🟢 较好

💡 架构师建议

组合使用是最佳实践。

  1. 路由层:使用 path-to-regexp 处理服务端或前端路由匹配。它是行业标准,性能最优。
  2. 参数层:使用 query-string 处理浏览器地址栏的查询参数。它的 API 最符合 JavaScript 开发习惯。
  3. API 客户端层:如果需要构建符合 HATEOAS 或超媒体规范的 API 客户端,使用 url-template 进行 URL 生成。它比 uri-template 包更可靠。

避免重复造轮子。不要试图用 query-string 去匹配路径,也不要用 path-to-regexp 去解析复杂的查询数组。让每个工具做它最擅长的事,代码会更清晰、更易维护。

如何选择: path-to-regexp vs query-string vs url-template vs uri-template

  • path-to-regexp:

    如果你的核心需求是路由匹配(例如在 Node.js 服务器或自定义前端路由器中解析 URL 路径参数),path-to-regexp 是首选。它是 Express 和 React Router 的底层依赖,社区支持最强,适合处理 /users/:id 这种路径模式。

  • query-string:

    如果你只需要处理 URL 中的查询参数部分(即 ? 之后的内容),query-string 是最轻量且功能完备的选择。它擅长处理数组、编码和解码,适合在浏览器端或 Node.js 中快速解析 location.search。

  • url-template:

    如果你需要 RFC 6570 URL 模板展开 功能(例如构建 HATEOAS API 客户端或动态生成复杂 URL),url-template(by Bramus)是更现代、维护更活跃的选择。它比 uri-template 包更受推荐,适合需要标准化模板语法的场景。

  • uri-template:

    如果你需要严格遵循 RFC 6570 标准 进行 URL 模板展开,且项目依赖较旧的生态,可以考虑 uri-template。但需注意其维护频率较低,若在新项目中需要 RFC 6570 支持,建议优先评估更活跃的替代方案。

path-to-regexp的README

Path-to-RegExp

Turn a path string such as /user/:name into a regular expression.

NPM version NPM downloads Build status Build coverage License

Installation

npm install path-to-regexp --save

Usage

const {
  match,
  pathToRegexp,
  compile,
  parse,
  stringify,
} = require("path-to-regexp");

Parameters

Parameters match arbitrary strings in a path by matching up to the end of the segment, or up to any proceeding tokens. They are defined by prefixing a colon to the parameter name (:foo). Parameter names can use any valid JavaScript identifier, or be double quoted to use other characters (:"param-name").

const fn = match("/:foo/:bar");

fn("/test/route");
//=> { path: '/test/route', params: { foo: 'test', bar: 'route' } }

Wildcard

Wildcard parameters match one or more characters across multiple segments. They are defined the same way as regular parameters, but are prefixed with an asterisk (*foo).

const fn = match("/*splat");

fn("/bar/baz");
//=> { path: '/bar/baz', params: { splat: [ 'bar', 'baz' ] } }

Optional

Braces can be used to define parts of the path that are optional.

const fn = match("/users{/:id}/delete");

fn("/users/delete");
//=> { path: '/users/delete', params: {} }

fn("/users/123/delete");
//=> { path: '/users/123/delete', params: { id: '123' } }

Match

The match function returns a function for matching strings against a path:

  • path String, TokenData object, or array of strings and TokenData objects.
  • options (optional) (Extends pathToRegexp options)
    • decode Function for decoding strings to params, or false to disable all processing. (default: decodeURIComponent)
const fn = match("/foo/:bar");

Please note: path-to-regexp is intended for ordered data (e.g. paths, hosts). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).

PathToRegexp

The pathToRegexp function returns the regexp for matching strings against paths, and an array of keys for understanding the RegExp#exec matches.

  • path String, TokenData object, or array of strings and TokenData objects.
  • options (optional) (See parse for more options)
    • sensitive Regexp will be case sensitive. (default: false)
    • end Validate the match reaches the end of the string. (default: true)
    • delimiter The default delimiter for segments, e.g. [^/] for :named parameters. (default: '/')
    • trailing Allows optional trailing delimiter to match. (default: true)
const { regexp, keys } = pathToRegexp("/foo/:bar");

regexp.exec("/foo/123"); //=> ["/foo/123", "123"]

Compile ("Reverse" Path-To-RegExp)

The compile function will return a function for transforming parameters into a valid path:

  • path A string or TokenData object.
  • options (See parse for more options)
    • delimiter The default delimiter for segments, e.g. [^/] for :named parameters. (default: '/')
    • encode Function for encoding input strings for output into the path, or false to disable entirely. (default: encodeURIComponent)
const toPath = compile("/user/:id");

toPath({ id: "name" }); //=> "/user/name"
toPath({ id: "café" }); //=> "/user/caf%C3%A9"

const toPathRepeated = compile("/*segment");

toPathRepeated({ segment: ["foo"] }); //=> "/foo"
toPathRepeated({ segment: ["a", "b", "c"] }); //=> "/a/b/c"

// When disabling `encode`, you need to make sure inputs are encoded correctly. No arrays are accepted.
const toPathRaw = compile("/user/:id", { encode: false });

toPathRaw({ id: "%3A%2F" }); //=> "/user/%3A%2F"

Stringify

Transform a TokenData object to a Path-to-RegExp string.

  • data A TokenData object.
const data = {
  tokens: [
    { type: "text", value: "/" },
    { type: "param", name: "foo" },
  ],
};

const path = stringify(data); //=> "/:foo"

Developers

  • If you are rewriting paths with match and compile, consider using encode: false and decode: false to keep raw paths passed around.
  • To ensure matches work on paths containing characters usually encoded, such as emoji, consider using encodeurl for encodePath.

Parse

The parse function accepts a string and returns TokenData, which can be used with match and compile.

  • path A string.
  • options (optional)
    • encodePath A function for encoding input strings. (default: x => x, recommended: encodeurl)

Tokens

TokenData has two properties:

  • tokens A sequence of tokens, currently of types text, param, wildcard, or group.
  • originalPath The original path used with parse, shown in error messages to assist debugging.

Custom path

In some applications you may not be able to use the path-to-regexp syntax, but you still want to use this library for match and compile. For example:

import { match } from "path-to-regexp";

const tokens = [
  { type: "text", value: "/" },
  { type: "param", name: "foo" },
];
const originalPath = "/[foo]"; // To help debug error messages.
const path = { tokens, originalPath };
const fn = match(path);

fn("/test"); //=> { path: '/test', params: { foo: 'test' } }

Errors

An effort has been made to ensure ambiguous paths from previous releases throw an error. This means you might be seeing an error when things worked before.

Missing parameter name

Parameter names must be provided after : or *, for example /*path. They can be valid JavaScript identifiers (e.g. :myName) or JSON strings (:"my-name").

Unexpected ? or +

In past releases, ?, *, and + were used to denote optional or repeating parameters. As an alternative, try these:

  • For optional (?), use braces: /file{.:ext}.
  • For one or more (+), use a wildcard: /*path.
  • For zero or more (*), use both: /files{/*path}.

Unexpected (, ), [, ], etc.

Previous versions of Path-to-RegExp used these for RegExp features. This version no longer supports them so they've been reserved to avoid ambiguity. To match these characters literally, escape them with a backslash, e.g. "\\(".

Unterminated quote

Parameter names can be wrapped in double quote characters, and this error means you forgot to close the quote character. For example, :"foo.

Express <= 4.x

Path-To-RegExp breaks compatibility with Express <= 4.x in the following ways:

  • The wildcard * must have a name and matches the behavior of parameters :.
  • The optional character ? is no longer supported, use braces instead: /:file{.:ext}.
  • Regexp characters are not supported.
  • Some characters have been reserved to avoid confusion during upgrade (()[]?+!).
  • Parameter names now support valid JavaScript identifiers, or quoted like :"this".

License

MIT