客户端架构
主题别名
主题会提供一组组件,比如 Navbar
、Layout
、Footer
,用于渲染插件传递下来的数据。 Docusaurus 和用户会通过 @theme
这个 Webpack 别名导入并使用这些组件:
import Navbar from '@theme/Navbar';
@theme
别名可能会指向若干目录,按照以下优先级排序:
- 用户的
website/src/theme
目录。这个目录是一个具有最高优先级的特殊目录。 - Docusaurus 主题包的
theme
目录。 - Docusaurus core 提供的原始组件(通常用不到)。
这被称为_分层架构_:一个较高优先级的层提供的组件会覆盖一个较低优先级的层,从而使 swizzle 成为可能。 假设有以下文件结构:
website
├── node_modules
│ └── @docusaurus/theme-classic
│ └── theme
│ └── Navbar.js
└── src
└── theme
└── Navbar.js
每当导入 @theme/Navbar
时,website/src/theme/Navbar.js
都会被优先载入。 这被称为 swizzle。 如果你熟悉 Objective C,你会知道在 Objective C 中,一个函数的实现可以在运行时被替换掉。在这里,更改 @theme/Navbar
别名的指向是完全相同的概念!
我们已经讨论过 src/theme
中的「用户个性化主题」是如何通过 @theme-original
别名复用原始主题组件的。 一个主题包也可以通过 @theme-init
别名从另一个主题导入组件,并将其二次封装。
下面的例子就用了这个功能来封装默认的 CodeBlock
主题组件,并提供 react-live
的实时演示功能。
import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';
export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}
要获得更多详细信息,可以浏览 @docusaurus/theme-live-codeblock
的代码。
除非你想要发布一个可复用的「主题增强器」(比如 @docusaurus/theme-live-codeblock
),否则你一般不需要 @theme-init
。
要理解这些别名可能有点困难。 我们来想象一个超级复杂的场景:三个主题,以及网站本身,都尝试定义同一个组件。 Docusaurus 内部会把这些主题加载成一个「栈」。
+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` 永远指向最顶部
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` 指向最顶部的非 swizzle 组件
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` 永远指向最底部
+-------------------------------------------------+
这个「栈」里的组件会按照预设插件 > 预设主题 > 独立插件 > 独立主题 > 网站的顺序被推入,所以 website/src/theme
中存储的 swizzle 后的组件永远处于顶部,因为它最后被推入。
@theme/*
始终指向最顶端的组件——所以当 CodeBlock
被swizzle 之后,所有其他导入 @theme/CodeBlock
的组件都会收到 swizzle 之后的版本。
@theme-original/*
始终指向最顶端的非 swizzle 组件。 这就是为什么你可以在 swizzle 组件中导入 @theme-origal/codeBlock
——它指向了「组件栈」中的下一个,由主题提供的组件。 插件作者不能使用这个别名,因为你的组件可能是最顶端的组件,从而导致自己导入自己的情况。
@theme-init/*
总是指向最底端的组件——通常这是首次提供此组件的主题或插件。 试图二次封装 CodeBlock
的独立插件或主题可以安全地使用 @theme-init/codeBlock
来获取其最初的版本。 网站创建者通常不会使用此别名,因为你大概率想要复用_最顶端_而不是_最底端_的组件。 @theme-init/CodeBlock
别名还有可能根本不存在——Docusaurus 只会在当它的指向和 @theme-origal/Code
不同(也就是当组件被多个主题提供)时创建它。 我们不会浪费别名的!
客户端模块
客户端模块是网站包的一部分,就像主题组件一样。 然而,它们通常会引入副作用。 客户端 模块是任何可以被 Webpack import
的东西——比如 CSS、JS,等等。 JS 脚本通常在全局环境中工作,比如注册事件监听器,创建全局变量……
这些模块是在 React 甚至还没开始渲染 UI 之前就导入的。
// 它在底层是如何工作的
import '@generated/client-modules';
Plugins and sites can both declare client modules, through getClientModules
and siteConfig.clientModules
, respectively.
Client modules are called during server-side rendering as well, so remember to check the execution environment before accessing client-side globals.
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
if (ExecutionEnvironment.canUseDOM) {
// 网站在浏览器中开始加载时,立即注册一个全局事件监听器
window.addEventListener('keydown', (e) => {
if (e.code === 'Period') {
location.assign(location.href.replace('.com', '.dev'));
}
});
}
CSS stylesheets imported as client modules are global.
/* 这个样式表是全局的。 */
.globalSelector {
color: red;
}