feat(core): rework swizzle CLI (#6243)

Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2022-02-25 21:13:15 +08:00 committed by GitHub
parent d43066f6f1
commit 39b66d82ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 3633 additions and 585 deletions

View file

@ -1,5 +0,0 @@
:::caution
We discourage swizzling of components during the Docusaurus 2 beta phase. The theme components APIs are likely to evolve and have breaking changes. If possible, stick with the default appearance for now.
:::

View file

@ -0,0 +1,77 @@
# Client architecture
## Theme aliases {#theme-aliases}
A theme works by exporting a set of components, e.g. `Navbar`, `Layout`, `Footer`, to render the data passed down from plugins. Docusaurus and users use these components by importing them using the `@theme` webpack alias:
```js
import Navbar from '@theme/Navbar';
```
The alias `@theme` can refer to a few directories, in the following priority:
1. A user's `website/src/theme` directory, which is a special directory that has the higher precedence.
2. A Docusaurus theme package's `theme` directory.
3. Fallback components provided by Docusaurus core (usually not needed).
This is called a _layered architecture_: a higher-priority layer providing the component would shadow a lower-priority layer, making swizzling possible. Given the following structure:
```
website
├── node_modules
│ └── @docusaurus/theme-classic
│ └── theme
│ └── Navbar.js
└── src
└── theme
└── Navbar.js
```
`website/src/theme/Navbar.js` takes precedence whenever `@theme/Navbar` is imported. This behavior is called component swizzling. If you are familiar with Objective C where a function's implementation can be swapped during runtime, it's the exact same concept here with changing the target `@theme/Navbar` is pointing to!
We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](#wrapping) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import.
Here's an example of using this feature to enhance the default theme `CodeBlock` component with a `react-live` playground feature.
```js
import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';
export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}
```
Check the code of `@docusaurus/theme-live-codeblock` for details.
:::caution
Unless you want to publish a re-usable "theme enhancer" (like `@docusaurus/theme-live-codeblock`), you likely don't need `@theme-init`.
:::
It can be quite hard to wrap your mind around these aliases. Let's imagine the following case with a super convoluted setup with three themes/plugins and the site itself all trying to define the same component. Internally, Docusaurus loads these themes as a "stack".
```text
+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` always points to the top
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` points to the topmost non-swizzled component
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` always points to the bottom
+-------------------------------------------------+
```
The components in this "stack" are pushed in the order of `preset plugins > preset themes > plugins > themes > site`, so the swizzled component in `website/src/theme` always comes out on top because it's loaded last.
`@theme/*` always points to the topmost component—when `CodeBlock` is swizzled, all other components requesting `@theme/CodeBlock` receive the swizzled version.
`@theme-original/*` always points to the topmost non-swizzled component. That's why you can import `@theme-original/CodeBlock` in the swizzled component—it points to the next one in the "component stack", a theme-provided one. Plugin authors should not try to use this because your component could be the topmost component and cause a self-import.
`@theme-init/*` always points to the bottommost component—usually, this comes from the theme or plugin that first provides this component. Individual plugins / themes trying to enhance code block can safely use `@theme-init/CodeBlock` to get its basic version. Site creators should generally not use this because you likely want to enhance the _topmost_ instead of the _bottommost_ component. It's also possible that the `@theme-init/CodeBlock` alias does not exist at all—Docusaurus only creates it when it points to a different one from `@theme-original/CodeBlock`, i.e. when it's provided by more than one theme. We don't waste aliases!

View file

@ -1,195 +0,0 @@
---
description: Customize your site's appearance through creating your own theme components
---
# Swizzling
In this section, we will introduce how customization of layout is done in Docusaurus.
> Déja vu...?
This section is similar to [Styling and Layout](../styling-layout.md), but this time, we are going to write more code and go deeper into the internals instead of playing with stylesheets. We will talk about a central concept in Docusaurus customization: **swizzling**, from how to swizzle, to how it works under the hood.
We know you are busy, so we will start with the "how" before going into the "why".
## Swizzling {#swizzling}
```mdx-code-block
import SwizzleWarning from "../_partials/swizzleWarning.mdx"
<SwizzleWarning/>
```
Docusaurus Themes' components are designed to be replaceable. The replacing is called "swizzle". In Objective C, method swizzling is the process of changing the implementation of an existing selector (method). **In the context of a website, component swizzling means providing an alternative component that takes precedence over the component provided by the theme.** (To gain a deeper understanding of this, you have to understand [how theme components are resolved](#theme-aliases)). To help you get started, we created a command called `docusaurus swizzle`.
### Ejecting theme components {#ejecting-theme-components}
To eject a component provided by the theme, run the following command in your doc site:
```bash npm2yarn
npm run swizzle [theme name] [component name]
```
As an example, to swizzle the `<Footer />` component in `@docusaurus/theme-classic` for your site, run:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic Footer
```
This will copy the current `<Footer />` component used by Docusaurus to an `src/theme/Footer` directory under the root of your site, which is where Docusaurus will look for swizzled components. Docusaurus will then use the swizzled component in place of the original one from the theme.
:::note
You need to restart your webpack dev server in order for Docusaurus to know about the new component.
:::
If you run `swizzle` without `component name` or `theme name`, the command will give you a list to choose from. To only list available components, run with the `--list` option:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic --list
```
"Swizzle" is a central concept in Docusaurus, and is a natural product of our [layered theme architecture](#theme-aliases). Note that the command `docusaurus swizzle` is only an automated way to help you swizzle the component: you can still do it manually by creating the `src/theme/Footer.js` file, and Docusaurus will pick that one up when resolving theme components. There's no internal magic behind this command!
### Wrapping theme components {#wrapping-theme-components}
Ejecting a component is risky. It means you have to maintain an almost duplicate copy of the original theme component. Also, it's likely that we will change internal implementations in future versions and break your component, even if you never touched that part of the code.
Very often, you don't need to re-implement a component from scratch, but only to render additional items before or after it, or conditionally call some other logic. In this case, you are still going to swizzle the component—but not making a self-sustained one. Instead, you can delegate most of the logic and layout to the original theme component. The `@theme-original` alias allows you to import the original theme component and wrap it as a higher-order component.
Here is an example to display some text just above the footer, with minimal code duplication.
```js title="src/theme/Footer.js"
import OriginalFooter from '@theme-original/Footer';
import React from 'react';
export default function Footer(props) {
return (
<>
<div>Before footer</div>
<OriginalFooter {...props} />
</>
);
}
```
Should you be wondering why we have to use `'@theme-original/Footer'` instead of `'@theme/Footer'`, a short explanation is that once you have the swizzled component, the `'@theme/Footer'` alias will now point to your swizzled component, and thus cause a self-import. For a more in-depth explanation, see [theme aliases](#theme-aliases).
## Which component should I swizzle? {#which-component-should-i-swizzle}
Currently, `theme-classic` has about 100 components[^source]! If you want to customize a part of your site's layout, which component should you choose?
[^source]: https://github.com/facebook/docusaurus/tree/main/packages/docusaurus-theme-classic/src/theme
You can follow the following steps to locate the component to swizzle:
1. **Search.** Our components are semantically named, so you should be able to infer its function from the name. The swizzle CLI allows you to enter part of a component name to narrow down the available choices. For example, if you run `yarn swizzle @docusaurus/theme-classic`, and enter `Doc`, only the docs-related components will be listed.
2. **Start with a higher-level component.** Components form a tree with some components importing others. Every route will be associated with one top-level component that the route will render (most of them listed in [Routing in content plugins](routing.md#routing-in-content-plugins)). For example, all blog post pages have `@theme/BlogPostPage` as the topmost component. You can start with swizzling this component, and then go down the component tree to locate the component that renders just what you are targeting. Don't forget to unswizzle the rest by deleting the files after you've found the correct one, so you don't maintain too many components.
3. **Read the source code and use search wisely.** Topmost components are registered by the plugin with `addRoute`, so you can search for `addRoute` and see which component the plugin references. Afterwards, read the code of all components that this component references.
4. **Ask.** If you still have no idea which component to swizzle to achieve the desired effect, you can reach out for help in one of our [support channels](/community/support).
### Wrapping your site with `<Root>` {#wrapper-your-site-with-root}
The `<Root>` component is one that you probably won't spot. Every component provided by `theme-classic` is ultimately only rendered on certain routes, and will be unmounted during route transition; however, the `<Root>` theme component is rendered at the very top of the Docusaurus SPA, above the router and the theme `<Layout>`, and will **never unmount**, allowing you to wrap your site with additional logic like global state. You can swizzle it by creating a file at `src/theme/Root.js`:
```js title="website/src/theme/Root.js"
import React from 'react';
// Default implementation, that you can customize
function Root({children}) {
return <>{children}</>;
}
export default Root;
```
:::tip
Use this component to render React Context providers and global stateful logic.
:::
## Do I need to swizzle? {#do-i-need-to-swizzle}
Swizzling ultimately means you have to maintain part of the code directly used to build your site, and you have to interact with Docusaurus internal APIs. If you can, think about the following alternatives when customizing your site:
1. **Use CSS.** CSS rules and selectors can often help you achieve a decent degree of customization. Refer to [styling and layout](../styling-layout.md) for more details.
2. **Use translations.** It may sound surprising, but translations are ultimately just a way to customize the text labels. For example, if your site's default language is `en`, you can still run `yarn write-translations -l en` and edit the `code.json` emitted. Refer to [i18n tutorial](../i18n/i18n-tutorial.md) for more details.
3. **The smaller, the better.** If swizzling is inevitable, prefer to swizzle only the relevant part and maintain as little code on your own as possible. Swizzling a small component often means less risk of breaking during upgrade. [Wrapping](#wrapping-theme-components) is also a far safer alternative to [ejecting](#ejecting-theme-components).
## Theme aliases {#theme-aliases}
A theme works by exporting a set of components, e.g. `Navbar`, `Layout`, `Footer`, to render the data passed down from plugins. Docusaurus and users use these components by importing them using the `@theme` webpack alias:
```js
import Navbar from '@theme/Navbar';
```
The alias `@theme` can refer to a few directories, in the following priority:
1. A user's `website/src/theme` directory, which is a special directory that has the higher precedence.
2. A Docusaurus theme package's `theme` directory.
3. Fallback components provided by Docusaurus core (usually not needed).
This is called a _layered architecture_: a higher-priority layer providing the component would shadow a lower-priority layer, making swizzling possible. Given the following structure:
```
website
├── node_modules
│ └── @docusaurus/theme-classic
│ └── theme
│ └── Navbar.js
└── src
└── theme
└── Navbar.js
```
`website/src/theme/Navbar.js` takes precedence whenever `@theme/Navbar` is imported. This behavior is called component swizzling. If you are familiar with Objective C where a function's implementation can be swapped during runtime, it's the exact same concept here with changing the target `@theme/Navbar` is pointing to!
We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](#wrapping-theme-components) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import.
Here's an example of using this feature to enhance the default theme `CodeBlock` component with a `react-live` playground feature.
```js
import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';
export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}
```
Check the code of `@docusaurus/theme-live-codeblock` for details.
:::caution
Unless you want to publish a re-usable "theme enhancer" (like `@docusaurus/theme-live-codeblock`), you likely don't need `@theme-init`.
:::
It can be quite hard to wrap your mind around these aliases. Let's imagine the following case with a super convoluted setup with three themes/plugins and the site itself all trying to define the same component. Internally, Docusaurus loads these themes as a "stack".
```text
+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` always points to the top
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` points to the topmost non-swizzled component
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` always points to the bottom
+-------------------------------------------------+
```
The components in this "stack" are pushed in the order of `preset plugins > preset themes > plugins > themes > site`, so the swizzled component in `website/src/theme` always comes out on top because it's loaded last.
`@theme/*` always points to the topmost component—when `CodeBlock` is swizzled, all other components requesting `@theme/CodeBlock` receive the swizzled version.
`@theme-original/*` always points to the topmost non-swizzled component. That's why you can import `@theme-original/CodeBlock` in the swizzled component—it points to the next one in the "component stack", a theme-provided one. Plugin authors should not try to use this because your component could be the topmost component and cause a self-import.
`@theme-init/*` always points to the bottommost component—usually, this comes from the theme or plugin that first provides this component. Individual plugins / themes trying to enhance code block can safely use `@theme-init/CodeBlock` to get its basic version. Site creators should generally not use this because you likely want to enhance the _topmost_ instead of the _bottommost_ component. It's also possible that the `@theme-init/CodeBlock` alias does not exist at all—Docusaurus only creates it when it points to a different one from `@theme-original/CodeBlock`, i.e. when it's provided by more than one theme. We don't waste aliases!

View file

@ -85,44 +85,37 @@ For advanced minification of CSS bundle, we use the [advanced cssnano preset](ht
:::
### `docusaurus swizzle [siteDir]` {#docusaurus-swizzle-sitedir}
### `docusaurus swizzle [themeName] [componentName] [siteDir]` {#docusaurus-swizzle}
```mdx-code-block
import SwizzleWarning from "./_partials/swizzleWarning.mdx"
<SwizzleWarning/>
```
Change any Docusaurus theme components to your liking with `npm run swizzle`.
[Swizzle](./swizzling.md) a theme component to customize it.
```bash npm2yarn
npm run swizzle [themeName] [componentName] [siteDir]
# Example (leaving out the siteDir to indicate this directory)
npm run swizzle @docusaurus/theme-classic DocSidebar
npm run swizzle @docusaurus/theme-classic Footer -- --eject
```
Running the command will copy the relevant theme files to your site folder. You may then make any changes to it and Docusaurus will use it instead of the one provided from the theme.
The swizzle CLI is interactive and will guide you through the whole [swizzle process](./swizzling.md).
`npm run swizzle` without `themeName` lists all the themes available for swizzling; similarly, `npm run swizzle [themeName]` without `componentName` lists all the components available for swizzling.
#### Options {#options-swizzle}
#### Options {#options-2}
| Name | Description |
| ------------------ | -------------------------------------- |
| `themeName` | The name of the theme you are using. |
| `swizzleComponent` | The name of the component to swizzle. |
| `--danger` | Allow swizzling of unstable components |
| `--typescript` | Swizzle TypeScript components |
| Name | Description |
| --------------- | ---------------------------------------------------- |
| `themeName` | The name of the theme to swizzle from. |
| `componentName` | The name of the theme component to swizzle. |
| `--list` | Display components available for swizzling |
| `--eject` | [Eject](./swizzling.md#ejecting) the theme component |
| `--wrap` | [Wrap](./swizzling.md#wrapping) the theme component |
| `--danger` | Allow immediate swizzling of unsafe components |
| `--typescript` | Swizzle the TypeScript variant component |
:::caution
Unstable Components: components that have a higher risk of breaking changes due to internal refactorings.
Unsafe components have a higher risk of breaking changes due to internal refactorings.
:::
To learn more about swizzling, see the [swizzling guide](./advanced/swizzling.md).
### `docusaurus deploy [siteDir]` {#docusaurus-deploy-sitedir}
Deploys your site with [GitHub Pages](https://pages.github.com/). Check out the docs on [deployment](deployment.mdx#deploying-to-github-pages) for more details.

View file

@ -492,7 +492,7 @@ You'll have to migrate your sidebar if it contains category type. Rename `subcat
### Footer {#footer}
`website/core/Footer.js` is no longer needed. If you want to modify the default footer provided by Docusaurus, [swizzle](../advanced/swizzling.md#swizzling) it:
`website/core/Footer.js` is no longer needed. If you want to modify the default footer provided by Docusaurus, [swizzle](../swizzling.md) it:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic Footer

View file

@ -8,7 +8,7 @@ import ColorGenerator from '@site/src/components/ColorGenerator';
:::tip
This section is focused on styling through stylesheets. If you find yourself needing to update the DOM structure, you can refer to [swizzling](./advanced/swizzling.md#swizzling).
This section is focused on styling through stylesheets. For more advanced customizations (DOM structure, React code...), refer to the [swizzling guide](./swizzling.md).
:::
@ -60,13 +60,19 @@ function MyComponent() {
If you want to add CSS to any element, you can open the DevTools in your browser to inspect its class names. Class names come in several kinds:
- **Theme class names**. These class names are listed exhaustively in [the next subsection](#theme-class-names). They don't have any default properties. You should always prioritize targeting stable class names in your custom CSS.
- **Infima class names**. These class names usually follow the [BEM convention](http://getbem.com/naming/) of `block__element--modifier`. They are usually stable but are still considered implementation details, so you should generally avoid targeting them. However, you can [modify Infima CSS variables](#styling-your-site-with-infima).
- **Theme class names**. These class names are listed exhaustively in [the next subsection](#theme-class-names). They don't have any default properties. You should always prioritize targeting those stable class names in your custom CSS.
- **Infima class names**. These class names are found in the classic theme and usually follow the [BEM convention](http://getbem.com/naming/) of `block__element--modifier`. They are usually stable but are still considered implementation details, so you should generally avoid targeting them. However, you can [modify Infima CSS variables](#styling-your-site-with-infima).
- **CSS module class names**. These class names have a hash in production (`codeBlockContainer_RIuc`) and are appended with a long file path in development. They are considered implementation details and you should almost always avoid targeting them in your custom CSS. If you must, you can use an [attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) (`[class*='codeBlockContainer']`) that ignores the hash.
### Theme Class Names {#theme-class-names}
We provide some predefined CSS class names for global layout styling. These names are theme-agnostic and meant to be targeted by custom CSS.
We provide some stable CSS class names for robust and maintainable global layout styling. These names are theme-agnostic and meant to be targeted by custom CSS.
:::tip
If you can't find a way to create a robust CSS selector, please [report your customization use-case](https://github.com/facebook/docusaurus/discussions/5468) and we will consider adding new class names.
:::
<details>

324
website/docs/swizzling.md Normal file
View file

@ -0,0 +1,324 @@
---
description: Customize your site's appearance through creating your own theme components
---
# Swizzling
In this section, we will introduce how customization of layout is done in Docusaurus.
> Déja vu...?
This section is similar to [Styling and Layout](./styling-layout.md), but this time, we will write actual React code instead of playing with stylesheets. We will talk about a central concept in Docusaurus: **swizzling**, which allows **deeper site customizations**.
In practice, swizzling permits to **swap a theme component with your own implementation**, and it comes in 2 patterns:
- [**Ejecting**](#ejecting): creates a **copy** of the original theme component, which you can fully **customize**
- [**Wrapping**](#wrapping): creates a **wrapper** around the original theme component, which you can **enhance**
<details>
<summary>Why is it called swizzling?</summary>
**The name comes from Objective-C and Swift-UI**: [method swizzling](https://pspdfkit.com/blog/2019/swizzling-in-swift/) is the process of changing the implementation of an existing selector (method).
**For Docusaurus, component swizzling means providing an alternative component that takes precedence over the component provided by the theme.**
You can think of it as [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch) for React components, enabling you to override the default implementation. Gatsby has a similar concept called [theme shadowing](https://www.gatsbyjs.com/docs/how-to/plugins-and-themes/shadowing/).
To gain a deeper understanding of this, you have to understand [how theme components are resolved](./advanced/client.md#theme-aliases).
</details>
## Swizzling Process
### Overview
Docusaurus provides an convenient **interactive CLI** to swizzle components. You generally only need to remember the following command:
```bash npm2yarn
npm run swizzle
```
It will generate a new component your `src/theme` directory, which should look like this example:
````mdx-code-block
<Tabs>
<TabItem value="Ejecting">
```jsx title="src/theme/SomeComponent.js"
import React from 'react';
export default function SomeComponent(props) {
// You can fully customize this implementation
// including changing the JSX, CSS and React hooks
return (
<div className="some-class">
<h1>Some Component</h1>
<p>Some component implementation details</p>
</div>
);
}
```
</TabItem>
<TabItem value="Wrapping">
```jsx title="src/theme/SomeComponent.js"
import React from 'react';
import SomeComponent from '@theme-original/SomeComponent';
export default function SomeComponentWrapper(props) {
// You can enhance the original component,
// including adding extra props or JSX elements around it
return (
<>
<SomeComponent {...props} />
</>
);
}
```
</TabItem>
</Tabs>
````
To get an overview of all the themes and components available to swizzle, run:
```bash npm2yarn
npm run swizzle -- --list
```
Use `--help` to see all available CLI options, or refer to the reference [swizzle CLI documentation](./cli.md#docusaurus-swizzle).
:::note
After swizzling a component, **restart your dev server** in order for Docusaurus to know about the new component.
:::
:::warning Prefer staying on the safe side
Be sure to understand [which components are **safe to swizzle**](#what-is-safe-to-swizzle). Some components are **internal implementation details** of a theme.
:::
:::info
`docusaurus swizzle` is only an automated way to help you swizzle the component. You can also create the `src/theme/SomeComponent.js` file manually, and Docusaurus will [resolve it](./advanced/client.md#theme-aliases). There's no internal magic behind this command!
:::
### Ejecting {#ejecting}
Ejecting a theme component is the process of **creating a copy** of the original theme component, which you can **fully customize and override**.
To eject a theme component, use the swizzle CLI interactively, or with the `--eject` option:
```bash npm2yarn
npm run swizzle [theme name] [component name] -- --eject
```
An example:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic Footer -- --eject
```
This will copy the current `<Footer />` component's implementation to your site's `src/theme` directory. Docusaurus will now use this `<Footer>` component copy instead of the original one. You are now free to completely re-implement the `<Footer>` component.
```jsx title="src/theme/SomeComponent.js"
import React from 'react';
export default function Footer(props) {
return (
<footer>
<h1>This is my custom site footer</h1>
<p>And it is very different from the original</p>
</footer>
);
}
```
:::caution
Ejecting an [**unsafe**](#what-is-safe-to-swizzle) component can sometimes lead to copying a large amount of internal code, which you now have to maintain yourself. It can make Docusaurus upgrades more difficult, as you will need to migrate your customizations if the props received or internal theme APIs used have changed.
**Prefer [wrapping](#wrapping) whenever possible**: the amount of code to maintain is smaller.
:::
:::tip Re-swizzling
To keep ejected components up-to-date after a Docusaurus upgrade, re-run the eject command and compare the changes with `git diff`. You are also recommended to write a brief comment at the top of the file explaining what changes you have made, so that you could more easily re-apply your changes after re-ejection.
:::
### Wrapping {#wrapping}
Wrapping a theme component is the process of **creating a wrapper** around the original theme component, which you can **enhance**.
To wrap a theme component, use the swizzle CLI interactively, or with the `--wrap` option:
```bash npm2yarn
npm run swizzle [theme name] [component name] -- --wrap
```
An example:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic Footer -- --wrap
```
This will create a wrapper in your site's `src/theme` directory. Docusaurus will now use the `<FooterWrapper>` component instead of the original one. You can now add customizations around the original component.
```jsx title="src/theme/SomeComponent.js"
import React from 'react';
import Footer from '@theme-original/Footer';
export default function FooterWrapper(props) {
return (
<>
<section>
<h2>Extra section</h2>
<p>This is an extra section that appears above the original footer</p>
</section>
<Footer {...props} />
</>
);
}
```
<details>
<summary>What is this <code>@theme-original</code> thing?</summary>
Docusaurus uses [theme aliases](./advanced/client.md#theme-aliases) to resolve the theme components to use. The newly created wrapper takes the `@theme/SomeComponent` alias. `@theme-original/SomeComponent` permits to import original component that the wrapper shadows without creating an infinite import loop where the wrapper imports itself.
</details>
:::tip
Wrapping a theme is a great way to **add extra components around existing one** without [ejecting](#ejecting) it. For example, you can easily add a custom comment system under each blog post:
```jsx title="src/theme/BlogPostItem.js"
import React from 'react';
import BlogPostItem from '@theme-original/BlogPostItem';
import MyCustomCommentSystem from '@site/src/MyCustomCommentSystem';
export default function BlogPostItemWrapper(props) {
return (
<>
<BlogPostItem {...props} />
<MyCustomCommentSystem />
</>
);
}
```
:::
## What is safe to swizzle? {#what-is-safe-to-swizzle}
> With great power comes great responsibility
Some theme components are **internal implementation details** of a theme. Docusaurus allows you to swizzle them, but it **might be risky**.
<details>
<summary>Why is it risky?</summary>
Theme authors (including us) might have to update their theme over time: changing the component props, name, file system location, types... For example, consider a component that receives two props `name` and `age`, but after a refactor, it now receives a `person` prop with the above two properties. Your component, which still expects these two props, will render `undefined` instead.
Moreover, internal components may simply disappear. If a component is called `Sidebar` and it's later renamed to `DocSidebar`, your swizzled component will be completely ignored.
**Theme components marked as unsafe may change in a backward-incompatible way between theme minor versions.** When upgrading a theme (or Docusaurus), your customizations might **behave unexpectedly**, and can even **break your site**.
</details>
For each theme component, the swizzle CLI will indicate **3 different levels of safety** declared by theme authors:
- **Safe**: this component is safe to be swizzled, its public API is considered stable, and no breaking changes should happen within a theme **major version**
- **Unsafe**: this component is a theme implementation detail, not safe to be swizzled, and breaking changes might happen withing a theme **minor version**
- **Forbidden**: the swizzle CLI will prevent you from swizzling this component, because it is not designed to be swizzled at all
:::note
Some components might be safe to wrap, but not safe to eject.
:::
:::info
Don't be too **afraid to swizzle unsafe components**: just keep in mind that **breaking changes** might happen, and you might need to upgrade your customizations manually on minor version upgrades.
:::
:::note Report your use-case
If you have a **strong use-case for swizzling an unsafe component**, please [**report it here**](https://github.com/facebook/docusaurus/discussions/5468) and we will work together to find a solution to make it safe.
:::
## Which component should I swizzle? {#which-component-should-i-swizzle}
It is not always clear which component you should swizzle exactly to achieve the desired result. `@docusaurus/theme-classic`, which provides most of the theme components, has about [100 components](https://github.com/facebook/docusaurus/tree/main/packages/docusaurus-theme-classic/src/theme)!
:::tip
To print an overview of all the `@docusaurus/theme-classic` components:
```bash npm2yarn
npm run swizzle @docusaurus/theme-classic -- --list
```
:::
You can follow these steps to locate the appropriate component to swizzle:
1. **Component description.** Some components provide a short description, which is a good way to find the right one.
2. **Component name.** Official theme components are semantically named, so you should be able to infer its function from the name. The swizzle CLI allows you to enter part of a component name to narrow down the available choices. For example, if you run `yarn swizzle @docusaurus/theme-classic`, and enter `Doc`, only the docs-related components will be listed.
3. **Start with a higher-level component.** Components form a tree with some components importing others. Every route will be associated with one top-level component that the route will render (most of them listed in [Routing in content plugins](./advanced/routing.md#routing-in-content-plugins)). For example, all blog post pages have `@theme/BlogPostPage` as the topmost component. You can start with swizzling this component, and then go down the component tree to locate the component that renders just what you are targeting. Don't forget to unswizzle the rest by deleting the files after you've found the correct one, so you don't maintain too many components.
4. **Read the [theme source code](https://github.com/facebook/docusaurus/tree/main/packages/docusaurus-theme-classic/src/theme)** and use search wisely.
:::tip Just ask!
If you still have no idea which component to swizzle to achieve the desired effect, you can reach out for help in one of our [support channels](/community/support).
We also want to understand better your fanciest customization use-cases, so please [**report them**](https://github.com/facebook/docusaurus/discussions/5468).
:::
## Do I need to swizzle? {#do-i-need-to-swizzle}
Swizzling ultimately means you have to maintain some additional React code that interact with Docusaurus internal APIs. If you can, think about the following alternatives when customizing your site:
1. **Use CSS.** CSS rules and selectors can often help you achieve a decent degree of customization. Refer to [styling and layout](./styling-layout.md) for more details.
2. **Use translations.** It may sound surprising, but translations are ultimately just a way to customize the text labels. For example, if your site's default language is `en`, you can still run `yarn write-translations -l en` and edit the `code.json` emitted. Refer to the [i18n tutorial](./i18n/i18n-tutorial.md) for more details.
:::tip
**The smaller, the better.** If swizzling is inevitable, prefer to swizzle only the relevant part and maintain as little code on your own as possible. Swizzling a small component often means less risk of **breaking changes** during upgrade.
[Wrapping](#wrapping) is also a far safer alternative to [ejecting](#ejecting).
:::
## Wrapping your site with `<Root>` {#wrapper-your-site-with-root}
The `<Root>` component is rendered at the **very top** of the React tree, above the theme `<Layout>`, and **never unmounts**. It is the perfect place to add stateful logic that should not be re-initialized across navigations (user authentication status, shopping card state...).
Swizzle it **manually** by creating a file at `src/theme/Root.js`:
```js title="src/theme/Root.js"
import React from 'react';
// Default implementation, that you can customize
export default function Root({children}) {
return <>{children}</>;
}
```
:::tip
Use this component to render React Context providers.
:::

View file

@ -114,7 +114,7 @@ At most one plugin instance can be the "default plugin instance", by omitting th
## Using themes {#using-themes}
Themes are loaded in the exact same way as plugins—the line between them is blurry. From the consumer perspective, the `themes` and `plugins` entries are interchangeable when installing and configuring a plugin. The only nuance is that themes are loaded after plugins, and it's possible for [a theme to override a plugin's default theme components](./advanced/swizzling.md#theme-aliases).
Themes are loaded in the exact same way as plugins—the line between them is blurry. From the consumer perspective, the `themes` and `plugins` entries are interchangeable when installing and configuring a plugin. The only nuance is that themes are loaded after plugins, and it's possible for [a theme to override a plugin's default theme components](./swizzling.md#theme-aliases).
:::tip