From 1ab8aa0af88ae9090f49c46b0092ea6ad151b69d Mon Sep 17 00:00:00 2001 From: Nam Hoang Le Date: Tue, 18 May 2021 23:27:46 +0700 Subject: [PATCH] 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 Co-authored-by: slorber --- .../__snapshots__/index.test.ts.snap | 44 ++++---- .../src/__tests__/index.test.ts | 90 +++++++++++++-- .../src/__tests__/sidebars.test.ts | 31 +---- .../src/__tests__/versions.test.ts | 7 +- .../docusaurus-plugin-content-docs/src/cli.ts | 106 ++++++++++-------- .../src/index.ts | 7 +- .../src/options.ts | 8 +- .../src/sidebars.ts | 23 +++- .../src/types.ts | 6 +- .../src/versions.ts | 42 +++++-- 10 files changed, 235 insertions(+), 129 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index 96902a8a94..abb22c0f68 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -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 [ diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 7c77b0276e..9a52d0d7e7 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -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,17 +125,84 @@ Entries created: }; }; -test('site with wrong sidebar file', async () => { - const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); - const context = await loadContext(siteDir); - const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); - const plugin = pluginContentDocs( - context, - normalizePluginOptions(OptionsSchema, { - sidebarPath, - }), - ); - await expect(plugin.loadContent!()).rejects.toThrowErrorMatchingSnapshot(); +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'); + const plugin = pluginContentDocs( + context, + normalizePluginOptions(OptionsSchema, { + sidebarPath, + }), + ); + 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', () => { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts index 1b87d2d9ac..a21899653f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts @@ -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 () => { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts index 7191f66cba..818d070f81 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts @@ -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, diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index 66988167c4..db51cb4367 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -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); diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index bc79d1a9b5..76eb8af6d8 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -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)); diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index 5ebf6c0342..1805e4ea71 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -21,12 +21,11 @@ import { DisabledNumberPrefixParser, } from './numberPrefix'; -export const DEFAULT_OPTIONS: Omit = { +export const DEFAULT_OPTIONS: Omit = { 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, ), diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars.ts b/packages/docusaurus-plugin-content-docs/src/sidebars.ts index feae57e475..5aa2539d99 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars.ts @@ -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); diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index 585408ba21..cee48a6e5e 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -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 = { diff --git a/packages/docusaurus-plugin-content-docs/src/versions.ts b/packages/docusaurus-plugin-content-docs/src/versions.ts index de52d2d7d3..7eab514bff 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions.ts @@ -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 - ? path.resolve(context.siteDir, options.sidebarPath) - : path.join( + function getSidebarFilePath() { + if (isCurrentVersion) { + return options.sidebarPath + ? path.resolve(context.siteDir, options.sidebarPath) + : 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`); } }