mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-04 01:09:20 +02:00
feat: shorthands for themes/plugins/presets configuration (#5930)
Co-authored-by: Josh-Cena <sidachen2003@gmail.com> Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
bfd7fd9d8a
commit
b366ba5603
11 changed files with 275 additions and 35 deletions
|
@ -18,7 +18,7 @@ const config = {
|
|||
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
|
|
|
@ -23,7 +23,7 @@ const config = {
|
|||
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
|
|
8
packages/docusaurus-types/src/index.d.ts
vendored
8
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -153,6 +153,14 @@ export interface Preset {
|
|||
themes?: PluginConfig[];
|
||||
}
|
||||
|
||||
export type PresetModule = {
|
||||
<T>(context: LoadContext, presetOptions: T): Preset;
|
||||
};
|
||||
|
||||
export type ImportedPresetModule = PresetModule & {
|
||||
default?: PresetModule;
|
||||
};
|
||||
|
||||
export type PresetConfig =
|
||||
| [string, Record<string, unknown>]
|
||||
| [string]
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* 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 {getNamePatterns, resolveModuleName} from '../moduleShorthand';
|
||||
|
||||
describe('getNamePatterns', () => {
|
||||
test('should resolve plain names', () => {
|
||||
expect(getNamePatterns('awesome', 'plugin')).toEqual([
|
||||
'awesome',
|
||||
'@docusaurus/plugin-awesome',
|
||||
'docusaurus-plugin-awesome',
|
||||
]);
|
||||
|
||||
expect(getNamePatterns('awesome', 'theme')).toEqual([
|
||||
'awesome',
|
||||
'@docusaurus/theme-awesome',
|
||||
'docusaurus-theme-awesome',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should expand bare scopes', () => {
|
||||
expect(getNamePatterns('@joshcena', 'plugin')).toEqual([
|
||||
'@joshcena/docusaurus-plugin',
|
||||
]);
|
||||
|
||||
expect(getNamePatterns('@joshcena', 'theme')).toEqual([
|
||||
'@joshcena/docusaurus-theme',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should expand scoped names', () => {
|
||||
expect(getNamePatterns('@joshcena/awesome', 'plugin')).toEqual([
|
||||
'@joshcena/awesome',
|
||||
'@joshcena/docusaurus-plugin-awesome',
|
||||
]);
|
||||
|
||||
expect(getNamePatterns('@joshcena/awesome', 'theme')).toEqual([
|
||||
'@joshcena/awesome',
|
||||
'@joshcena/docusaurus-theme-awesome',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should expand deep scoped paths', () => {
|
||||
expect(getNamePatterns('@joshcena/awesome/web', 'plugin')).toEqual([
|
||||
'@joshcena/awesome/web',
|
||||
'@joshcena/docusaurus-plugin-awesome/web',
|
||||
]);
|
||||
|
||||
expect(getNamePatterns('@joshcena/awesome/web', 'theme')).toEqual([
|
||||
'@joshcena/awesome/web',
|
||||
'@joshcena/docusaurus-theme-awesome/web',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveModuleName', () => {
|
||||
test('should resolve longhand', () => {
|
||||
expect(
|
||||
resolveModuleName('@docusaurus/plugin-content-docs', require, 'plugin'),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
test('should resolve shorthand', () => {
|
||||
expect(resolveModuleName('content-docs', require, 'plugin')).toBeDefined();
|
||||
});
|
||||
|
||||
test('should throw good error message for longhand', () => {
|
||||
expect(() =>
|
||||
resolveModuleName('@docusaurus/plugin-content-doc', require, 'plugin'),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Docusaurus was unable to resolve the \\"@docusaurus/plugin-content-doc\\" plugin. Make sure one of the following packages are installed:
|
||||
- @docusaurus/plugin-content-doc
|
||||
- @docusaurus/docusaurus-plugin-plugin-content-doc"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should throw good error message for shorthand', () => {
|
||||
expect(() => resolveModuleName('content-doc', require, 'plugin'))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Docusaurus was unable to resolve the \\"content-doc\\" plugin. Make sure one of the following packages are installed:
|
||||
- content-doc
|
||||
- @docusaurus/plugin-content-doc
|
||||
- docusaurus-plugin-content-doc"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -39,6 +39,8 @@ import {
|
|||
import {mapValues} from 'lodash';
|
||||
import {RuleSetRule} from 'webpack';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {createRequire} from 'module';
|
||||
import {resolveModuleName} from './moduleShorthand';
|
||||
|
||||
export type LoadContextOptions = {
|
||||
customOutDir?: string;
|
||||
|
@ -127,14 +129,44 @@ export async function loadContext(
|
|||
}
|
||||
|
||||
export function loadPluginConfigs(context: LoadContext): PluginConfig[] {
|
||||
const {plugins: presetPlugins, themes: presetThemes} = loadPresets(context);
|
||||
const {siteConfig} = context;
|
||||
let {plugins: presetPlugins, themes: presetThemes} = loadPresets(context);
|
||||
const {siteConfig, siteConfigPath} = context;
|
||||
const require = createRequire(siteConfigPath);
|
||||
function normalizeShorthand(
|
||||
pluginConfig: PluginConfig,
|
||||
pluginType: 'plugin' | 'theme',
|
||||
): PluginConfig {
|
||||
if (typeof pluginConfig === 'string') {
|
||||
return resolveModuleName(pluginConfig, require, pluginType);
|
||||
} else if (
|
||||
Array.isArray(pluginConfig) &&
|
||||
typeof pluginConfig[0] === 'string'
|
||||
) {
|
||||
return [
|
||||
resolveModuleName(pluginConfig[0], require, pluginType),
|
||||
pluginConfig[1] ?? {},
|
||||
];
|
||||
}
|
||||
return pluginConfig;
|
||||
}
|
||||
presetPlugins = presetPlugins.map((plugin) =>
|
||||
normalizeShorthand(plugin, 'plugin'),
|
||||
);
|
||||
presetThemes = presetThemes.map((theme) =>
|
||||
normalizeShorthand(theme, 'theme'),
|
||||
);
|
||||
const standalonePlugins = (siteConfig.plugins || []).map((plugin) =>
|
||||
normalizeShorthand(plugin, 'plugin'),
|
||||
);
|
||||
const standaloneThemes = (siteConfig.themes || []).map((theme) =>
|
||||
normalizeShorthand(theme, 'theme'),
|
||||
);
|
||||
return [
|
||||
...presetPlugins,
|
||||
...presetThemes,
|
||||
// Site config should be the highest priority.
|
||||
...(siteConfig.plugins || []),
|
||||
...(siteConfig.themes || []),
|
||||
...standalonePlugins,
|
||||
...standaloneThemes,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
45
packages/docusaurus/src/server/moduleShorthand.ts
Normal file
45
packages/docusaurus/src/server/moduleShorthand.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function getNamePatterns(
|
||||
moduleName: string,
|
||||
moduleType: 'preset' | 'theme' | 'plugin',
|
||||
): string[] {
|
||||
if (moduleName.startsWith('@')) {
|
||||
// Pure scope: `@scope` => `@scope/docusaurus-plugin`
|
||||
if (!moduleName.includes('/')) {
|
||||
return [`${moduleName}/docusaurus-${moduleType}`];
|
||||
}
|
||||
const [scope, packageName] = moduleName.split(/\/(.*)/);
|
||||
return [
|
||||
`${scope}/${packageName}`,
|
||||
`${scope}/docusaurus-${moduleType}-${packageName}`,
|
||||
];
|
||||
}
|
||||
return [
|
||||
moduleName,
|
||||
`@docusaurus/${moduleType}-${moduleName}`,
|
||||
`docusaurus-${moduleType}-${moduleName}`,
|
||||
];
|
||||
}
|
||||
|
||||
export function resolveModuleName(
|
||||
moduleName: string,
|
||||
moduleRequire: NodeRequire,
|
||||
moduleType: 'preset' | 'theme' | 'plugin',
|
||||
): string {
|
||||
const modulePatterns = getNamePatterns(moduleName, moduleType);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const module of modulePatterns) {
|
||||
try {
|
||||
moduleRequire.resolve(module);
|
||||
return module;
|
||||
} catch (e) {}
|
||||
}
|
||||
throw new Error(`Docusaurus was unable to resolve the "${moduleName}" ${moduleType}. Make sure one of the following packages are installed:
|
||||
${modulePatterns.map((module) => `- ${module}`).join('\n')}`);
|
||||
}
|
|
@ -10,17 +10,18 @@ import importFresh from 'import-fresh';
|
|||
import {
|
||||
LoadContext,
|
||||
PluginConfig,
|
||||
Preset,
|
||||
PresetConfig,
|
||||
ImportedPresetModule,
|
||||
} from '@docusaurus/types';
|
||||
import {resolveModuleName} from '../moduleShorthand';
|
||||
|
||||
export default function loadPresets(context: LoadContext): {
|
||||
plugins: PluginConfig[];
|
||||
themes: PluginConfig[];
|
||||
} {
|
||||
// We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json
|
||||
// declares the dependency on these plugins.
|
||||
const pluginRequire = createRequire(context.siteConfigPath);
|
||||
// We need to resolve presets from the perspective of the siteDir, since the siteDir's package.json
|
||||
// declares the dependency on these presets.
|
||||
const presetRequire = createRequire(context.siteConfigPath);
|
||||
|
||||
const presets: PresetConfig[] = (context.siteConfig || {}).presets || [];
|
||||
const unflatPlugins: PluginConfig[][] = [];
|
||||
|
@ -36,17 +37,16 @@ export default function loadPresets(context: LoadContext): {
|
|||
} else {
|
||||
throw new Error('Invalid presets format detected in config.');
|
||||
}
|
||||
const presetName = resolveModuleName(
|
||||
presetModuleImport,
|
||||
presetRequire,
|
||||
'preset',
|
||||
);
|
||||
|
||||
type PresetInitializeFunction = (
|
||||
context: LoadContext,
|
||||
presetOptions: Record<string, unknown>,
|
||||
) => Preset;
|
||||
const presetModule = importFresh<
|
||||
PresetInitializeFunction & {
|
||||
default?: PresetInitializeFunction;
|
||||
}
|
||||
>(pluginRequire.resolve(presetModuleImport));
|
||||
const preset: Preset = (presetModule.default || presetModule)(
|
||||
const presetModule = importFresh<ImportedPresetModule>(
|
||||
presetRequire.resolve(presetName),
|
||||
);
|
||||
const preset = (presetModule.default ?? presetModule)(
|
||||
context,
|
||||
presetOptions,
|
||||
);
|
||||
|
@ -60,7 +60,7 @@ export default function loadPresets(context: LoadContext): {
|
|||
});
|
||||
|
||||
return {
|
||||
plugins: ([] as PluginConfig[]).concat(...unflatPlugins).filter(Boolean),
|
||||
themes: ([] as PluginConfig[]).concat(...unflatThemes).filter(Boolean),
|
||||
plugins: unflatPlugins.flat().filter(Boolean),
|
||||
themes: unflatThemes.flat().filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue