refactor(docs): extract loadVersion() without changing the behavior (#11230)

* refactor, extract loadVersion() without changing behavior

* Add minimal test case for loadVersion()

* Add minimal test case for loadVersion()

* more refactor, rename index.ts to version.ts

* fix tests

* empty
This commit is contained in:
Sébastien Lorber 2025-06-02 18:44:07 +02:00 committed by GitHub
parent dacfc17fb4
commit 18b47fdfc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 281 additions and 121 deletions

View file

@ -25,7 +25,7 @@ import {
type DocEnv,
} from '../docs';
import {loadSidebars} from '../sidebars';
import {readVersionsMetadata} from '../versions';
import {readVersionsMetadata} from '../versions/version';
import {DEFAULT_OPTIONS} from '../options';
import type {Sidebars} from '../sidebars/types';
import type {DocFile} from '../types';

View file

@ -7,7 +7,6 @@
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
normalizeUrl,
@ -17,30 +16,19 @@ import {
posixPath,
addTrailingPathSeparator,
createAbsoluteFilePathMatcher,
createSlugger,
resolveMarkdownLinkPathname,
DEFAULT_PLUGIN_ID,
type TagsFile,
} from '@docusaurus/utils';
import {
getTagsFile,
getTagsFilePathsToWatch,
} from '@docusaurus/utils-validation';
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
import {createMDXLoaderRule} from '@docusaurus/mdx-loader';
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {resolveSidebarPathOption} from './sidebars';
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import {
readVersionDocs,
processDocMetadata,
addDocNavigation,
type DocEnv,
createDocsByIdIndex,
} from './docs';
import {type DocEnv} from './docs';
import {
getVersionFromSourceFilePath,
readVersionsMetadata,
toFullVersion,
} from './versions';
} from './versions/version';
import cliDocs from './cli';
import {VERSIONS_JSON_FILE} from './constants';
import {toGlobalDataVersion} from './globalData';
@ -49,19 +37,17 @@ import {
getLoadedContentTranslationFiles,
} from './translations';
import {createAllRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils';
import {createContentHelpers} from './contentHelpers';
import {loadVersion} from './versions/loadVersion';
import type {
PluginOptions,
DocMetadataBase,
VersionMetadata,
DocFrontMatter,
LoadedContent,
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {DocFile, FullVersion} from './types';
import type {FullVersion} from './types';
import type {RuleSetRule} from 'webpack';
// MDX loader is not 100% deterministic, leading to cache invalidation issue
@ -243,102 +229,17 @@ export default async function pluginContentDocs(
},
async loadContent() {
async function loadVersionDocsBase(
versionMetadata: VersionMetadata,
tagsFile: TagsFile | null,
): Promise<DocMetadataBase[]> {
const docFiles = await readVersionDocs(versionMetadata, options);
if (docFiles.length === 0) {
throw new Error(
`Docs version "${
versionMetadata.versionName
}" has no docs! At least one doc should exist at "${path.relative(
siteDir,
versionMetadata.contentPath,
)}".`,
);
}
function processVersionDoc(docFile: DocFile) {
return processDocMetadata({
docFile,
versionMetadata,
context,
options,
env,
tagsFile,
});
}
return Promise.all(docFiles.map(processVersionDoc));
}
async function doLoadVersion(
versionMetadata: VersionMetadata,
): Promise<LoadedVersion> {
const tagsFile = await getTagsFile({
contentPaths: versionMetadata,
tags: options.tags,
});
const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
versionMetadata,
tagsFile,
);
// TODO we only ever need draftIds in further code, not full draft items
// To simplify and prevent mistakes, avoid exposing draft
// replace draft=>draftIds in content loaded
const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: options.sidebarItemsGenerator,
numberPrefixParser: options.numberPrefixParser,
docs,
drafts,
version: versionMetadata,
sidebarOptions: {
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
},
categoryLabelSlugger: createSlugger(),
});
const sidebarsUtils = createSidebarsUtils(sidebars);
const docsById = createDocsByIdIndex(docs);
const allDocIds = Object.keys(docsById);
sidebarsUtils.checkLegacyVersionedSidebarNames({
sidebarFilePath: versionMetadata.sidebarFilePath as string,
versionMetadata,
});
sidebarsUtils.checkSidebarsDocIds({
allDocIds,
sidebarFilePath: versionMetadata.sidebarFilePath as string,
versionMetadata,
});
return {
...versionMetadata,
docs: addDocNavigation({
docs,
sidebarsUtils,
}),
drafts,
sidebars,
};
}
async function loadVersion(versionMetadata: VersionMetadata) {
try {
return await doLoadVersion(versionMetadata);
} catch (err) {
logger.error`Loading of version failed for version name=${versionMetadata.versionName}`;
throw err;
}
}
return {
loadedVersions: await Promise.all(versionsMetadata.map(loadVersion)),
loadedVersions: await Promise.all(
versionsMetadata.map((versionMetadata) =>
loadVersion({
context,
options,
env,
versionMetadata,
}),
),
),
};
},

