mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 16:17:25 +02:00
fix(v2): improve dx sidebar config, ability to have no sidebars file (#4775)
* Improve sidebar config * Edit message * fix some little issues in the way undefined/false sidebars are handled * remove old error message as it has been moved to a better place Co-authored-by: Nam Hoang Le <nam.hoang.le@mgm-tp.com> Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
e85ec1ab12
commit
1ab8aa0af8
10 changed files with 235 additions and 129 deletions
|
@ -1,5 +1,27 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sidebar site with wrong sidebar content 1`] = `
|
||||
"Bad sidebars file.
|
||||
These sidebar document ids do not exist:
|
||||
- goku,
|
||||
|
||||
Available document ids=
|
||||
- foo/bar
|
||||
- foo/baz
|
||||
- headingAsTitle
|
||||
- hello
|
||||
- ipsum
|
||||
- lorem
|
||||
- rootAbsoluteSlug
|
||||
- rootRelativeSlug
|
||||
- rootResolvedSlug
|
||||
- rootTryToEscapeSlug
|
||||
- slugs/absoluteSlug
|
||||
- slugs/relativeSlug
|
||||
- slugs/resolvedSlug
|
||||
- slugs/tryToEscapeSlug"
|
||||
`;
|
||||
|
||||
exports[`simple website content 1`] = `
|
||||
Object {
|
||||
"docs": Array [
|
||||
|
@ -790,28 +812,6 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`site with wrong sidebar file 1`] = `
|
||||
"Bad sidebars file.
|
||||
These sidebar document ids do not exist:
|
||||
- goku,
|
||||
|
||||
Available document ids=
|
||||
- foo/bar
|
||||
- foo/baz
|
||||
- headingAsTitle
|
||||
- hello
|
||||
- ipsum
|
||||
- lorem
|
||||
- rootAbsoluteSlug
|
||||
- rootRelativeSlug
|
||||
- rootResolvedSlug
|
||||
- rootTryToEscapeSlug
|
||||
- slugs/absoluteSlug
|
||||
- slugs/relativeSlug
|
||||
- slugs/resolvedSlug
|
||||
- slugs/tryToEscapeSlug"
|
||||
`;
|
||||
|
||||
exports[`versioned website (community) content: 100 version sidebars 1`] = `
|
||||
Object {
|
||||
"version-1.0.0/community": Array [
|
||||
|
|
|
@ -35,6 +35,7 @@ import {toSidebarsProp} from '../props';
|
|||
|
||||
import {validate} from 'webpack';
|
||||
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
|
||||
import {DisabledSidebars} from '../sidebars';
|
||||
|
||||
function findDocById(version: LoadedVersion, unversionedId: string) {
|
||||
return version.docs.find((item) => item.unversionedId === unversionedId);
|
||||
|
@ -124,7 +125,8 @@ Entries created:
|
|||
};
|
||||
};
|
||||
|
||||
test('site with wrong sidebar file', async () => {
|
||||
describe('sidebar', () => {
|
||||
test('site with wrong sidebar content', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
|
||||
const context = await loadContext(siteDir);
|
||||
const sidebarPath = path.join(siteDir, 'wrong-sidebars.json');
|
||||
|
@ -135,6 +137,72 @@ test('site with wrong sidebar file', async () => {
|
|||
}),
|
||||
);
|
||||
await expect(plugin.loadContent!()).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('site with wrong sidebar file path', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
|
||||
const context = await loadContext(siteDir);
|
||||
|
||||
await expect(async () => {
|
||||
const plugin = pluginContentDocs(
|
||||
context,
|
||||
normalizePluginOptions(OptionsSchema, {
|
||||
sidebarPath: 'wrong-path-sidebar.json',
|
||||
}),
|
||||
);
|
||||
await plugin.loadContent!();
|
||||
}).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"The path to the sidebar file does not exist at [wrong-path-sidebar.json].
|
||||
Please set the docs [sidebarPath] field in your config file to:
|
||||
- a sidebars path that exists
|
||||
- false: to disable the sidebar
|
||||
- undefined: for Docusaurus generates it automatically"
|
||||
`);
|
||||
});
|
||||
|
||||
test('site with undefined sidebar', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
|
||||
const context = await loadContext(siteDir);
|
||||
const plugin = pluginContentDocs(
|
||||
context,
|
||||
normalizePluginOptions(OptionsSchema, {
|
||||
sidebarPath: undefined,
|
||||
}),
|
||||
);
|
||||
const result = await plugin.loadContent!();
|
||||
|
||||
expect(result.loadedVersions).toHaveLength(1);
|
||||
expect(result.loadedVersions[0].sidebars).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"defaultSidebar": Array [
|
||||
Object {
|
||||
"id": "hello-1",
|
||||
"type": "doc",
|
||||
},
|
||||
Object {
|
||||
"id": "hello-2",
|
||||
"label": "Hello 2 From Doc",
|
||||
"type": "doc",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('site with disabled sidebar', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label');
|
||||
const context = await loadContext(siteDir);
|
||||
const plugin = pluginContentDocs(
|
||||
context,
|
||||
normalizePluginOptions(OptionsSchema, {
|
||||
sidebarPath: false,
|
||||
}),
|
||||
);
|
||||
const result = await plugin.loadContent!();
|
||||
|
||||
expect(result.loadedVersions).toHaveLength(1);
|
||||
expect(result.loadedVersions[0].sidebars).toEqual(DisabledSidebars);
|
||||
});
|
||||
});
|
||||
|
||||
describe('empty/no docs website', () => {
|
||||
|
|
|
@ -14,8 +14,9 @@ import {
|
|||
collectSidebarCategories,
|
||||
collectSidebarLinks,
|
||||
transformSidebarItems,
|
||||
DefaultSidebars,
|
||||
processSidebars,
|
||||
DefaultSidebars,
|
||||
DisabledSidebars,
|
||||
} from '../sidebars';
|
||||
import {
|
||||
Sidebar,
|
||||
|
@ -127,35 +128,15 @@ describe('loadSidebars', () => {
|
|||
});
|
||||
|
||||
test('unexisting path', () => {
|
||||
/*
|
||||
expect(() => loadSidebars('badpath')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"No sidebar file exist at path: badpath"`,
|
||||
);
|
||||
*/
|
||||
// See https://github.com/facebook/docusaurus/issues/3366
|
||||
expect(loadSidebars('badpath')).toEqual(DefaultSidebars);
|
||||
expect(loadSidebars('badpath')).toEqual(DisabledSidebars);
|
||||
});
|
||||
|
||||
test('undefined path', () => {
|
||||
expect(() =>
|
||||
loadSidebars(
|
||||
// @ts-expect-error: bad arg
|
||||
undefined,
|
||||
),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"sidebarFilePath not provided: undefined"`,
|
||||
);
|
||||
expect(loadSidebars(undefined)).toEqual(DefaultSidebars);
|
||||
});
|
||||
|
||||
test('null path', () => {
|
||||
expect(() =>
|
||||
loadSidebars(
|
||||
// @ts-expect-error: bad arg
|
||||
null,
|
||||
),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"sidebarFilePath not provided: null"`,
|
||||
);
|
||||
test('literal false path', () => {
|
||||
expect(loadSidebars(false)).toEqual(DisabledSidebars);
|
||||
});
|
||||
|
||||
test('sidebars with category.collapsed property', async () => {
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('simple site', () => {
|
|||
),
|
||||
isLast: true,
|
||||
routePriority: -1,
|
||||
sidebarFilePath: path.join(simpleSiteDir, 'sidebars.json'),
|
||||
sidebarFilePath: undefined,
|
||||
versionLabel: 'Next',
|
||||
versionName: 'current',
|
||||
versionPath: '/docs',
|
||||
|
@ -138,6 +138,9 @@ describe('simple site', () => {
|
|||
versionPath: '/myBaseUrl/docs/current-path',
|
||||
versionLabel: 'current-label',
|
||||
routePriority: undefined,
|
||||
sidebarFilePath: undefined,
|
||||
versionEditUrl: undefined,
|
||||
versionEditUrlLocalized: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -210,6 +213,7 @@ describe('versioned site, pluginId=default', () => {
|
|||
const defaultOptions: PluginOptions = {
|
||||
id: DEFAULT_PLUGIN_ID,
|
||||
...DEFAULT_OPTIONS,
|
||||
sidebarPath: 'sidebars.json',
|
||||
};
|
||||
const defaultContext = {
|
||||
siteDir: versionedSiteDir,
|
||||
|
@ -607,6 +611,7 @@ describe('versioned site, pluginId=community', () => {
|
|||
id: 'community',
|
||||
path: 'community',
|
||||
routeBasePath: 'communityBasePath',
|
||||
sidebarPath: 'sidebars.json',
|
||||
};
|
||||
const defaultContext = {
|
||||
siteDir: versionedSiteDir,
|
||||
|
|
|
@ -20,6 +20,67 @@ import {
|
|||
import {loadSidebars} from './sidebars';
|
||||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
||||
|
||||
function createVersionedSidebarFile({
|
||||
siteDir,
|
||||
pluginId,
|
||||
sidebarPath,
|
||||
version,
|
||||
}: {
|
||||
siteDir: string;
|
||||
pluginId: string;
|
||||
sidebarPath: string | false | undefined;
|
||||
version: string;
|
||||
}) {
|
||||
// Load current sidebar and create a new versioned sidebars file (if needed).
|
||||
const loadedSidebars = loadSidebars(sidebarPath);
|
||||
|
||||
// Do not create a useless versioned sidebars file if sidebars file is empty or sidebars are disabled/false)
|
||||
const shouldCreateVersionedSidebarFile =
|
||||
Object.keys(loadedSidebars).length > 0;
|
||||
|
||||
if (shouldCreateVersionedSidebarFile) {
|
||||
// TODO @slorber: this "version prefix" in versioned sidebars looks like a bad idea to me
|
||||
// TODO try to get rid of it
|
||||
// Transform id in original sidebar to versioned id.
|
||||
const normalizeItem = (
|
||||
item: UnprocessedSidebarItem,
|
||||
): UnprocessedSidebarItem => {
|
||||
switch (item.type) {
|
||||
case 'category':
|
||||
return {...item, items: item.items.map(normalizeItem)};
|
||||
case 'ref':
|
||||
case 'doc':
|
||||
return {
|
||||
type: item.type,
|
||||
id: `version-${version}/${item.id}`,
|
||||
};
|
||||
default:
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
const versionedSidebar: UnprocessedSidebars = Object.entries(
|
||||
loadedSidebars,
|
||||
).reduce((acc: UnprocessedSidebars, [sidebarId, sidebarItems]) => {
|
||||
const newVersionedSidebarId = `version-${version}/${sidebarId}`;
|
||||
acc[newVersionedSidebarId] = sidebarItems.map(normalizeItem);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const versionedSidebarsDir = getVersionedSidebarsDirPath(siteDir, pluginId);
|
||||
const newSidebarFile = path.join(
|
||||
versionedSidebarsDir,
|
||||
`version-${version}-sidebars.json`,
|
||||
);
|
||||
fs.ensureDirSync(path.dirname(newSidebarFile));
|
||||
fs.writeFileSync(
|
||||
newSidebarFile,
|
||||
`${JSON.stringify(versionedSidebar, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests depend on non-default export for mocking.
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function cliDocsVersionCommand(
|
||||
|
@ -92,50 +153,7 @@ export function cliDocsVersionCommand(
|
|||
throw new Error(`${pluginIdLogPrefix}There is no docs to version !`);
|
||||
}
|
||||
|
||||
// Load current sidebar and create a new versioned sidebars file.
|
||||
if (fs.existsSync(sidebarPath)) {
|
||||
const loadedSidebars = loadSidebars(sidebarPath);
|
||||
|
||||
// TODO @slorber: this "version prefix" in versioned sidebars looks like a bad idea to me
|
||||
// TODO try to get rid of it
|
||||
// Transform id in original sidebar to versioned id.
|
||||
const normalizeItem = (
|
||||
item: UnprocessedSidebarItem,
|
||||
): UnprocessedSidebarItem => {
|
||||
switch (item.type) {
|
||||
case 'category':
|
||||
return {...item, items: item.items.map(normalizeItem)};
|
||||
case 'ref':
|
||||
case 'doc':
|
||||
return {
|
||||
type: item.type,
|
||||
id: `version-${version}/${item.id}`,
|
||||
};
|
||||
default:
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
const versionedSidebar: UnprocessedSidebars = Object.entries(
|
||||
loadedSidebars,
|
||||
).reduce((acc: UnprocessedSidebars, [sidebarId, sidebarItems]) => {
|
||||
const newVersionedSidebarId = `version-${version}/${sidebarId}`;
|
||||
acc[newVersionedSidebarId] = sidebarItems.map(normalizeItem);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const versionedSidebarsDir = getVersionedSidebarsDirPath(siteDir, pluginId);
|
||||
const newSidebarFile = path.join(
|
||||
versionedSidebarsDir,
|
||||
`version-${version}-sidebars.json`,
|
||||
);
|
||||
fs.ensureDirSync(path.dirname(newSidebarFile));
|
||||
fs.writeFileSync(
|
||||
newSidebarFile,
|
||||
`${JSON.stringify(versionedSidebar, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
createVersionedSidebarFile({siteDir, pluginId, version, sidebarPath});
|
||||
|
||||
// Update versions.json file.
|
||||
versions.unshift(version);
|
||||
|
|
|
@ -118,8 +118,7 @@ export default function pluginContentDocs(
|
|||
|
||||
getPathsToWatch() {
|
||||
function getVersionPathsToWatch(version: VersionMetadata): string[] {
|
||||
return [
|
||||
version.sidebarFilePath,
|
||||
const result = [
|
||||
...flatten(
|
||||
options.include.map((pattern) =>
|
||||
getDocsDirPaths(version).map(
|
||||
|
@ -129,6 +128,10 @@ export default function pluginContentDocs(
|
|||
),
|
||||
`${version.contentPath}/**/${CategoryMetadataFilenamePattern}`,
|
||||
];
|
||||
if (typeof version.sidebarFilePath === 'string') {
|
||||
result.unshift(version.sidebarFilePath);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return flatten(versionsMetadata.map(getVersionPathsToWatch));
|
||||
|
|
|
@ -21,12 +21,11 @@ import {
|
|||
DisabledNumberPrefixParser,
|
||||
} from './numberPrefix';
|
||||
|
||||
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
||||
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
|
||||
path: 'docs', // Path to data on filesystem, relative to site dir.
|
||||
routeBasePath: 'docs', // URL Route.
|
||||
homePageId: undefined, // TODO remove soon, deprecated
|
||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||
sidebarPath: 'sidebars.json', // Path to the sidebars configuration file
|
||||
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||
numberPrefixParser: DefaultNumberPrefixParser,
|
||||
docLayoutComponent: '@theme/DocPage',
|
||||
|
@ -67,7 +66,10 @@ export const OptionsSchema = Joi.object({
|
|||
.default(DEFAULT_OPTIONS.routeBasePath),
|
||||
homePageId: Joi.string().optional(),
|
||||
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
||||
sidebarPath: Joi.string().allow('').default(DEFAULT_OPTIONS.sidebarPath),
|
||||
sidebarPath: Joi.alternatives().try(
|
||||
Joi.boolean().invalid(true),
|
||||
Joi.string(),
|
||||
),
|
||||
sidebarItemsGenerator: Joi.function().default(
|
||||
() => DEFAULT_OPTIONS.sidebarItemsGenerator,
|
||||
),
|
||||
|
|
|
@ -254,18 +254,29 @@ export const DefaultSidebars: UnprocessedSidebars = {
|
|||
],
|
||||
};
|
||||
|
||||
export const DisabledSidebars: UnprocessedSidebars = {};
|
||||
|
||||
// TODO refactor: make async
|
||||
export function loadSidebars(sidebarFilePath: string): UnprocessedSidebars {
|
||||
if (!sidebarFilePath) {
|
||||
throw new Error(`sidebarFilePath not provided: ${sidebarFilePath}`);
|
||||
export function loadSidebars(
|
||||
sidebarFilePath: string | false | undefined,
|
||||
): UnprocessedSidebars {
|
||||
// false => no sidebars
|
||||
if (sidebarFilePath === false) {
|
||||
return DisabledSidebars;
|
||||
}
|
||||
|
||||
// No sidebars file: by default we use the file-system structure to generate the sidebar
|
||||
// See https://github.com/facebook/docusaurus/pull/4582
|
||||
if (!fs.existsSync(sidebarFilePath)) {
|
||||
// undefined => defaults to autogenerated sidebars
|
||||
if (typeof sidebarFilePath === 'undefined') {
|
||||
return DefaultSidebars;
|
||||
}
|
||||
|
||||
// unexisting sidebars file: no sidebars
|
||||
// Note: this edge case can happen on versioned docs, not current version
|
||||
// We avoid creating empty versioned sidebars file with the CLI
|
||||
if (!fs.existsSync(sidebarFilePath)) {
|
||||
return DisabledSidebars;
|
||||
}
|
||||
|
||||
// We don't want sidebars to be cached because of hot reloading.
|
||||
const sidebarJson = importFresh(sidebarFilePath) as SidebarsJSON;
|
||||
return normalizeSidebars(sidebarJson);
|
||||
|
|
|
@ -31,9 +31,7 @@ export type VersionMetadata = ContentPaths & {
|
|||
versionEditUrl?: string | undefined;
|
||||
versionEditUrlLocalized?: string | undefined;
|
||||
isLast: boolean;
|
||||
// contentPath: string; // "versioned_docs/version-1.0.0"
|
||||
// contentPathLocalized: string; // "i18n/fr/version-1.0.0/default"
|
||||
sidebarFilePath: string; // versioned_sidebars/1.0.0.json
|
||||
sidebarFilePath: string | false | undefined; // versioned_sidebars/1.0.0.json
|
||||
routePriority: number | undefined; // -1 for the latest docs
|
||||
};
|
||||
|
||||
|
@ -58,7 +56,7 @@ export type MetadataOptions = {
|
|||
|
||||
export type PathOptions = {
|
||||
path: string;
|
||||
sidebarPath: string;
|
||||
sidebarPath?: string | false | undefined;
|
||||
};
|
||||
|
||||
export type VersionOptions = {
|
||||
|
|
|
@ -24,7 +24,6 @@ import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
|||
import {LoadContext} from '@docusaurus/types';
|
||||
import {getPluginI18nPath, normalizeUrl, posixPath} from '@docusaurus/utils';
|
||||
import {difference} from 'lodash';
|
||||
import chalk from 'chalk';
|
||||
|
||||
// retro-compatibility: no prefix for the default plugin id
|
||||
function addPluginIdPrefix(fileOrDir: string, pluginId: string): string {
|
||||
|
@ -183,14 +182,24 @@ function getVersionMetadataPaths({
|
|||
versionName,
|
||||
});
|
||||
|
||||
const sidebarFilePath = isCurrentVersion
|
||||
function getSidebarFilePath() {
|
||||
if (isCurrentVersion) {
|
||||
return options.sidebarPath
|
||||
? path.resolve(context.siteDir, options.sidebarPath)
|
||||
: path.join(
|
||||
: options.sidebarPath;
|
||||
} else {
|
||||
return path.join(
|
||||
getVersionedSidebarsDirPath(context.siteDir, options.id),
|
||||
`version-${versionName}-sidebars.json`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {contentPath, contentPathLocalized, sidebarFilePath};
|
||||
return {
|
||||
contentPath,
|
||||
contentPathLocalized,
|
||||
sidebarFilePath: getSidebarFilePath(),
|
||||
};
|
||||
}
|
||||
|
||||
function getVersionEditUrls({
|
||||
|
@ -330,6 +339,7 @@ function checkVersionMetadataPaths({
|
|||
}) {
|
||||
const {versionName, contentPath, sidebarFilePath} = versionMetadata;
|
||||
const {siteDir} = context;
|
||||
const isCurrentVersion = versionName === CURRENT_VERSION_NAME;
|
||||
|
||||
if (!fs.existsSync(contentPath)) {
|
||||
throw new Error(
|
||||
|
@ -340,13 +350,23 @@ function checkVersionMetadataPaths({
|
|||
);
|
||||
}
|
||||
|
||||
// If the current version defines a path to a sidebar file that does not exist, we throw!
|
||||
// Note: for versioned sidebars, the file may not exist (as we prefer to not create it rather than to create an empty file)
|
||||
// See https://github.com/facebook/docusaurus/issues/3366
|
||||
if (!fs.existsSync(sidebarFilePath)) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`The sidebar file of docs version [${versionName}] does not exist. It is optional, but should rather be provided at ${sidebarFilePath}`,
|
||||
),
|
||||
);
|
||||
// See https://github.com/facebook/docusaurus/pull/4775
|
||||
if (
|
||||
isCurrentVersion &&
|
||||
typeof sidebarFilePath === 'string' &&
|
||||
!fs.existsSync(sidebarFilePath)
|
||||
) {
|
||||
throw new Error(`The path to the sidebar file does not exist at [${path.relative(
|
||||
siteDir,
|
||||
sidebarFilePath,
|
||||
)}].
|
||||
Please set the docs [sidebarPath] field in your config file to:
|
||||
- a sidebars path that exists
|
||||
- false: to disable the sidebar
|
||||
- undefined: for Docusaurus generates it automatically`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue