fix(core): do not apply theme-init alias to user component (#5983)

Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2021-12-04 01:59:45 +08:00 committed by GitHub
parent b366ba5603
commit fcaa94695d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 76 additions and 22 deletions

View file

@ -14,7 +14,7 @@ describe('loadThemeAliases', () => {
const theme1Path = path.join(fixtures, 'theme-1');
const theme2Path = path.join(fixtures, 'theme-2');
const alias = loadThemeAliases([theme1Path, theme2Path]);
const alias = loadThemeAliases([theme1Path, theme2Path], []);
// Testing entries, because order matters!
expect(Object.entries(alias)).toEqual(

View file

@ -12,34 +12,31 @@ import themeAlias, {sortAliases} from './alias';
const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback');
function buildThemeAliases(
themeAliases: ThemeAliases,
aliases: ThemeAliases = {},
): ThemeAliases {
Object.keys(themeAliases).forEach((aliasKey) => {
if (aliasKey in aliases) {
const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1);
aliases[`@theme-init/${componentName}`] = aliases[aliasKey];
}
aliases[aliasKey] = themeAliases[aliasKey];
});
return aliases;
}
export function loadThemeAliases(
themePaths: string[],
userThemePaths: string[] = [],
userThemePaths: string[],
): ThemeAliases {
let aliases = {}; // TODO refactor, inelegant side-effect
const aliases: ThemeAliases = {};
themePaths.forEach((themePath) => {
const themeAliases = themeAlias(themePath, true);
aliases = {...aliases, ...buildThemeAliases(themeAliases, aliases)};
Object.keys(themeAliases).forEach((aliasKey) => {
// If this alias shadows a previous one, use @theme-init to preserve the initial one.
// @theme-init is only applied once: to the initial theme that provided this component
if (aliasKey in aliases) {
const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1);
const initAlias = `@theme-init/${componentName}`;
if (!(initAlias in aliases)) {
aliases[initAlias] = aliases[aliasKey];
}
}
aliases[aliasKey] = themeAliases[aliasKey];
});
});
userThemePaths.forEach((themePath) => {
const userThemeAliases = themeAlias(themePath, false);
aliases = {...aliases, ...buildThemeAliases(userThemeAliases, aliases)};
Object.assign(aliases, userThemeAliases);
});
return sortAliases(aliases);

View file

@ -23,13 +23,14 @@ Object {
"@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts",
"@generated": "../../../../../../..",
"@site": "",
"@theme-init/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js",
"@theme-init/PluginThemeComponentEnhanced": "pluginThemeFolder/PluginThemeComponentEnhanced.js",
"@theme-original/Error": "../../../../client/theme-fallback/Error/index.js",
"@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.js",
"@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
"@theme-original/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.js",
"@theme-original/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js",
"@theme-original/PluginThemeComponentEnhanced": "secondPluginThemeFolder/PluginThemeComponentEnhanced.js",
"@theme-original/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js",
"@theme-original/Root": "../../../../client/theme-fallback/Root/index.js",
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
"@theme/Error": "../../../../client/theme-fallback/Error/index.js",
@ -37,7 +38,8 @@ Object {
"@theme/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
"@theme/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.js",
"@theme/PluginThemeComponentOverridden": "src/theme/PluginThemeComponentOverridden.js",
"@theme/PluginThemeComponentEnhanced": "src/theme/PluginThemeComponentEnhanced.js",
"@theme/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js",
"@theme/Root": "../../../../client/theme-fallback/Root/index.js",
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",

View file

@ -103,6 +103,16 @@ describe('base webpack config', () => {
);
},
},
{
getThemePath() {
return path.resolve(
__dirname,
'__fixtures__',
'base_test_site',
'secondPluginThemeFolder',
);
},
},
],
};

View file

@ -182,6 +182,34 @@ Unless you want publish to npm a "theme enhancer" (like `docusaurus-theme-live-c
:::
<details>
<summary>How are theme aliases resolved?</summary>
It can be quite hard to wrap your mind around these aliases. Let's imagine the following case with a super convoluted setup where three themes/plugins and the site itself all try 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 code block 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!
</details>
## Themes design {#themes-design}
While themes share the exact same lifecycle methods with plugins, their implementations can look very different from those of plugins based on themes' designed objectives.

View file

@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import type {Props} from '@theme/CodeBlock';
import CodeBlock from '@theme-original/CodeBlock';
// This component does nothing on purpose
// Dogfood: wrapping a theme component already enhanced by another theme
// See https://github.com/facebook/docusaurus/pull/5983
export default function CodeBlockWrapper(props: Props): JSX.Element {
return <CodeBlock {...props} />;
}