View file

@ -22,5 +22,5 @@ export {
getDefaultVersionBanner,
getVersionBadge,
getVersionBanner,
} from './versions';
} from './versions/version';
export {readVersionNames} from './versions/files';

View file

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`minimal site can load current version 1`] = `
{
"badge": false,
"banner": null,
"className": "docs-version-current",
"contentPath": "<PROJECT_ROOT>/packages/docusaurus-plugin-content-docs/src/versions/__tests__/__fixtures__/minimal-site/docs",
"contentPathLocalized": "<PROJECT_ROOT>/packages/docusaurus-plugin-content-docs/src/versions/__tests__/__fixtures__/minimal-site/i18n/en/docusaurus-plugin-content-docs/current",
"docs": [
{
"description": "World",
"draft": false,
"editUrl": undefined,
"frontMatter": {},
"id": "hello",
"lastUpdatedAt": undefined,
"lastUpdatedBy": undefined,
"next": undefined,
"permalink": "/docs/hello",
"previous": undefined,
"sidebar": "defaultSidebar",
"sidebarPosition": undefined,
"slug": "/hello",
"source": "@site/docs/hello.md",
"sourceDirName": ".",
"tags": [],
"title": "Hello",
"unlisted": false,
"version": "current",
},
],
"drafts": [],
"editUrl": undefined,
"editUrlLocalized": undefined,
"isLast": true,
"label": "Next",
"noIndex": false,
"path": "/docs",
"routePriority": -1,
"sidebarFilePath": undefined,
"sidebars": {
"defaultSidebar": [
{
"id": "hello",
"type": "doc",
},
],
},
"tagsPath": "/docs/tags",
"versionName": "current",
}
`;

View file

