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 theme1Path = path.join(fixtures, 'theme-1');
const theme2Path = path.join(fixtures, 'theme-2'); const theme2Path = path.join(fixtures, 'theme-2');
const alias = loadThemeAliases([theme1Path, theme2Path]); const alias = loadThemeAliases([theme1Path, theme2Path], []);
// Testing entries, because order matters! // Testing entries, because order matters!
expect(Object.entries(alias)).toEqual( expect(Object.entries(alias)).toEqual(

View file

@ -12,34 +12,31 @@ import themeAlias, {sortAliases} from './alias';
const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback'); 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( export function loadThemeAliases(
themePaths: string[], themePaths: string[],
userThemePaths: string[] = [], userThemePaths: string[],
): ThemeAliases { ): ThemeAliases {
let aliases = {}; // TODO refactor, inelegant side-effect const aliases: ThemeAliases = {};
themePaths.forEach((themePath) => { themePaths.forEach((themePath) => {
const themeAliases = themeAlias(themePath, true); 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) => { userThemePaths.forEach((themePath) => {
const userThemeAliases = themeAlias(themePath, false); const userThemeAliases = themeAlias(themePath, false);
aliases = {...aliases, ...buildThemeAliases(userThemeAliases, aliases)}; Object.assign(aliases, userThemeAliases);
}); });
return sortAliases(aliases); return sortAliases(aliases);

View file

@ -23,13 +23,14 @@ Object {
"@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts", "@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts",
"@generated": "../../../../../../..", "@generated": "../../../../../../..",
"@site": "", "@site": "",
"@theme-init/PluginThemeComponentOverridden": "pluginThemeFolder/PluginThemeComponentOverridden.js", "@theme-init/PluginThemeComponentEnhanced": "pluginThemeFolder/PluginThemeComponentEnhanced.js",
"@theme-original/Error": "../../../../client/theme-fallback/Error/index.js", "@theme-original/Error": "../../../../client/theme-fallback/Error/index.js",
"@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.js", "@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.js",
"@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.js", "@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.js", "@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
"@theme-original/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.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/Root": "../../../../client/theme-fallback/Root/index.js",
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js", "@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
"@theme/Error": "../../../../client/theme-fallback/Error/index.js", "@theme/Error": "../../../../client/theme-fallback/Error/index.js",
@ -37,7 +38,8 @@ Object {
"@theme/Loading": "../../../../client/theme-fallback/Loading/index.js", "@theme/Loading": "../../../../client/theme-fallback/Loading/index.js",
"@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.js", "@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.js",
"@theme/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.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/Root": "../../../../client/theme-fallback/Root/index.js",
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js", "@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.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} ## 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. 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} />;
}