diff --git a/packages/docusaurus/src/server/themes/__tests__/index.test.ts b/packages/docusaurus/src/server/themes/__tests__/index.test.ts
index 7ce9e042f4..157ec169d3 100644
--- a/packages/docusaurus/src/server/themes/__tests__/index.test.ts
+++ b/packages/docusaurus/src/server/themes/__tests__/index.test.ts
@@ -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(
diff --git a/packages/docusaurus/src/server/themes/index.ts b/packages/docusaurus/src/server/themes/index.ts
index 37cecdc9ca..cc92463678 100644
--- a/packages/docusaurus/src/server/themes/index.ts
+++ b/packages/docusaurus/src/server/themes/index.ts
@@ -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);
diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentOverridden.js b/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentEnhanced.js
similarity index 100%
rename from packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentOverridden.js
rename to packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentEnhanced.js
diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentOverridden.js b/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentOverriddenByUser.js
similarity index 100%
rename from packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentOverridden.js
rename to packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/pluginThemeFolder/PluginThemeComponentOverriddenByUser.js
diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/secondPluginThemeFolder/PluginThemeComponentEnhanced.js b/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/secondPluginThemeFolder/PluginThemeComponentEnhanced.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentEnhanced.js b/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentEnhanced.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentOverriddenByUser.js b/packages/docusaurus/src/webpack/__tests__/__fixtures__/base_test_site/src/theme/PluginThemeComponentOverriddenByUser.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap
index 1a92081d41..623fa86a83 100644
--- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap
+++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap
@@ -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",
diff --git a/packages/docusaurus/src/webpack/__tests__/base.test.ts b/packages/docusaurus/src/webpack/__tests__/base.test.ts
index 7d2af48b4b..564395e812 100644
--- a/packages/docusaurus/src/webpack/__tests__/base.test.ts
+++ b/packages/docusaurus/src/webpack/__tests__/base.test.ts
@@ -103,6 +103,16 @@ describe('base webpack config', () => {
);
},
},
+ {
+ getThemePath() {
+ return path.resolve(
+ __dirname,
+ '__fixtures__',
+ 'base_test_site',
+ 'secondPluginThemeFolder',
+ );
+ },
+ },
],
};
diff --git a/website/docs/using-themes.md b/website/docs/using-themes.md
index 85633ea551..77e9f4a71d 100644
--- a/website/docs/using-themes.md
+++ b/website/docs/using-themes.md
@@ -182,6 +182,34 @@ Unless you want publish to npm a "theme enhancer" (like `docusaurus-theme-live-c
:::
+
+
+How are theme aliases resolved?
+
+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!
+
+
+
## 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.
diff --git a/website/src/theme/CodeBlock/index.tsx b/website/src/theme/CodeBlock/index.tsx
new file mode 100644
index 0000000000..dd5aedfc18
--- /dev/null
+++ b/website/src/theme/CodeBlock/index.tsx
@@ -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 ;
+}