路由
道格龙(Docusaurus)的路由系统遵循单页应用(SPA)的约定:一个路由,一个组件。在本章节中,我们将首先探讨三个内容插件(文档、博客和页面)内部的路由机制,然后进一步讨论其底层的路由系统。
内容插件中的路由
每个内容插件都提供一个 routeBasePath 选项。它定义了插件将其路由附加到的位置。默认情况下,文档插件将其路由置于 /docs 下;博客插件置于 /blog 下;而页面插件则置于 / 下。你可以这样理解路由结构:
任何路由都会对照这个嵌套的路由配置进行匹配,直到找到一个合适的匹配项。例如,当给定一个路由 /docs/configuration 时,道 格龙(Docusaurus)首先进入 /docs 分支,然后在文档插件创建的子路由中进行搜索。
更改 routeBasePath 可以有效地改变你网站的路由结构。例如,在纯文档模式中,我们提到为文档配置 routeBasePath: '/' 意味着文档插件创建的所有路由都不会有 /docs 前缀,但这并不妨碍你拥有像 /blog 这样由其他插件创建的更多子路由。
接下来,让我们看看这三个插件是如何构建它们各自的"子路由盒子"的。
页面路由
页面路由非常直接:文件路径直接映射到 URL,没有其他自定义方式。更多信息请参阅页面文档。
Markdown 页面使用的组件是 @theme/MDXPage。React 页面则直接用作路由的组件。
博客路由
博客会创建以下路由:
- 博文列表页:
/,/page/2,/page/3...- 该路由可通过
pageBasePath选项进行自定义。 - 组件是
@theme/BlogListPage。
- 该路由可通过
- 博文页:
/2021/11/21/algolia-docsearch-migration,/2021/05/12/announcing-docusaurus-two-beta...- 由每篇 Markdown 博文生成。
- 路由可通过
slugfront matter 进行完全自定义。 - 组件是
@theme/BlogPostPage。
- 标签列表页:
/tags- 该路由可通过
tagsBasePath选项进行自定义。 - 组件是
@theme/BlogTagsListPage。
- 该路由可通过
- 标签页:
/tags/adoption,/tags/beta...- 通过每篇博文 front matter 中定义的标签生成。
- 路由的基地址始终由
tagsBasePath定义,但子路由可通过标签的permalink字段进行自定义。 - 组件是
@theme/BlogTagsPostsPage。
- 归档页:
/archive- 该路由可通过
archiveBasePath选项进行自定义。 - 组件是
@theme/BlogArchivePage。
- 该路由可通过
文档路由
文档是唯一一个创建嵌套路由的插件。在顶层,它会注册版本路径(version paths):/, /next, /2.0.0-beta.13... 这些路径提供了版本上下文,包括布局和侧边栏。这确保了在切换单个文档时,侧边栏的状态得以保留,并且你可以通过导航栏的下拉菜单在不同版本之间切换,同时停留在同一个文档上。使用的组件是 @theme/DocPage。
单个文档会在 DocPage 组件提供了导航栏、页脚、侧边栏等所有元素后的剩余空间中进行渲染。例如,当前这个页面 /docusaurus/advanced/routing,就是由 ./docs/advanced/routing.md 处的 文件生成的。使用的组件是 @theme/DocItem。
文档的 slug front matter 可以自定义路由的最后一部分,但基础 路由始终由插件的 routeBasePath 和版本的 path 定义。
文件路径和 URL 路径
在整个文档中,我们始终努力明确地区分我们是在谈论文件路径还是 URL 路径。内容插件通常将文件路径直接映射到 URL 路径,例如,./docs/advanced/routing.md 将变为 /docs/advanced/routing。然而,通过 slug,你可以使 URL 与文件结构完全解耦。
在 Markdown 中编写链接时,你可能指的是一个文件路径,也可能是一个URL 路径,道格龙(Docusaurus)会使用几种启发式方法来确定。
- 如果路径有
@site前缀,它始终是一个资源文件路径。 - 如果路径有
http(s)://前缀,它始终是一个 URL 路径。 - 如果路径没有扩展名,它是一个 URL 路径。例如,在一个 URL 为
/docs/advanced/routing的页面上的链接[page](../plugins)将链接到/docs/plugins。道格龙(Docusaurus)只会在构建你的网站时(当它知道完整的路由结构时)检测损坏的链接,但不会对文件的存在性做任何假设。这与在 JSX 文件中编写<a href="../plugins">page</a>完全等效。 - 如果路径有
.md(x)扩展名,道格龙(Docusaurus)会尝试将该 Markdown 文件解析为一个 URL,并用 URL 路径替换文件路径。 - 如果路径有任何其他扩展名,道格龙(Docusaurus)会将其视为一个资源并进行打包。
以下目录结构可以帮助你形象化地理解这种文 件 → URL 的映射关系。假设在任何页面中都没有 slug 自定义。
一个示例网站结构
.
├── blog # 博客插件的 routeBasePath 为 '/blog'
│ ├── 2019-05-28-first-blog-post.md # -> /blog/2019/05/28/first-blog-post
│ ├── 2019-05-29-long-blog-post.md # -> /blog/2019/05/29/long-blog-post
│ ├── 2021-08-01-mdx-blog-post.mdx # -> /blog/2021/08/01/mdx-blog-post
│ └── 2021-08-26-welcome
│ ├── docusaurus-plushie-banner.jpeg
│ └── index.md # -> /blog/2021/08/26/welcome
├── docs # 文档插件的 routeBasePath 为 '/docs'; 当前版本的 base path 为 '/'
│ ├── intro.md # -> /docs/intro
│ ├── tutorial-basics
│ │ ├── _category_.json
│ │ ├── congratulations.md # -> /docs/tutorial-basics/congratulations
│ │ └── markdown-features.mdx # -> /docs/tutorial-basics/markdown-features
│ └── tutorial-extras
│ ├── _category_.json
│ ├── manage-docs-versions.md # -> /docs/tutorial-extras/manage-docs-versions
│ └── translate-your-site.md # -> /docs/tutorial-extras/translate-your-site
├── src
│ └── pages # 页面插件的 routeBasePath 为 '/'
│ ├── index.module.css
│ ├── index.tsx # -> /
│ └── markdown-page.md # -> /markdown-page
└── versioned_docs
└── version-1.0.0 # 该版本的 base path 为 '/1.0.0'
├── intro.md # -> /docs/1.0.0/intro
├── tutorial-basics
│ ├── _category_.json
│ ├── congratulations.md # -> /docs/1.0.0/tutorial-basics/congratulations
│ └── markdown-features.mdx # -> /docs/1.0.0/tutorial-basics/markdown-features
└── tutorial-extras
├── _category_.json
├── manage-docs-versions.md # -> /docs/1.0.0/tutorial-extras/manage-docs-versions
└── translate-your-site.md # -> /docs/1.0.0/tutorial-extras/translate-your-site
关于内容插件就说这么多。让我们退一步,总体上谈谈 道格龙(Docusaurus)应用中的路由是如何工作的。
路由如何变成 HTML 文件
因为 道格龙(Docusaurus)是一个服务端渲染框架,所有生成的路由都将在服务端被渲染成静态的 HTML 文件。如果你熟悉像 Apache2 这样的 HTTP 服务器的行为,你就会明白这是如何完成的:当浏览器向路由 /docs/advanced/routing 发送请求时,服务器会将其解释为对 HTML 文件 /docs/advanced/routing/index.html 的请求,并返回该文件。
/docs/advanced/routing 路由可以对应 /docs/advanced/routing/index.html 或 /docs/advanced/routing.html。一些托管服务提供商通过是否存在结尾斜杠来区分它们,并且可能容忍或不容忍另一种形式。更多内容请阅读关于结尾斜杠的指南。
例如,上面目录的构建输出是(忽略其他资源和 JS 包):
上述工作空间的输出
build
├── 404.html # /404/
├── blog
│ ├── archive
│ │ └── index.html # /blog/archive/
│ ├── first-blog-post
│ │ └── index.html # /blog/first-blog-post/
│ ├── index.html # /blog/
│ ├── long-blog-post
│ │ └── index.html # /blog/long-blog-post/
│ ├── mdx-blog-post
│ │ └── index.html # /blog/mdx-blog-post/
│ ├── tags
│ │ ├── docusaurus
│ │ │ └── index.html # /blog/tags/docusaurus/
│ │ ├── hola
│ │ │ └── index.html # /blog/tags/hola/
│ │ └── index.html # /blog/tags/
│ └── welcome
│ └── index.html # /blog/welcome/
├── docs
│ ├── 1.0.0
│ │ ├── intro
│ │ │ └── index.html # /docs/1.0.0/intro/
│ │ ├── tutorial-basics
│ │ │ ├── congratulations
│ │ │ │ └── index.html # /docs/1.0.0/tutorial-basics/congratulations/
│ │ │ └── markdown-features
│ │ │ └── index.html # /docs/1.0.0/tutorial-basics/markdown-features/
│ │ └── tutorial-extras
│ │ ├── manage-docs-versions
│ │ │ └── index.html # /docs/1.0.0/tutorial-extras/manage-docs-versions/
│ │ └── translate-your-site
│ │ └── index.html # /docs/1.0.0/tutorial-extras/translate-your-site/
│ ├── intro
│ │ └── index.html # /docs/1.0.0/intro/
│ ├── tutorial-basics
│ │ ├── congratulations
│ │ │ └── index.html # /docs/tutorial-basics/congratulations/
│ │ └── markdown-features
│ │ └── index.html # /docs/tutorial-basics/markdown-features/
│ └── tutorial-extras
│ ├── manage-docs-versions
│ │ └── index.html # /docs/tutorial-extras/manage-docs-versions/
│ └── translate-your-site
│ └── index.html # /docs/tutorial-extras/translate-your-site/
├── index.html # /
└── markdown-page
└── index.html # /markdown-page/
如果 trailingSlash 设置为 false,构建过程将生成 intro.html 而不是 intro/index.html。
所有 HTML 文件都将使用绝对 URL 来引用其 JS 资源,因此为了能够正确定位到资源,你必须配置 baseUrl 字段。请注意,baseUrl 不会影响生成的包的文件结构:基准 URL 是在 道格龙(Docusaurus)路由系统之上的一个层级。你可以将 url 和 baseUrl 的组合视为你的 道格龙(Docusaurus)网站的实际位置。
例如,生成的 HTML 将包含像 <link rel="preload" href="/assets/js/runtime~main.7ed5108a.js" as="script"> 这样的链接。因为绝对 URL 是从主机名解析的,如果包被放置在路径 https://example.com/base/ 下,该链接将指向 https://example.com/assets/js/runtime~main.7ed5108a.js,而这个文件是不存在的。通过将 /base/ 指定为基准 URL,链接将正确指向 /base/assets/js/runtime~main.7ed5108a.js。
本地化的网站也会将地区设置作为基准 URL 的一部分。例如,https://docusaurus.io/zh-CN/docs/advanced/routing/ 的基准 URL 是 /zh-CN/。
生成和访问路由
addRoute 生命周期操作用于生成路由。它将一条路由配置注册到路由树中,提供一个路由、一个组件以及该组件所需的 props。props 和组件都以路径的形式提供,以便打包工具可以 require 它们,因为正如架构概览中所解释的,服务端和客户端之间仅通过临时文件进行通信。
所有路由都汇总在 .docusaurus/routes.js 文件中,你可以通过调试插件的路由面板来查看。
在客户端,我们提供 @docusaurus/router 来访问页面的路由。@docusaurus/router 是对 react-router-dom 包的再导出。例如,你可以使用 useLocation 来获取当前页面的 location,使用 useHistory 来访问 history 对象。(它们与浏览器 API 不同,尽管功能相似。具体 API 请参考 React Router 的文档。)
这个 API 是服务端渲染安全的,与仅限浏览器的 window.location 不同。
import React from 'react';
import {useLocation} from '@docusaurus/router';
export function PageRoute() {
// React router 提供了当前组件的路由,即使在服务端渲染(SSR)中也是如此
const location = useLocation();
return (
<span>
我们当前在 <code>{location.pathname}</code>
</span>
);
}
/docusaurus/advanced/routing