文档目录
JavaScript路由构建器

简介

本技术文档围绕 DouPHP 的 JavaScript 路由构建体系,重点解释 JsRouteBuilder 类的作用与重要性,说明后端路由信息如何被序列化为前端可用的 JavaScript 路由对象,并详述路由名称、URL 模板、参数结构的转换过程。文档同时覆盖前端路由生成的安全与性能策略、前后端路由同步机制与版本兼容性处理,并提供丰富的场景化示例与最佳实践,帮助初学者快速集成、高级开发者进行扩展与定制。

项目结构

DouPHP 的前台与后台均提供独立的 JS 路由实现,并通过 PHP 侧的导出器将声明式路由清单以脚本形式注入浏览器,形成“后端路由表 → 前端可执行脚本 → 命名路由生成”的闭环。

graph TB
subgraph "后端"
A["JsRouteExporter<br/>导出 manifest/config"]
B["RouteManifest<br/>声明式路由清单"]
C["PrettyRouteMatcher<br/>入站匹配规则"]
end
subgraph "前端"
D["route.js前台/后台<br/>window.route()"]
E["__douRouteConfig<br/>运行时配置"]
F["__douRouteManifest<br/>name=>pattern 映射"]
end
A --> |输出 JSON/JS| F
A --> |内联 JSON| E
B --> |读取 declared 条目| A
C --> |规则来源| B
D --> |读取| E
D --> |读取| F

核心组件

  • JsRouteBuilder:浏览器 route.js 的 PHP 镜像,用于 roundtrip 扫描与离线断言;提供 url()、placeholderNames() 等能力,确保前后端 URL 生成语义一致。
  • JsRouteExporter:负责将 RouteManifest 中的具名路由导出为前端可消费的 payload(config + routes),并以缓存化的 manifest 脚本输出到浏览器。
  • 前端 route.js(前台/后台):在浏览器中实现命名路由生成,支持可选段填充、语言前缀、重写模式、查询参数拼接等。
  • 路由清单与匹配:RouteManifest 提供声明式路由清单;PrettyRouteMatcher 负责前台入站匹配,保证前后端规则一致性。

架构总览

下图展示了从后端声明式路由到前端命名路由生成的完整流程,包括配置注入、manifest 缓存、前端解析与 URL 组装。

sequenceDiagram
participant Dev as "开发者"
participant RM as "RouteManifest"
participant Exp as "JsRouteExporter"
participant Admin as "Admin RoutesController"
participant Front as "Front RoutesController"
participant Browser as "浏览器"
participant JS as "route.js"
Dev->>RM : 定义声明式路由declared
RM-->>Exp : 暴露 getEntriesByType('declared')
Exp->>Exp : buildFrontPayload()/buildAdminPayload()
Exp->>Browser : 输出 window.__douRouteManifest缓存
Exp->>Browser : 内联 window.__douRouteConfig含 rewrite/lang/url
Browser->>JS : 调用 route(name, params, options)
JS->>JS : placeholderNames/fillPattern/appendQuery
JS-->>Dev : 返回最终 URL

详细组件分析

JsRouteBuilder 类分析

JsRouteBuilder 是浏览器端 route.js 的 PHP 镜像,用于在服务器端对前端生成的 URL 进行校验与断言,确保前后端行为一致。其关键能力包括:

  • url(payload, name, params, options):根据 name 查找 pattern,提取占位符并填充,处理 rewrite 模式、shell 差异(前台语言前缀 vs 后台 base url)、查询参数与分页参数。
  • placeholderNames(pattern):从 pattern 中提取占位符名列表,供参数校验与过滤。
  • applyFrontLanguagePrefix(path, payload):对齐前台语言前缀逻辑,支持 rewrite_open 模式与 lang 参数追加。
classDiagram
class JsRouteBuilder {
+url(payload, name, params, options) string
+placeholderNames(pattern) array
-applyFrontLanguagePrefix(path, payload) string
}

JsRouteExporter 导出器分析

JsRouteExporter 负责将后端路由清单转换为前端可消费的数据结构,并提供缓存化 manifest 脚本输出能力:

  • exportRoutesForEnd(endNamespace):按端(Front/Admin)筛选 declared 路由,生成 name => pattern 映射。
  • buildFrontPayload()/buildAdminPayload():组合运行时配置(rewrite、url、lang)与路由表。
  • ensureCachedManifest(endNamespace):生成 window.__douRouteManifest 脚本并写入 storage/cache/js,基于内容指纹实现 immutable 缓存。
  • manifestJson()/manifestHash()/manifestUrlVersion():提供 JSON 序列化、哈希与带版本的 URL 参数,便于浏览器强缓存与失效控制。
flowchart TD
Start(["开始"]) --> BuildCfg["构建运行时配置<br/>buildFrontPayload/buildAdminPayload"]
BuildCfg --> ExportRoutes["导出 name=>pattern<br/>exportRoutesForEnd"]
ExportRoutes --> ToJSON["序列化 JSON<br/>manifestJson"]
ToJSON --> Hash["计算内容指纹<br/>manifestHash"]
Hash --> Cache["写入缓存文件<br/>ensureCachedManifest"]
Cache --> Output["输出脚本<br/>application/javascript"]
Output --> End(["结束"])

前端 route.js 分析