@ -0,0 +1,73 @@
/**
* 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 path from 'path';
import {fromPartial} from '@total-typescript/shoehorn';
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils/src';
import {readVersionsMetadata} from '../version';
import {DEFAULT_OPTIONS} from '../../options';
import {loadVersion} from '../loadVersion';
import type {I18n, LoadContext} from '@docusaurus/types';
import type {PluginOptions} from '@docusaurus/plugin-content-docs';
const DefaultI18N: I18n = {
path: 'i18n',
currentLocale: 'en',
locales: ['en'],
defaultLocale: 'en',
localeConfigs: {},
};
describe('minimal site', () => {
async function loadSite() {
const siteDir = path.resolve(
path.join(__dirname, './__fixtures__', 'minimal-site'),
);
const options: PluginOptions = fromPartial<PluginOptions>({
...DEFAULT_OPTIONS,
});
const context = fromPartial<LoadContext>({
siteDir,
baseUrl: '/',
i18n: DefaultI18N,
localizationDir: path.join(siteDir, 'i18n/en'),
siteConfig: {
markdown: {
parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
},
},
});
return {
siteDir,
options,
context,
};
}
it('can load current version', async () => {
const {options, context} = await loadSite();
const versionsMetadata = await readVersionsMetadata({
options,
context,
});
expect(versionsMetadata).toHaveLength(1);
expect(versionsMetadata[0]!.versionName).toBe('current');
const versionMetadata = versionsMetadata[0]!;
const loadedVersion = loadVersion({
context,
options,
versionMetadata,
env: 'production',
});
await expect(loadedVersion).resolves.toMatchSnapshot();
});
});

View file

@ -8,7 +8,7 @@
import {jest} from '@jest/globals';
import path from 'path';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import {readVersionsMetadata} from '../index';
import {readVersionsMetadata} from '../version';
import {DEFAULT_OPTIONS} from '../../options';
import type {I18n, LoadContext} from '@docusaurus/types';
import type {

View file

@ -19,7 +19,7 @@ import type {
PluginOptions,
VersionMetadata,
} from '@docusaurus/plugin-content-docs';
import type {VersionContext} from './index';
import type {VersionContext} from './version';
/** Add a prefix like `community_version-1.0.0`. No-op for default instance. */
function addPluginIdPrefix(fileOrDir: string, pluginId: string): string {

View file

@ -0,0 +1,130 @@
/**
* 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 path from 'path';
import _ from 'lodash';
import {createSlugger} from '@docusaurus/utils';
import {getTagsFile} from '@docusaurus/utils-validation';
import logger from '@docusaurus/logger';
import {
addDocNavigation,
createDocsByIdIndex,
type DocEnv,
processDocMetadata,
readVersionDocs,
} from '../docs';
import {loadSidebars} from '../sidebars';
import {createSidebarsUtils} from '../sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type {
DocMetadataBase,
LoadedVersion,
PluginOptions,
VersionMetadata,
} from '@docusaurus/plugin-content-docs';
import type {DocFile} from '../types';
import type {LoadContext} from '@docusaurus/types';
export async function loadVersion({
context,
options,
versionMetadata,
env,
}: {
context: LoadContext;
options: PluginOptions;
versionMetadata: VersionMetadata;
env: DocEnv;
}): Promise<LoadedVersion> {
const {siteDir} = context;
async function loadVersionDocsBase(
tagsFile: TagsFile | null,
): Promise<DocMetadataBase[]> {
const docFiles = await readVersionDocs(versionMetadata, options);
if (docFiles.length === 0) {
throw new Error(
`Docs version "${
versionMetadata.versionName
}" has no docs! At least one doc should exist at "${path.relative(
siteDir,
versionMetadata.contentPath,
)}".`,
);
}
function processVersionDoc(docFile: DocFile) {
return processDocMetadata({
docFile,
versionMetadata,
context,
options,
env,
tagsFile,
});
}
return Promise.all(docFiles.map(processVersionDoc));
}
async function doLoadVersion(): Promise<LoadedVersion> {
const tagsFile = await getTagsFile({
contentPaths: versionMetadata,
tags: options.tags,
});
const docsBase: DocMetadataBase[] = await loadVersionDocsBase(tagsFile);
// TODO we only ever need draftIds in further code, not full draft items
// To simplify and prevent mistakes, avoid exposing draft
// replace draft=>draftIds in content loaded
const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: options.sidebarItemsGenerator,
numberPrefixParser: options.numberPrefixParser,
docs,
drafts,
version: versionMetadata,
sidebarOptions: {
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
},
categoryLabelSlugger: createSlugger(),
});
const sidebarsUtils = createSidebarsUtils(sidebars);
const docsById = createDocsByIdIndex(docs);
const allDocIds = Object.keys(docsById);
sidebarsUtils.checkLegacyVersionedSidebarNames({
sidebarFilePath: versionMetadata.sidebarFilePath as string,
versionMetadata,
});
sidebarsUtils.checkSidebarsDocIds({
allDocIds,
sidebarFilePath: versionMetadata.sidebarFilePath as string,
versionMetadata,
});
return {
...versionMetadata,
docs: addDocNavigation({
docs,
sidebarsUtils,
}),
drafts,
sidebars,
};
}
try {
return await doLoadVersion();
} catch (err) {
logger.error`Loading of version failed for version name=${versionMetadata.versionName}`;
throw err;
}
}

View file

@ -243,7 +243,7 @@ export async function readVersionsMetadata({
validateVersionsOptions(allVersionNames, options);
const versionNames = filterVersions(allVersionNames, options);
const lastVersionName = getLastVersionName({versionNames, options});
const versionsMetadata = await Promise.all(
return Promise.all(
versionNames.map((versionName) =>
createVersionMetadata({
versionName,
@ -254,7 +254,6 @@ export async function readVersionsMetadata({
}),
),
);
return versionsMetadata;
}
export function toFullVersion(version: LoadedVersion): FullVersion {