Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f2ee7af
feat(plugins): Add aliases resolver for plugins
Nov 11, 2021
513b9fa
refactor: add PluginConfigs to index plugins by type (plugin or theme)
Nov 11, 2021
bf9d2af
refactor: add PluginConfigs to index plugins by type (plugin or theme)
Nov 11, 2021
adb095e
chore: hide implementation detail from the variable name
Nov 12, 2021
d3fa972
feat(presets): Add aliases resolver for presets
Nov 16, 2021
e24b980
chore: use preset alias/shorthand in examples
Nov 16, 2021
2bc2947
style: apply generic typeguard to plugin resolver
Nov 16, 2021
83c49c3
chore: apply shorthands to create-docusaurus instead of examples
Nov 17, 2021
6a8b6bf
chore: apply shorthands to create-docusaurus instead of examples
Nov 17, 2021
aafc148
chore: apply shorthands to create-docusaurus instead of examples
Nov 17, 2021
96080e8
chore: apply shorthands to create-docusaurus instead of examples
Nov 17, 2021
75e9aa9
Merge branch 'main' into fsmaia/main
Josh-Cena Nov 17, 2021
6a73cb4
dogfood on website
Josh-Cena Nov 17, 2021
6cf84fb
fix: apply loadPluginModule when the plugin config is array
Nov 17, 2021
b8cf8e3
refactor: return resolvedPath into NormalizedPluginImport
Nov 17, 2021
c664a1f
Fix e2e
Josh-Cena Nov 27, 2021
01788bf
Merge branch 'main' into fsmaia/main
Josh-Cena Nov 27, 2021
424f9d8
Refactors
Josh-Cena Nov 27, 2021
64d976a
Fix types
Josh-Cena Nov 27, 2021
3f28a10
Total refactor
Josh-Cena Nov 27, 2021
d7787d3
Less diff
Josh-Cena Nov 27, 2021
0c37a2a
Doc writeup
Josh-Cena Nov 27, 2021
c6170f1
Merge branch 'main' into fsmaia/main
Josh-Cena Nov 29, 2021
eeff8e3
Fix scoped packages
Josh-Cena Nov 30, 2021
c59b20e
Merge branch 'main' into fsmaia/main
slorber Dec 3, 2021
5781c02
dogfood names
slorber Dec 3, 2021
0b6fb24
minor doc change
slorber Dec 3, 2021
ddd3385
add tests for resolveModuleName
slorber Dec 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const config = {

presets: [
[
'@docusaurus/preset-classic',
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const config = {

presets: [
[
'@docusaurus/preset-classic',
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
Expand Down
8 changes: 8 additions & 0 deletions packages/docusaurus-types/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: added this test, but this resolve name looks a bit weird.

I'll still merge, but if you feel we should do something about it 🤷‍♂️ feel free to open another PR

`);
});

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"
`);
});
});
40 changes: 36 additions & 4 deletions packages/docusaurus/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
];
}

Expand Down
45 changes: 45 additions & 0 deletions packages/docusaurus/src/server/moduleShorthand.ts
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok for that.

Throwing exceptions has a runtime cost, but it's probably not very important here.
I don't know if there's a fail-safe way to resolve a module in nodejs but if there is one, that would be preferable.

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')}`);
}
32 changes: 16 additions & 16 deletions packages/docusaurus/src/server/presets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[][] = [];
Expand All @@ -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,
);
Expand All @@ -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),
};
}
6 changes: 3 additions & 3 deletions website/_dogfooding/dogfooding.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const fs = require('fs');
/** @type {import('@docusaurus/types').PluginConfig[]} */
const dogfoodingPluginInstances = [
[
'@docusaurus/plugin-content-docs',
'content-docs', // dogfood shorthand
/** @type {import('@docusaurus/plugin-content-docs').Options} */
({
id: 'docs-tests',
Expand All @@ -24,7 +24,7 @@ const dogfoodingPluginInstances = [
],

[
'@docusaurus/plugin-content-blog',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

going to dogfood other forms of plugin names here, it's the purpose of dogfooding to have a variety of settings ;)

'@docusaurus/plugin-content-blog', // dogfood longhand
/** @type {import('@docusaurus/plugin-content-blog').Options} */
({
id: 'blog-tests',
Expand All @@ -46,7 +46,7 @@ const dogfoodingPluginInstances = [
],

[
'@docusaurus/plugin-content-pages',
require.resolve('@docusaurus/plugin-content-pages'), // dogfood longhand resolve
/** @type {import('@docusaurus/plugin-content-pages').Options} */
({
id: 'pages-tests',
Expand Down
Loading