前端 route.js 实现了命名路由生成,支持以下特性:

  • 占位符提取与可选段填充:placeholderNames/fillPattern/shouldRenderOptionalSegment。
  • 语言前缀处理:applyFrontLanguagePrefix,支持 rewrite_open 模式与 lang 参数追加。
  • 查询参数拼接:appendQuery,自动编码并避免重复键。
  • 重写模式:根据 config.rewrite 决定 inner 路径是否走 index.php?route= 或直出路径。
sequenceDiagram
participant App as "应用代码"
participant JS as "route.js"
participant CFG as "__douRouteConfig"
participant MAN as "__douRouteManifest"
App->>JS : route(name, params, options)
JS->>MAN : 读取 routes[name]
JS->>JS : placeholderNames/fillPattern
JS->>CFG : 判断 rewrite/shell/lang
JS->>JS : appendQuery(page/query)
JS-->>App : 返回 URL

路由清单与入站匹配

  • RouteManifest:提供进程级缓存的声明式路由清单,支持按类型过滤与具名反查,供 UrlBuilder 出站生成与 PrettyRouteMatcher 入站匹配使用。
  • PrettyRouteMatcher:仅前台 declared 条目参与前台规则表,包含方法感知消歧与显式字段附加,确保入站匹配与出站生成的一致性。

依赖关系分析

  • JsRouteBuilder 依赖 PrettyUrlCompiler 进行 pattern 填充(通过 PrettyUrlCompiler::fill)。
  • JsRouteExporter 依赖 RouteManifest 获取 declared 条目,并依赖 ManifestCacheGeneration 生成带版本的 URL。
  • 前端 route.js 依赖 douRouteConfig 与 douRouteManifest 全局变量,由后端注入。
  • 后台与前台分别通过各自的 Controller 输出 manifest 脚本,统一使用 JsRouteExporter 的能力。
graph LR
JRB["JsRouteBuilder"] --> PUC["PrettyUrlCompiler"]
JRE["JsRouteExporter"] --> RM["RouteManifest"]
JRE --> MCG["ManifestCacheGeneration"]
JSF["route.js前台"] --> CFG["__douRouteConfig"]
JSF --> MAN["__douRouteManifest"]
JSB["route.js后台"] --> CFG
JSB --> MAN

性能考虑

  • 缓存化 manifest:JsRouteExporter::ensureCachedManifest 将 manifest 写入 storage/cache/js,并使用 immutable 缓存头与 ETag,减少重复请求与解析开销。
  • 内容指纹版本:manifestUrlVersion 结合 ManifestCacheGeneration 生成带版本号的 URL,路由表变更时自动触发浏览器重新拉取。
  • 前端轻量解析:route.js 使用正则与字符串操作完成 pattern 填充与查询参数拼接,避免重型库依赖。
  • 语言前缀优化:前台语言前缀仅在 pack 非空且 mode 为 rewrite_open 时插入路径,否则以 lang 参数追加,降低路径复杂度。

故障排查指南

  • 未知路由错误:当 route(name) 找不到对应 pattern 时,前端会抛出错误并记录日志。检查声明式路由是否正确注册且属于当前端(Front/Admin)。
  • 路由未初始化:若 douRouteConfig 或 douRouteManifest 未加载,前端会报错。确认模板已正确内联配置并引入 manifest 脚本。
  • 缓存问题:若路由变更后未生效,检查 storage/cache/js 下的缓存文件与 ?v= 版本号,必要时清理缓存并重启服务。
  • 安全头冲突:后台/前台 manifest 输出前会移除早期可能下发的 text/html 与缓存头,避免与 SecurityHeaders 的 nosniff 冲突导致脚本被拦截。

结论

JsRouteBuilder 作为前后端路由生成的桥梁,确保了命名路由在不同环境下的语义一致性。配合 JsRouteExporter 的缓存化 manifest 与前端 route.js 的轻量解析,DouPHP 实现了高效、安全且易维护的路由体系。通过声明式路由与前后端一致的 URL 生成逻辑,开发者可以专注于业务逻辑,而无需担心路由细节的差异。

附录:使用示例与最佳实践

基本用法

  • 前台页面中使用命名路由:在前台模板或 JS 中调用 window.route('name', params, options),其中 name 为声明式路由名,params 为路径参数,options 包含 query 与 page。
  • 后台管理中使用命名路由:后台模板同样可通过 window.route 生成 URL,注意后台 shell 模式下会拼接 base url。

静态路由

  • 适用于固定路径,如首页、关于页等。在声明式路由中定义 name 与 pattern,前端通过 route(name) 直接生成 URL。

动态路由

  • 适用于带参数的资源路径,如 /product/{id}。在 params 中传入对应占位符值,前端会自动填充并生成 URL。

带参数的路由

  • 支持可选段与正则约束,前端会根据参数是否存在决定是否渲染可选段,并按需拼接查询参数。

前后端路由同步机制

  • 后端通过 JsRouteExporter 将 declared 路由导出为 name => pattern 映射,并缓存为 manifest 脚本;前端 route.js 读取该映射进行 URL 生成,确保前后端一致。
  • 版本兼容:manifestUrlVersion 结合内容指纹生成带版本的 URL,路由表变更时自动触发浏览器重新拉取,避免缓存不一致。

自定义路由生成器与前端框架集成

  • 对于需要深度集成的前端框架,可复用 JsRouteBuilder 的 url() 方法进行服务端断言,或在客户端封装 route.js 的 route() 函数,统一命名路由生成逻辑。
  • 建议在大型项目中建立路由契约测试,对比前后端生成的 URL 是否一致,提前发现差异。
添加日期:2026-10-05