mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 00:27:21 +02:00
refactor(core): reorganize functions (#7037)
This commit is contained in:
parent
c81d21a641
commit
85a79fd9b9
64 changed files with 1207 additions and 1304 deletions
|
@ -17,7 +17,7 @@ import {loadContext} from '@docusaurus/core/src/server/index';
|
||||||
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
|
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
|
||||||
import type {RouteConfig} from '@docusaurus/types';
|
import type {RouteConfig} from '@docusaurus/types';
|
||||||
import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
||||||
import {sortConfig} from '@docusaurus/core/src/server/plugins';
|
import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig';
|
||||||
|
|
||||||
import * as cliDocs from '../cli';
|
import * as cliDocs from '../cli';
|
||||||
import {validateOptions} from '../options';
|
import {validateOptions} from '../options';
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
mergeTranslations,
|
mergeTranslations,
|
||||||
updateTranslationFileMessages,
|
updateTranslationFileMessages,
|
||||||
getPluginI18nPath,
|
getPluginI18nPath,
|
||||||
|
localizePath,
|
||||||
} from '../i18nUtils';
|
} from '../i18nUtils';
|
||||||
|
|
||||||
describe('mergeTranslations', () => {
|
describe('mergeTranslations', () => {
|
||||||
|
@ -93,3 +95,85 @@ describe('getPluginI18nPath', () => {
|
||||||
).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`);
|
).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('localizePath', () => {
|
||||||
|
it('localizes url path with current locale', () => {
|
||||||
|
expect(
|
||||||
|
localizePath({
|
||||||
|
pathType: 'url',
|
||||||
|
path: '/baseUrl',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
currentLocale: 'fr',
|
||||||
|
localeConfigs: {},
|
||||||
|
},
|
||||||
|
options: {localizePath: true},
|
||||||
|
}),
|
||||||
|
).toBe('/baseUrl/fr/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('localizes fs path with current locale', () => {
|
||||||
|
expect(
|
||||||
|
localizePath({
|
||||||
|
pathType: 'fs',
|
||||||
|
path: '/baseFsPath',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
currentLocale: 'fr',
|
||||||
|
localeConfigs: {},
|
||||||
|
},
|
||||||
|
options: {localizePath: true},
|
||||||
|
}),
|
||||||
|
).toBe(`${path.sep}baseFsPath${path.sep}fr`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('localizes path for default locale, if requested', () => {
|
||||||
|
expect(
|
||||||
|
localizePath({
|
||||||
|
pathType: 'url',
|
||||||
|
path: '/baseUrl/',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
currentLocale: 'en',
|
||||||
|
localeConfigs: {},
|
||||||
|
},
|
||||||
|
options: {localizePath: true},
|
||||||
|
}),
|
||||||
|
).toBe('/baseUrl/en/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not localize path for default locale by default', () => {
|
||||||
|
expect(
|
||||||
|
localizePath({
|
||||||
|
pathType: 'url',
|
||||||
|
path: '/baseUrl/',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
currentLocale: 'en',
|
||||||
|
localeConfigs: {},
|
||||||
|
},
|
||||||
|
// options: {localizePath: true},
|
||||||
|
}),
|
||||||
|
).toBe('/baseUrl/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('localizes path for non-default locale by default', () => {
|
||||||
|
expect(
|
||||||
|
localizePath({
|
||||||
|
pathType: 'url',
|
||||||
|
path: '/baseUrl/',
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
currentLocale: 'en',
|
||||||
|
localeConfigs: {},
|
||||||
|
},
|
||||||
|
// options: {localizePath: true},
|
||||||
|
}),
|
||||||
|
).toBe('/baseUrl/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -7,8 +7,13 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import type {TranslationFileContent, TranslationFile} from '@docusaurus/types';
|
import type {
|
||||||
|
TranslationFileContent,
|
||||||
|
TranslationFile,
|
||||||
|
I18n,
|
||||||
|
} from '@docusaurus/types';
|
||||||
import {DEFAULT_PLUGIN_ID, I18N_DIR_NAME} from './constants';
|
import {DEFAULT_PLUGIN_ID, I18N_DIR_NAME} from './constants';
|
||||||
|
import {normalizeUrl} from './urlUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a list of translation file contents, and shallow-merges them into one.
|
* Takes a list of translation file contents, and shallow-merges them into one.
|
||||||
|
@ -65,3 +70,46 @@ export function getPluginI18nPath({
|
||||||
...subPaths,
|
...subPaths,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a path and returns a localized a version (which is basically `path +
|
||||||
|
* i18n.currentLocale`).
|
||||||
|
*/
|
||||||
|
export function localizePath({
|
||||||
|
pathType,
|
||||||
|
path: originalPath,
|
||||||
|
i18n,
|
||||||
|
options = {},
|
||||||
|
}: {
|
||||||
|
/**
|
||||||
|
* FS paths will treat Windows specially; URL paths will always have a
|
||||||
|
* trailing slash to make it a valid base URL.
|
||||||
|
*/
|
||||||
|
pathType: 'fs' | 'url';
|
||||||
|
/** The path, URL or file path, to be localized. */
|
||||||
|
path: string;
|
||||||
|
/** The current i18n context. */
|
||||||
|
i18n: I18n;
|
||||||
|
options?: {
|
||||||
|
/**
|
||||||
|
* By default, we don't localize the path of defaultLocale. This option
|
||||||
|
* would override that behavior. Setting `false` is useful for `yarn build
|
||||||
|
* -l zh-Hans` to always emit into the root build directory.
|
||||||
|
*/
|
||||||
|
localizePath?: boolean;
|
||||||
|
};
|
||||||
|
}): string {
|
||||||
|
const shouldLocalizePath: boolean =
|
||||||
|
//
|
||||||
|
options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale;
|
||||||
|
|
||||||
|
if (!shouldLocalizePath) {
|
||||||
|
return originalPath;
|
||||||
|
}
|
||||||
|
// FS paths need special care, for Windows support
|
||||||
|
if (pathType === 'fs') {
|
||||||
|
return path.join(originalPath, i18n.currentLocale);
|
||||||
|
}
|
||||||
|
// Url paths; add a trailing slash so it's a valid base URL
|
||||||
|
return normalizeUrl([originalPath, i18n.currentLocale, '/']);
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ export {
|
||||||
mergeTranslations,
|
mergeTranslations,
|
||||||
updateTranslationFileMessages,
|
updateTranslationFileMessages,
|
||||||
getPluginI18nPath,
|
getPluginI18nPath,
|
||||||
|
localizePath,
|
||||||
} from './i18nUtils';
|
} from './i18nUtils';
|
||||||
export {
|
export {
|
||||||
removeSuffix,
|
removeSuffix,
|
||||||
|
|
|
@ -28,7 +28,7 @@ import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
|
||||||
import {loadI18n} from '../server/i18n';
|
import {loadI18n} from '../server/i18n';
|
||||||
import {mapAsyncSequential} from '@docusaurus/utils';
|
import {mapAsyncSequential} from '@docusaurus/utils';
|
||||||
|
|
||||||
export default async function build(
|
export async function build(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
cliOptions: Partial<BuildCLIOptions> = {},
|
cliOptions: Partial<BuildCLIOptions> = {},
|
||||||
// When running build, we force terminate the process to prevent async
|
// When running build, we force terminate the process to prevent async
|
||||||
|
|
|
@ -26,7 +26,7 @@ async function removePath(entry: {path: string; description: string}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function clear(siteDir: string): Promise<unknown> {
|
export async function clear(siteDir: string): Promise<unknown> {
|
||||||
const generatedFolder = {
|
const generatedFolder = {
|
||||||
path: path.join(siteDir, GENERATED_FILES_DIR_NAME),
|
path: path.join(siteDir, GENERATED_FILES_DIR_NAME),
|
||||||
description: 'generated folder',
|
description: 'generated folder',
|
||||||
|
|
|
@ -10,7 +10,7 @@ import shell from 'shelljs';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import {hasSSHProtocol, buildSshUrl, buildHttpsUrl} from '@docusaurus/utils';
|
import {hasSSHProtocol, buildSshUrl, buildHttpsUrl} from '@docusaurus/utils';
|
||||||
import {loadContext} from '../server';
|
import {loadContext} from '../server';
|
||||||
import build from './build';
|
import {build} from './build';
|
||||||
import type {BuildCLIOptions} from '@docusaurus/types';
|
import type {BuildCLIOptions} from '@docusaurus/types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
@ -34,7 +34,7 @@ function shellExecLog(cmd: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function deploy(
|
export async function deploy(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
cliOptions: Partial<BuildCLIOptions> = {},
|
cliOptions: Partial<BuildCLIOptions> = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
|
@ -6,16 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {CommanderStatic} from 'commander';
|
import type {CommanderStatic} from 'commander';
|
||||||
import {loadContext, loadPluginConfigs} from '../server';
|
import {loadContext} from '../server';
|
||||||
import initPlugins from '../server/plugins/init';
|
import {initPlugins} from '../server/plugins/init';
|
||||||
|
|
||||||
export default async function externalCommand(
|
export async function externalCommand(
|
||||||
cli: CommanderStatic,
|
cli: CommanderStatic,
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const context = await loadContext(siteDir);
|
const context = await loadContext(siteDir);
|
||||||
const pluginConfigs = await loadPluginConfigs(context);
|
const plugins = await initPlugins(context);
|
||||||
const plugins = await initPlugins({pluginConfigs, context});
|
|
||||||
|
|
||||||
// Plugin Lifecycle - extendCli.
|
// Plugin Lifecycle - extendCli.
|
||||||
plugins.forEach((plugin) => {
|
plugins.forEach((plugin) => {
|
||||||
|
|
|
@ -10,11 +10,11 @@ import serveHandler from 'serve-handler';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {loadSiteConfig} from '../server';
|
import {loadSiteConfig} from '../server';
|
||||||
import build from './build';
|
import {build} from './build';
|
||||||
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
||||||
import type {ServeCLIOptions} from '@docusaurus/types';
|
import type {ServeCLIOptions} from '@docusaurus/types';
|
||||||
|
|
||||||
export default async function serve(
|
export async function serve(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
cliOptions: ServeCLIOptions,
|
cliOptions: ServeCLIOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
||||||
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
|
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
|
||||||
|
|
||||||
export default async function start(
|
export async function start(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
cliOptions: Partial<StartCLIOptions>,
|
cliOptions: Partial<StartCLIOptions>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {ThemePath, createTempSiteDir, Components} from './testUtils';
|
import {ThemePath, createTempSiteDir, Components} from './testUtils';
|
||||||
import tree from 'tree-node-cli';
|
import tree from 'tree-node-cli';
|
||||||
import swizzle from '../index';
|
import {swizzle} from '../index';
|
||||||
import {escapePath, Globby, posixPath} from '@docusaurus/utils';
|
import {escapePath, Globby, posixPath} from '@docusaurus/utils';
|
||||||
|
|
||||||
const FixtureThemeName = 'fixture-theme-name';
|
const FixtureThemeName = 'fixture-theme-name';
|
||||||
|
|
|
@ -5,21 +5,17 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {loadContext, loadPluginConfigs} from '../../server';
|
import {loadContext} from '../../server';
|
||||||
import initPlugins, {normalizePluginConfigs} from '../../server/plugins/init';
|
import {initPlugins, normalizePluginConfigs} from '../../server/plugins/init';
|
||||||
import type {InitializedPlugin} from '@docusaurus/types';
|
import {loadPluginConfigs} from '../../server/plugins/configs';
|
||||||
import type {SwizzleContext} from './common';
|
import type {SwizzleContext} from './common';
|
||||||
|
|
||||||
export async function initSwizzleContext(
|
export async function initSwizzleContext(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
): Promise<SwizzleContext> {
|
): Promise<SwizzleContext> {
|
||||||
const context = await loadContext(siteDir);
|
const context = await loadContext(siteDir);
|
||||||
|
const plugins = await initPlugins(context);
|
||||||
const pluginConfigs = await loadPluginConfigs(context);
|
const pluginConfigs = await loadPluginConfigs(context);
|
||||||
const plugins: InitializedPlugin[] = await initPlugins({
|
|
||||||
pluginConfigs,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pluginsNormalized = await normalizePluginConfigs(
|
const pluginsNormalized = await normalizePluginConfigs(
|
||||||
pluginConfigs,
|
pluginConfigs,
|
||||||
|
|
|
@ -86,7 +86,7 @@ If you want to swizzle it, use the code=${'--danger'} flag, or confirm that you
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function swizzle(
|
export async function swizzle(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
themeNameParam: string | undefined,
|
themeNameParam: string | undefined,
|
||||||
componentNameParam: string | undefined,
|
componentNameParam: string | undefined,
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
writeMarkdownHeadingId,
|
writeMarkdownHeadingId,
|
||||||
type WriteHeadingIDOptions,
|
type WriteHeadingIDOptions,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {loadContext, loadPluginConfigs} from '../server';
|
import {loadContext} from '../server';
|
||||||
import initPlugins from '../server/plugins/init';
|
import {initPlugins} from '../server/plugins/init';
|
||||||
import {safeGlobby} from '../server/utils';
|
import {safeGlobby} from '../server/utils';
|
||||||
|
|
||||||
async function transformMarkdownFile(
|
async function transformMarkdownFile(
|
||||||
|
@ -36,15 +36,11 @@ async function transformMarkdownFile(
|
||||||
*/
|
*/
|
||||||
async function getPathsToWatch(siteDir: string): Promise<string[]> {
|
async function getPathsToWatch(siteDir: string): Promise<string[]> {
|
||||||
const context = await loadContext(siteDir);
|
const context = await loadContext(siteDir);
|
||||||
const pluginConfigs = await loadPluginConfigs(context);
|
const plugins = await initPlugins(context);
|
||||||
const plugins = await initPlugins({
|
|
||||||
pluginConfigs,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
return plugins.flatMap((plugin) => plugin?.getPathsToWatch?.() ?? []);
|
return plugins.flatMap((plugin) => plugin?.getPathsToWatch?.() ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function writeHeadingIds(
|
export async function writeHeadingIds(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
files?: string[],
|
files?: string[],
|
||||||
options?: WriteHeadingIDOptions,
|
options?: WriteHeadingIDOptions,
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
import type {ConfigOptions, InitializedPlugin} from '@docusaurus/types';
|
import type {ConfigOptions, InitializedPlugin} from '@docusaurus/types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {loadContext, loadPluginConfigs} from '../server';
|
import {loadContext} from '../server';
|
||||||
import initPlugins from '../server/plugins/init';
|
import {initPlugins} from '../server/plugins/init';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
writePluginTranslations,
|
writePluginTranslations,
|
||||||
|
@ -72,7 +72,7 @@ async function writePluginTranslationFiles({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function writeTranslations(
|
export async function writeTranslations(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
options: WriteTranslationsOptions & ConfigOptions & {locale?: string},
|
options: WriteTranslationsOptions & ConfigOptions & {locale?: string},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -80,11 +80,7 @@ export default async function writeTranslations(
|
||||||
customConfigFilePath: options.config,
|
customConfigFilePath: options.config,
|
||||||
locale: options.locale,
|
locale: options.locale,
|
||||||
});
|
});
|
||||||
const pluginConfigs = await loadPluginConfigs(context);
|
const plugins = await initPlugins(context);
|
||||||
const plugins = await initPlugins({
|
|
||||||
pluginConfigs,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
const locale = options.locale ?? context.i18n.defaultLocale;
|
const locale = options.locale ?? context.i18n.defaultLocale;
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,12 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import build from './commands/build';
|
export {build} from './commands/build';
|
||||||
import clear from './commands/clear';
|
export {clear} from './commands/clear';
|
||||||
import deploy from './commands/deploy';
|
export {deploy} from './commands/deploy';
|
||||||
import externalCommand from './commands/external';
|
export {externalCommand} from './commands/external';
|
||||||
import serve from './commands/serve';
|
export {serve} from './commands/serve';
|
||||||
import start from './commands/start';
|
export {start} from './commands/start';
|
||||||
import swizzle from './commands/swizzle';
|
export {swizzle} from './commands/swizzle';
|
||||||
import writeHeadingIds from './commands/writeHeadingIds';
|
export {writeHeadingIds} from './commands/writeHeadingIds';
|
||||||
import writeTranslations from './commands/writeTranslations';
|
export {writeTranslations} from './commands/writeTranslations';
|
||||||
|
|
||||||
export {
|
|
||||||
build,
|
|
||||||
clear,
|
|
||||||
deploy,
|
|
||||||
externalCommand,
|
|
||||||
serve,
|
|
||||||
start,
|
|
||||||
swizzle,
|
|
||||||
writeHeadingIds,
|
|
||||||
writeTranslations,
|
|
||||||
};
|
|
||||||
|
|
107
packages/docusaurus/src/server/__tests__/clientModules.test.ts
Normal file
107
packages/docusaurus/src/server/__tests__/clientModules.test.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* 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 {loadClientModules} from '../clientModules';
|
||||||
|
import type {LoadedPlugin} from '@docusaurus/types';
|
||||||
|
|
||||||
|
const pluginEmpty: LoadedPlugin = {
|
||||||
|
name: 'plugin-empty',
|
||||||
|
path: __dirname,
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginFooBar: LoadedPlugin = {
|
||||||
|
name: 'plugin-foo-bar',
|
||||||
|
path: __dirname,
|
||||||
|
getClientModules() {
|
||||||
|
return ['foo', 'bar'];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginHelloWorld: LoadedPlugin = {
|
||||||
|
plugin: 'plugin-hello-world',
|
||||||
|
path: __dirname,
|
||||||
|
getClientModules() {
|
||||||
|
return [
|
||||||
|
// Absolute path
|
||||||
|
'/hello',
|
||||||
|
'world',
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('loadClientModules', () => {
|
||||||
|
it('loads an empty plugin', () => {
|
||||||
|
const clientModules = loadClientModules([pluginEmpty]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`[]`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads a non-empty plugin', () => {
|
||||||
|
const clientModules = loadClientModules([pluginFooBar]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/bar",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads multiple non-empty plugins', () => {
|
||||||
|
const clientModules = loadClientModules([pluginFooBar, pluginHelloWorld]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/bar",
|
||||||
|
"/hello",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/world",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads multiple non-empty plugins in different order', () => {
|
||||||
|
const clientModules = loadClientModules([pluginHelloWorld, pluginFooBar]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"/hello",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/world",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/bar",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads both empty and non-empty plugins', () => {
|
||||||
|
const clientModules = loadClientModules([
|
||||||
|
pluginHelloWorld,
|
||||||
|
pluginEmpty,
|
||||||
|
pluginFooBar,
|
||||||
|
]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"/hello",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/world",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/bar",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads empty and non-empty in a different order', () => {
|
||||||
|
const clientModules = loadClientModules([
|
||||||
|
pluginHelloWorld,
|
||||||
|
pluginFooBar,
|
||||||
|
pluginEmpty,
|
||||||
|
]);
|
||||||
|
expect(clientModules).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"/hello",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/world",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||||
|
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/bar",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import loadConfig from '../config';
|
import {loadConfig} from '../config';
|
||||||
|
|
||||||
describe('loadConfig', () => {
|
describe('loadConfig', () => {
|
||||||
it('website with valid siteConfig', async () => {
|
it('website with valid siteConfig', async () => {
|
||||||
|
|
224
packages/docusaurus/src/server/__tests__/htmlTags.test.ts
Normal file
224
packages/docusaurus/src/server/__tests__/htmlTags.test.ts
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
/**
|
||||||
|
* 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 {loadHtmlTags} from '../htmlTags';
|
||||||
|
import type {LoadedPlugin} from '@docusaurus/types';
|
||||||
|
|
||||||
|
const pluginEmpty: LoadedPlugin = {
|
||||||
|
name: 'plugin-empty',
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginPreBodyTags: LoadedPlugin = {
|
||||||
|
name: 'plugin-preBodyTags',
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
preBodyTags: {
|
||||||
|
tagName: 'script',
|
||||||
|
attributes: {
|
||||||
|
type: 'text/javascript',
|
||||||
|
async: false,
|
||||||
|
},
|
||||||
|
innerHTML: 'window.foo = null;',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginHeadTags: LoadedPlugin = {
|
||||||
|
name: 'plugin-headTags-only',
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
headTags: [
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
rel: 'preconnect',
|
||||||
|
href: 'www.google-analytics.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'meta',
|
||||||
|
attributes: {
|
||||||
|
name: 'generator',
|
||||||
|
content: 'Docusaurus',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'script',
|
||||||
|
attributes: {
|
||||||
|
type: 'text/javascript',
|
||||||
|
src: 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js',
|
||||||
|
async: true,
|
||||||
|
'data-options': '{"prop":true}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginPostBodyTags: LoadedPlugin = {
|
||||||
|
name: 'plugin-postBody-tags',
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
postBodyTags: [
|
||||||
|
{
|
||||||
|
tagName: 'div',
|
||||||
|
innerHTML: 'Test content',
|
||||||
|
},
|
||||||
|
'<script>window.alert(1);</script>',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pluginMaybeInjectHeadTags: LoadedPlugin = {
|
||||||
|
name: 'plugin-postBody-tags',
|
||||||
|
injectHtmlTags() {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('loadHtmlTags', () => {
|
||||||
|
it('works for an empty plugin', () => {
|
||||||
|
const htmlTags = loadHtmlTags([pluginEmpty]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "",
|
||||||
|
"postBodyTags": "",
|
||||||
|
"preBodyTags": "",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only injects headTags', () => {
|
||||||
|
const htmlTags = loadHtmlTags([pluginHeadTags]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
||||||
|
<meta name=\\"generator\\" content=\\"Docusaurus\\">
|
||||||
|
<script type=\\"text/javascript\\" src=\\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\" async data-options=\\"{"prop":true}\\"></script>",
|
||||||
|
"postBodyTags": "",
|
||||||
|
"preBodyTags": "",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only injects preBodyTags', () => {
|
||||||
|
const htmlTags = loadHtmlTags([pluginPreBodyTags]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "",
|
||||||
|
"postBodyTags": "",
|
||||||
|
"preBodyTags": "<script type=\\"text/javascript\\">window.foo = null;</script>",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only injects postBodyTags', () => {
|
||||||
|
const htmlTags = loadHtmlTags([pluginPostBodyTags]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "",
|
||||||
|
"postBodyTags": "<div>Test content</div>
|
||||||
|
<script>window.alert(1);</script>",
|
||||||
|
"preBodyTags": "",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows multiple plugins that inject different part of html tags', () => {
|
||||||
|
const htmlTags = loadHtmlTags([
|
||||||
|
pluginHeadTags,
|
||||||
|
pluginPostBodyTags,
|
||||||
|
pluginPreBodyTags,
|
||||||
|
]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
||||||
|
<meta name=\\"generator\\" content=\\"Docusaurus\\">
|
||||||
|
<script type=\\"text/javascript\\" src=\\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\" async data-options=\\"{"prop":true}\\"></script>",
|
||||||
|
"postBodyTags": "<div>Test content</div>
|
||||||
|
<script>window.alert(1);</script>",
|
||||||
|
"preBodyTags": "<script type=\\"text/javascript\\">window.foo = null;</script>",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows multiple plugins that might/might not inject html tags', () => {
|
||||||
|
const htmlTags = loadHtmlTags([
|
||||||
|
pluginEmpty,
|
||||||
|
pluginHeadTags,
|
||||||
|
pluginPostBodyTags,
|
||||||
|
pluginMaybeInjectHeadTags,
|
||||||
|
]);
|
||||||
|
expect(htmlTags).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
||||||
|
<meta name=\\"generator\\" content=\\"Docusaurus\\">
|
||||||
|
<script type=\\"text/javascript\\" src=\\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\" async data-options=\\"{"prop":true}\\"></script>",
|
||||||
|
"postBodyTags": "<div>Test content</div>
|
||||||
|
<script>window.alert(1);</script>",
|
||||||
|
"preBodyTags": "",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('throws for invalid tag', () => {
|
||||||
|
expect(() =>
|
||||||
|
loadHtmlTags([
|
||||||
|
{
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
headTags: {
|
||||||
|
tagName: 'endiliey',
|
||||||
|
attributes: {
|
||||||
|
this: 'is invalid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Error loading {\\"tagName\\":\\"endiliey\\",\\"attributes\\":{\\"this\\":\\"is invalid\\"}}, \\"endiliey\\" is not a valid HTML tag."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws for invalid tagName', () => {
|
||||||
|
expect(() =>
|
||||||
|
loadHtmlTags([
|
||||||
|
{
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
headTags: {
|
||||||
|
tagName: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"{\\"tagName\\":true} is not a valid HTML tag object. \\"tagName\\" must be defined as a string."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws for invalid tag object', () => {
|
||||||
|
expect(() =>
|
||||||
|
loadHtmlTags([
|
||||||
|
{
|
||||||
|
injectHtmlTags() {
|
||||||
|
return {
|
||||||
|
headTags: 2,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"\\"2\\" is not a valid HTML tag object."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,9 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import {loadI18n, localizePath, getDefaultLocaleConfig} from '../i18n';
|
import {loadI18n, getDefaultLocaleConfig} from '../i18n';
|
||||||
import {DEFAULT_I18N_CONFIG} from '../configValidation';
|
import {DEFAULT_I18N_CONFIG} from '../configValidation';
|
||||||
import path from 'path';
|
|
||||||
import type {I18nConfig} from '@docusaurus/types';
|
import type {I18nConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
function testLocaleConfigsFor(locales: string[]) {
|
function testLocaleConfigsFor(locales: string[]) {
|
||||||
|
@ -166,85 +165,3 @@ describe('loadI18n', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('localizePath', () => {
|
|
||||||
it('localizes url path with current locale', () => {
|
|
||||||
expect(
|
|
||||||
localizePath({
|
|
||||||
pathType: 'url',
|
|
||||||
path: '/baseUrl',
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
currentLocale: 'fr',
|
|
||||||
localeConfigs: {},
|
|
||||||
},
|
|
||||||
options: {localizePath: true},
|
|
||||||
}),
|
|
||||||
).toBe('/baseUrl/fr/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('localizes fs path with current locale', () => {
|
|
||||||
expect(
|
|
||||||
localizePath({
|
|
||||||
pathType: 'fs',
|
|
||||||
path: '/baseFsPath',
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
currentLocale: 'fr',
|
|
||||||
localeConfigs: {},
|
|
||||||
},
|
|
||||||
options: {localizePath: true},
|
|
||||||
}),
|
|
||||||
).toBe(`${path.sep}baseFsPath${path.sep}fr`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('localizes path for default locale, if requested', () => {
|
|
||||||
expect(
|
|
||||||
localizePath({
|
|
||||||
pathType: 'url',
|
|
||||||
path: '/baseUrl/',
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
currentLocale: 'en',
|
|
||||||
localeConfigs: {},
|
|
||||||
},
|
|
||||||
options: {localizePath: true},
|
|
||||||
}),
|
|
||||||
).toBe('/baseUrl/en/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not localize path for default locale by default', () => {
|
|
||||||
expect(
|
|
||||||
localizePath({
|
|
||||||
pathType: 'url',
|
|
||||||
path: '/baseUrl/',
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
currentLocale: 'en',
|
|
||||||
localeConfigs: {},
|
|
||||||
},
|
|
||||||
// options: {localizePath: true},
|
|
||||||
}),
|
|
||||||
).toBe('/baseUrl/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('localizes path for non-default locale by default', () => {
|
|
||||||
expect(
|
|
||||||
localizePath({
|
|
||||||
pathType: 'url',
|
|
||||||
path: '/baseUrl/',
|
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en',
|
|
||||||
locales: ['en', 'fr'],
|
|
||||||
currentLocale: 'en',
|
|
||||||
localeConfigs: {},
|
|
||||||
},
|
|
||||||
// options: {localizePath: true},
|
|
||||||
}),
|
|
||||||
).toBe('/baseUrl/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import loadRoutes from '../routes';
|
import {loadRoutes} from '../routes';
|
||||||
import type {RouteConfig} from '@docusaurus/types';
|
import type {RouteConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
describe('loadRoutes', () => {
|
describe('loadRoutes', () => {
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getPluginVersion} from '..';
|
import {getPluginVersion} from '../siteMetadata';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
describe('getPluginVersion', () => {
|
describe('getPluginVersion', () => {
|
||||||
it('detects external packages plugins versions', async () => {
|
it('detects external packages plugins versions', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
getPluginVersion(
|
getPluginVersion(
|
||||||
path.join(__dirname, '__fixtures__/dummy-plugin.js'),
|
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
|
||||||
// Make the plugin appear external.
|
// Make the plugin appear external.
|
||||||
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
|
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
|
||||||
),
|
),
|
||||||
|
@ -22,14 +22,14 @@ describe('getPluginVersion', () => {
|
||||||
it('detects project plugins versions', async () => {
|
it('detects project plugins versions', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
getPluginVersion(
|
getPluginVersion(
|
||||||
path.join(__dirname, '__fixtures__/dummy-plugin.js'),
|
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
|
||||||
// Make the plugin appear project local.
|
// Make the plugin appear project local.
|
||||||
path.join(__dirname, '__fixtures__'),
|
path.join(__dirname, '__fixtures__/siteMetadata'),
|
||||||
),
|
),
|
||||||
).resolves.toEqual({type: 'project'});
|
).resolves.toEqual({type: 'project'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detect local packages versions', async () => {
|
it('detects local packages versions', async () => {
|
||||||
await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
|
await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-empty',
|
|
||||||
path: __dirname,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-foo-bar',
|
|
||||||
path: __dirname,
|
|
||||||
getClientModules() {
|
|
||||||
return ['foo', 'bar'];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
plugin: 'plugin-hello-world',
|
|
||||||
path: __dirname,
|
|
||||||
getClientModules() {
|
|
||||||
return ['hello', 'world'];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,91 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 loadClientModules from '../index';
|
|
||||||
|
|
||||||
import pluginEmpty from './__fixtures__/plugin-empty';
|
|
||||||
import pluginFooBar from './__fixtures__/plugin-foo-bar';
|
|
||||||
import pluginHelloWorld from './__fixtures__/plugin-hello-world';
|
|
||||||
|
|
||||||
describe('loadClientModules', () => {
|
|
||||||
it('empty', () => {
|
|
||||||
const clientModules = loadClientModules([pluginEmpty()]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`[]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('non-empty', () => {
|
|
||||||
const clientModules = loadClientModules([pluginFooBar()]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('multiple non-empty', () => {
|
|
||||||
const clientModules = loadClientModules([
|
|
||||||
pluginFooBar(),
|
|
||||||
pluginHelloWorld(),
|
|
||||||
]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('multiple non-empty different order', () => {
|
|
||||||
const clientModules = loadClientModules([
|
|
||||||
pluginHelloWorld(),
|
|
||||||
pluginFooBar(),
|
|
||||||
]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('empty and non-empty', () => {
|
|
||||||
const clientModules = loadClientModules([
|
|
||||||
pluginHelloWorld(),
|
|
||||||
pluginEmpty(),
|
|
||||||
pluginFooBar(),
|
|
||||||
]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('empty and non-empty different order', () => {
|
|
||||||
const clientModules = loadClientModules([
|
|
||||||
pluginHelloWorld(),
|
|
||||||
pluginFooBar(),
|
|
||||||
pluginEmpty(),
|
|
||||||
]);
|
|
||||||
expect(clientModules).toMatchInlineSnapshot(`
|
|
||||||
[
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/hello",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/world",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/foo",
|
|
||||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/bar",
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -8,9 +8,7 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type {LoadedPlugin} from '@docusaurus/types';
|
import type {LoadedPlugin} from '@docusaurus/types';
|
||||||
|
|
||||||
export default function loadClientModules(
|
export function loadClientModules(plugins: LoadedPlugin<unknown>[]): string[] {
|
||||||
plugins: LoadedPlugin<unknown>[],
|
|
||||||
): string[] {
|
|
||||||
return plugins.flatMap(
|
return plugins.flatMap(
|
||||||
(plugin) =>
|
(plugin) =>
|
||||||
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??
|
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??
|
|
@ -10,7 +10,7 @@ import importFresh from 'import-fresh';
|
||||||
import type {DocusaurusConfig} from '@docusaurus/types';
|
import type {DocusaurusConfig} from '@docusaurus/types';
|
||||||
import {validateConfig} from './configValidation';
|
import {validateConfig} from './configValidation';
|
||||||
|
|
||||||
export default async function loadConfig(
|
export async function loadConfig(
|
||||||
configPath: string,
|
configPath: string,
|
||||||
): Promise<DocusaurusConfig> {
|
): Promise<DocusaurusConfig> {
|
||||||
if (!(await fs.pathExists(configPath))) {
|
if (!(await fs.pathExists(configPath))) {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-empty',
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-headTags-only',
|
|
||||||
injectHtmlTags() {
|
|
||||||
return {
|
|
||||||
headTags: [
|
|
||||||
{
|
|
||||||
tagName: 'link',
|
|
||||||
attributes: {
|
|
||||||
rel: 'preconnect',
|
|
||||||
href: 'www.google-analytics.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`<meta name="generator" content="docusaurus">`,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-postBody-tags',
|
|
||||||
injectHtmlTags() {
|
|
||||||
return {
|
|
||||||
postBodyTags: [
|
|
||||||
{
|
|
||||||
tagName: 'div',
|
|
||||||
innerHTML: 'Test content',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
return {
|
|
||||||
name: 'plugin-preBodyTags',
|
|
||||||
injectHtmlTags() {
|
|
||||||
return {
|
|
||||||
preBodyTags: {
|
|
||||||
tagName: 'script',
|
|
||||||
attributes: {
|
|
||||||
type: 'text/javascript',
|
|
||||||
},
|
|
||||||
innerHTML: 'window.foo = null;',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,122 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 htmlTagObjectToString from '../htmlTags';
|
|
||||||
|
|
||||||
describe('htmlTagObjectToString', () => {
|
|
||||||
it('valid html tag', () => {
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'script',
|
|
||||||
attributes: {
|
|
||||||
type: 'text/javascript',
|
|
||||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js',
|
|
||||||
async: true,
|
|
||||||
'data-options': '{"prop":true}',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(
|
|
||||||
`"<script type=\\"text/javascript\\" src=\\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\" async data-options=\\"{"prop":true}\\"></script>"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'link',
|
|
||||||
attributes: {
|
|
||||||
rel: 'preconnect',
|
|
||||||
href: 'www.google-analytics.com',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(
|
|
||||||
`"<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'div',
|
|
||||||
attributes: {
|
|
||||||
style: 'background-color:lightblue',
|
|
||||||
},
|
|
||||||
innerHTML: 'Lightblue color here',
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(
|
|
||||||
`"<div style=\\"background-color:lightblue\\">Lightblue color here</div>"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'div',
|
|
||||||
innerHTML: 'Test',
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(`"<div>Test</div>"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('valid html void tag', () => {
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'meta',
|
|
||||||
attributes: {
|
|
||||||
name: 'generator',
|
|
||||||
content: 'Docusaurus',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(
|
|
||||||
`"<meta name=\\"generator\\" content=\\"Docusaurus\\">"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'img',
|
|
||||||
attributes: {
|
|
||||||
src: '/img/docusaurus.png',
|
|
||||||
alt: 'Docusaurus logo',
|
|
||||||
height: '42',
|
|
||||||
width: '42',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toMatchInlineSnapshot(
|
|
||||||
`"<img src=\\"/img/docusaurus.png\\" alt=\\"Docusaurus logo\\" height=\\"42\\" width=\\"42\\">"`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid tag', () => {
|
|
||||||
expect(() =>
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: 'endiliey',
|
|
||||||
attributes: {
|
|
||||||
this: 'is invalid',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
|
||||||
`"Error loading {\\"tagName\\":\\"endiliey\\",\\"attributes\\":{\\"this\\":\\"is invalid\\"}}, \\"endiliey\\" is not a valid HTML tags."`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid tagName', () => {
|
|
||||||
expect(() =>
|
|
||||||
htmlTagObjectToString({
|
|
||||||
tagName: true,
|
|
||||||
}),
|
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
|
||||||
`"{\\"tagName\\":true} is not a valid HTML tag object. \\"tagName\\" must be defined as a string."`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid html tag object', () => {
|
|
||||||
expect(() =>
|
|
||||||
htmlTagObjectToString('foo'),
|
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
|
||||||
`"\\"foo\\" is not a valid HTML tag object."`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
htmlTagObjectToString(null),
|
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
|
||||||
`"\\"null\\" is not a valid HTML tag object."`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,92 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 {loadHtmlTags} from '../index';
|
|
||||||
|
|
||||||
import pluginEmpty from './__fixtures__/plugin-empty';
|
|
||||||
import pluginPreBodyTags from './__fixtures__/plugin-preBodyTags';
|
|
||||||
import pluginHeadTags from './__fixtures__/plugin-headTags';
|
|
||||||
import pluginPostBodyTags from './__fixtures__/plugin-postBodyTags';
|
|
||||||
|
|
||||||
describe('loadHtmlTags', () => {
|
|
||||||
it('empty plugin', () => {
|
|
||||||
const htmlTags = loadHtmlTags([pluginEmpty()]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "",
|
|
||||||
"postBodyTags": "",
|
|
||||||
"preBodyTags": "",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only inject headTags', () => {
|
|
||||||
const htmlTags = loadHtmlTags([pluginHeadTags()]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
|
||||||
<meta name=\\"generator\\" content=\\"docusaurus\\">",
|
|
||||||
"postBodyTags": "",
|
|
||||||
"preBodyTags": "",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only inject preBodyTags', () => {
|
|
||||||
const htmlTags = loadHtmlTags([pluginPreBodyTags()]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "",
|
|
||||||
"postBodyTags": "",
|
|
||||||
"preBodyTags": "<script type=\\"text/javascript\\">window.foo = null;</script>",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only inject postBodyTags', () => {
|
|
||||||
const htmlTags = loadHtmlTags([pluginPostBodyTags()]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "",
|
|
||||||
"postBodyTags": "<div>Test content</div>",
|
|
||||||
"preBodyTags": "",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('multiple plugins that inject different part of html tags', () => {
|
|
||||||
const htmlTags = loadHtmlTags([
|
|
||||||
pluginHeadTags(),
|
|
||||||
pluginPostBodyTags(),
|
|
||||||
pluginPreBodyTags(),
|
|
||||||
]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
|
||||||
<meta name=\\"generator\\" content=\\"docusaurus\\">",
|
|
||||||
"postBodyTags": "<div>Test content</div>",
|
|
||||||
"preBodyTags": "<script type=\\"text/javascript\\">window.foo = null;</script>",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('multiple plugins that might/might not inject html tags', () => {
|
|
||||||
const htmlTags = loadHtmlTags([
|
|
||||||
pluginEmpty(),
|
|
||||||
pluginHeadTags(),
|
|
||||||
pluginPostBodyTags(),
|
|
||||||
]);
|
|
||||||
expect(htmlTags).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"headTags": "<link rel=\\"preconnect\\" href=\\"www.google-analytics.com\\">
|
|
||||||
<meta name=\\"generator\\" content=\\"docusaurus\\">",
|
|
||||||
"postBodyTags": "<div>Test content</div>",
|
|
||||||
"preBodyTags": "",
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,50 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 type {HtmlTagObject} from '@docusaurus/types';
|
|
||||||
import htmlTags from 'html-tags';
|
|
||||||
import voidHtmlTags from 'html-tags/void';
|
|
||||||
import escapeHTML from 'escape-html';
|
|
||||||
|
|
||||||
function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject {
|
|
||||||
if (typeof val !== 'object' || !val) {
|
|
||||||
throw new Error(`"${val}" is not a valid HTML tag object.`);
|
|
||||||
}
|
|
||||||
if (typeof (val as HtmlTagObject).tagName !== 'string') {
|
|
||||||
throw new Error(
|
|
||||||
`${JSON.stringify(
|
|
||||||
val,
|
|
||||||
)} is not a valid HTML tag object. "tagName" must be defined as a string.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function htmlTagObjectToString(tagDefinition: unknown): string {
|
|
||||||
assertIsHtmlTagObject(tagDefinition);
|
|
||||||
if (htmlTags.indexOf(tagDefinition.tagName) === -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Error loading ${JSON.stringify(tagDefinition)}, "${
|
|
||||||
tagDefinition.tagName
|
|
||||||
}" is not a valid HTML tags.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const isVoidTag = voidHtmlTags.indexOf(tagDefinition.tagName) !== -1;
|
|
||||||
const tagAttributes = tagDefinition.attributes ?? {};
|
|
||||||
const attributes = Object.keys(tagAttributes)
|
|
||||||
.filter((attributeName) => tagAttributes[attributeName] !== false)
|
|
||||||
.map((attributeName) => {
|
|
||||||
if (tagAttributes[attributeName] === true) {
|
|
||||||
return attributeName;
|
|
||||||
}
|
|
||||||
return `${attributeName}="${escapeHTML(
|
|
||||||
tagAttributes[attributeName] as string,
|
|
||||||
)}"`;
|
|
||||||
});
|
|
||||||
return `<${[tagDefinition.tagName].concat(attributes).join(' ')}>${
|
|
||||||
(!isVoidTag && tagDefinition.innerHTML) || ''
|
|
||||||
}${isVoidTag ? '' : `</${tagDefinition.tagName}>`}`;
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 htmlTagObjectToString from './htmlTags';
|
|
||||||
import type {
|
|
||||||
InjectedHtmlTags,
|
|
||||||
HtmlTagObject,
|
|
||||||
HtmlTags,
|
|
||||||
LoadedPlugin,
|
|
||||||
} from '@docusaurus/types';
|
|
||||||
|
|
||||||
function toString(val: string | HtmlTagObject): string {
|
|
||||||
return typeof val === 'string' ? val : htmlTagObjectToString(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createHtmlTagsString(tags: HtmlTags): string {
|
|
||||||
return Array.isArray(tags) ? tags.map(toString).join('\n') : toString(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadHtmlTags(plugins: LoadedPlugin[]): InjectedHtmlTags {
|
|
||||||
const htmlTags = plugins.reduce(
|
|
||||||
(acc, plugin) => {
|
|
||||||
if (!plugin.injectHtmlTags) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
const {headTags, preBodyTags, postBodyTags} =
|
|
||||||
plugin.injectHtmlTags({content: plugin.content}) ?? {};
|
|
||||||
return {
|
|
||||||
headTags: headTags
|
|
||||||
? `${acc.headTags}\n${createHtmlTagsString(headTags)}`
|
|
||||||
: acc.headTags,
|
|
||||||
preBodyTags: preBodyTags
|
|
||||||
? `${acc.preBodyTags}\n${createHtmlTagsString(preBodyTags)}`
|
|
||||||
: acc.preBodyTags,
|
|
||||||
postBodyTags: postBodyTags
|
|
||||||
? `${acc.postBodyTags}\n${createHtmlTagsString(postBodyTags)}`
|
|
||||||
: acc.postBodyTags,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{headTags: '', preBodyTags: '', postBodyTags: ''},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
headTags: htmlTags.headTags.trim(),
|
|
||||||
preBodyTags: htmlTags.preBodyTags.trim(),
|
|
||||||
postBodyTags: htmlTags.postBodyTags.trim(),
|
|
||||||
};
|
|
||||||
}
|
|
81
packages/docusaurus/src/server/htmlTags.ts
Normal file
81
packages/docusaurus/src/server/htmlTags.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/**
|
||||||
|
* 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 htmlTags from 'html-tags';
|
||||||
|
import voidHtmlTags from 'html-tags/void';
|
||||||
|
import escapeHTML from 'escape-html';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import type {
|
||||||
|
InjectedHtmlTags,
|
||||||
|
HtmlTagObject,
|
||||||
|
HtmlTags,
|
||||||
|
LoadedPlugin,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
|
function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject {
|
||||||
|
if (typeof val !== 'object' || !val) {
|
||||||
|
throw new Error(`"${val}" is not a valid HTML tag object.`);
|
||||||
|
}
|
||||||
|
if (typeof (val as HtmlTagObject).tagName !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`${JSON.stringify(
|
||||||
|
val,
|
||||||
|
)} is not a valid HTML tag object. "tagName" must be defined as a string.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!htmlTags.includes((val as HtmlTagObject).tagName)) {
|
||||||
|
throw new Error(
|
||||||
|
`Error loading ${JSON.stringify(val)}, "${
|
||||||
|
(val as HtmlTagObject).tagName
|
||||||
|
}" is not a valid HTML tag.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlTagObjectToString(tag: unknown): string {
|
||||||
|
assertIsHtmlTagObject(tag);
|
||||||
|
const isVoidTag = voidHtmlTags.includes(tag.tagName);
|
||||||
|
const tagAttributes = tag.attributes ?? {};
|
||||||
|
const attributes = Object.keys(tagAttributes)
|
||||||
|
.map((attr) => {
|
||||||
|
const value = tagAttributes[attr]!;
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value ? attr : undefined;
|
||||||
|
}
|
||||||
|
return `${attr}="${escapeHTML(value)}"`;
|
||||||
|
})
|
||||||
|
.filter((str): str is string => Boolean(str));
|
||||||
|
const openingTag = `<${[tag.tagName].concat(attributes).join(' ')}>`;
|
||||||
|
const innerHTML = (!isVoidTag && tag.innerHTML) || '';
|
||||||
|
const closingTag = isVoidTag ? '' : `</${tag.tagName}>`;
|
||||||
|
return openingTag + innerHTML + closingTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHtmlTagsString(tags: HtmlTags | undefined): string {
|
||||||
|
return (Array.isArray(tags) ? tags : [tags])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((val) => (typeof val === 'string' ? val : htmlTagObjectToString(val)))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadHtmlTags(plugins: LoadedPlugin[]): InjectedHtmlTags {
|
||||||
|
const pluginHtmlTags = plugins.map(
|
||||||
|
(plugin) => plugin.injectHtmlTags?.({content: plugin.content}) ?? {},
|
||||||
|
);
|
||||||
|
const tagTypes = ['headTags', 'preBodyTags', 'postBodyTags'] as const;
|
||||||
|
return Object.fromEntries(
|
||||||
|
_.zip(
|
||||||
|
tagTypes,
|
||||||
|
tagTypes.map((type) =>
|
||||||
|
pluginHtmlTags
|
||||||
|
.map((tags) => createHtmlTagsString(tags[type]))
|
||||||
|
.join('\n')
|
||||||
|
.trim(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,11 +5,9 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types';
|
|
||||||
import path from 'path';
|
|
||||||
import {normalizeUrl} from '@docusaurus/utils';
|
|
||||||
import {getLangDir} from 'rtl-detect';
|
import {getLangDir} from 'rtl-detect';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
|
import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
function getDefaultLocaleLabel(locale: string) {
|
function getDefaultLocaleLabel(locale: string) {
|
||||||
const languageName = new Intl.DisplayNames(locale, {type: 'language'}).of(
|
const languageName = new Intl.DisplayNames(locale, {type: 'language'}).of(
|
||||||
|
@ -64,29 +62,3 @@ Note: Docusaurus only support running one locale at a time.`;
|
||||||
localeConfigs,
|
localeConfigs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function localizePath({
|
|
||||||
pathType,
|
|
||||||
path: originalPath,
|
|
||||||
i18n,
|
|
||||||
options = {},
|
|
||||||
}: {
|
|
||||||
pathType: 'fs' | 'url';
|
|
||||||
path: string;
|
|
||||||
i18n: I18n;
|
|
||||||
options?: {localizePath?: boolean};
|
|
||||||
}): string {
|
|
||||||
const shouldLocalizePath: boolean =
|
|
||||||
// By default, we don't localize the path of defaultLocale
|
|
||||||
options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale;
|
|
||||||
|
|
||||||
if (!shouldLocalizePath) {
|
|
||||||
return originalPath;
|
|
||||||
}
|
|
||||||
// FS paths need special care, for Windows support
|
|
||||||
if (pathType === 'fs') {
|
|
||||||
return path.join(originalPath, i18n.currentLocale);
|
|
||||||
}
|
|
||||||
// Url paths; add a trailing slash so it's a valid base URL
|
|
||||||
return normalizeUrl([originalPath, i18n.currentLocale, '/']);
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,40 +8,27 @@
|
||||||
import {
|
import {
|
||||||
generate,
|
generate,
|
||||||
escapePath,
|
escapePath,
|
||||||
|
localizePath,
|
||||||
DEFAULT_BUILD_DIR_NAME,
|
DEFAULT_BUILD_DIR_NAME,
|
||||||
DEFAULT_CONFIG_FILE_NAME,
|
DEFAULT_CONFIG_FILE_NAME,
|
||||||
GENERATED_FILES_DIR_NAME,
|
GENERATED_FILES_DIR_NAME,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
import _ from 'lodash';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import logger from '@docusaurus/logger';
|
|
||||||
import ssrDefaultTemplate from '../webpack/templates/ssr.html.template';
|
import ssrDefaultTemplate from '../webpack/templates/ssr.html.template';
|
||||||
import loadClientModules from './client-modules';
|
import {loadClientModules} from './clientModules';
|
||||||
import loadConfig from './config';
|
import {loadConfig} from './config';
|
||||||
import {loadPlugins} from './plugins';
|
import {loadPlugins} from './plugins';
|
||||||
import loadPresets from './presets';
|
import {loadRoutes} from './routes';
|
||||||
import loadRoutes from './routes';
|
import {loadHtmlTags} from './htmlTags';
|
||||||
import type {
|
import {loadSiteMetadata} from './siteMetadata';
|
||||||
DocusaurusConfig,
|
|
||||||
DocusaurusSiteMetadata,
|
|
||||||
HtmlTagObject,
|
|
||||||
LoadContext,
|
|
||||||
LoadedPlugin,
|
|
||||||
PluginConfig,
|
|
||||||
Props,
|
|
||||||
} from '@docusaurus/types';
|
|
||||||
import {loadHtmlTags} from './html-tags';
|
|
||||||
import {getPackageJsonVersion} from './versions';
|
|
||||||
import {handleDuplicateRoutes} from './duplicateRoutes';
|
import {handleDuplicateRoutes} from './duplicateRoutes';
|
||||||
import {loadI18n, localizePath} from './i18n';
|
import {loadI18n} from './i18n';
|
||||||
import {
|
import {
|
||||||
readCodeTranslationFileContent,
|
readCodeTranslationFileContent,
|
||||||
getPluginsDefaultCodeTranslationMessages,
|
getPluginsDefaultCodeTranslationMessages,
|
||||||
} from './translations/translations';
|
} from './translations/translations';
|
||||||
import _ from 'lodash';
|
import type {DocusaurusConfig, LoadContext, Props} from '@docusaurus/types';
|
||||||
import type {RuleSetRule} from 'webpack';
|
|
||||||
import admonitions from 'remark-admonitions';
|
|
||||||
import {createRequire} from 'module';
|
|
||||||
import {resolveModuleName} from './moduleShorthand';
|
|
||||||
|
|
||||||
export type LoadContextOptions = {
|
export type LoadContextOptions = {
|
||||||
customOutDir?: string;
|
customOutDir?: string;
|
||||||
|
@ -126,171 +113,6 @@ export async function loadContext(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPluginConfigs(
|
|
||||||
context: LoadContext,
|
|
||||||
): Promise<PluginConfig[]> {
|
|
||||||
let {plugins: presetPlugins, themes: presetThemes} = await 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.
|
|
||||||
...standalonePlugins,
|
|
||||||
...standaloneThemes,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a fake plugin to:
|
|
||||||
// - Resolve aliased theme components
|
|
||||||
// - Inject scripts/stylesheets
|
|
||||||
function createBootstrapPlugin({
|
|
||||||
siteDir,
|
|
||||||
siteConfig,
|
|
||||||
}: {
|
|
||||||
siteDir: string;
|
|
||||||
siteConfig: DocusaurusConfig;
|
|
||||||
}): LoadedPlugin {
|
|
||||||
const {
|
|
||||||
stylesheets,
|
|
||||||
scripts,
|
|
||||||
clientModules: siteConfigClientModules,
|
|
||||||
} = siteConfig;
|
|
||||||
return {
|
|
||||||
name: 'docusaurus-bootstrap-plugin',
|
|
||||||
content: null,
|
|
||||||
options: {
|
|
||||||
id: 'default',
|
|
||||||
},
|
|
||||||
version: {type: 'synthetic'},
|
|
||||||
path: siteDir,
|
|
||||||
getClientModules() {
|
|
||||||
return siteConfigClientModules;
|
|
||||||
},
|
|
||||||
injectHtmlTags: () => {
|
|
||||||
const stylesheetsTags = stylesheets.map((source) =>
|
|
||||||
typeof source === 'string'
|
|
||||||
? `<link rel="stylesheet" href="${source}">`
|
|
||||||
: ({
|
|
||||||
tagName: 'link',
|
|
||||||
attributes: {
|
|
||||||
rel: 'stylesheet',
|
|
||||||
...source,
|
|
||||||
},
|
|
||||||
} as HtmlTagObject),
|
|
||||||
);
|
|
||||||
const scriptsTags = scripts.map((source) =>
|
|
||||||
typeof source === 'string'
|
|
||||||
? `<script src="${source}"></script>`
|
|
||||||
: ({
|
|
||||||
tagName: 'script',
|
|
||||||
attributes: {
|
|
||||||
...source,
|
|
||||||
},
|
|
||||||
} as HtmlTagObject),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
headTags: [...stylesheetsTags, ...scriptsTags],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure Webpack fallback mdx loader for md/mdx files out of content-plugin
|
|
||||||
* folders. Adds a "fallback" mdx loader for mdx files that are not processed by
|
|
||||||
* content plugins. This allows to do things such as importing repo/README.md as
|
|
||||||
* a partial from another doc. Not ideal solution, but good enough for now
|
|
||||||
*/
|
|
||||||
function createMDXFallbackPlugin({
|
|
||||||
siteDir,
|
|
||||||
siteConfig,
|
|
||||||
}: {
|
|
||||||
siteDir: string;
|
|
||||||
siteConfig: DocusaurusConfig;
|
|
||||||
}): LoadedPlugin {
|
|
||||||
return {
|
|
||||||
name: 'docusaurus-mdx-fallback-plugin',
|
|
||||||
content: null,
|
|
||||||
options: {
|
|
||||||
id: 'default',
|
|
||||||
},
|
|
||||||
version: {type: 'synthetic'},
|
|
||||||
// Synthetic, the path doesn't matter much
|
|
||||||
path: '.',
|
|
||||||
configureWebpack(config, isServer, {getJSLoader}) {
|
|
||||||
// We need the mdx fallback loader to exclude files that were already
|
|
||||||
// processed by content plugins mdx loaders. This works, but a bit
|
|
||||||
// hacky... Not sure there's a way to handle that differently in webpack
|
|
||||||
function getMDXFallbackExcludedPaths(): string[] {
|
|
||||||
const rules: RuleSetRule[] = config?.module?.rules as RuleSetRule[];
|
|
||||||
return rules.flatMap((rule) => {
|
|
||||||
const isMDXRule =
|
|
||||||
rule.test instanceof RegExp && rule.test.test('x.mdx');
|
|
||||||
return isMDXRule ? (rule.include as string[]) : [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.mdx?$/i,
|
|
||||||
exclude: getMDXFallbackExcludedPaths(),
|
|
||||||
use: [
|
|
||||||
getJSLoader({isServer}),
|
|
||||||
{
|
|
||||||
loader: require.resolve('@docusaurus/mdx-loader'),
|
|
||||||
options: {
|
|
||||||
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
|
||||||
path.resolve(siteDir, dir),
|
|
||||||
),
|
|
||||||
siteDir,
|
|
||||||
isMDXPartial: () => true, // External mdx files are always meant to be imported as partials
|
|
||||||
isMDXPartialFrontMatterWarningDisabled: true, // External mdx files might have front matter, let's just disable the warning
|
|
||||||
remarkPlugins: [admonitions],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function load(
|
export async function load(
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
options: LoadContextOptions = {},
|
options: LoadContextOptions = {},
|
||||||
|
@ -308,9 +130,8 @@ export async function load(
|
||||||
codeTranslations,
|
codeTranslations,
|
||||||
} = context;
|
} = context;
|
||||||
// Plugins.
|
// Plugins.
|
||||||
const pluginConfigs: PluginConfig[] = await loadPluginConfigs(context);
|
|
||||||
const {plugins, pluginsRouteConfigs, globalData, themeConfigTranslated} =
|
const {plugins, pluginsRouteConfigs, globalData, themeConfigTranslated} =
|
||||||
await loadPlugins({pluginConfigs, context});
|
await loadPlugins(context);
|
||||||
|
|
||||||
// Side-effect to replace the untranslated themeConfig by the translated one
|
// Side-effect to replace the untranslated themeConfig by the translated one
|
||||||
context.siteConfig.themeConfig = themeConfigTranslated;
|
context.siteConfig.themeConfig = themeConfigTranslated;
|
||||||
|
@ -341,11 +162,6 @@ export default ${JSON.stringify(siteConfig, null, 2)};
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
plugins.push(
|
|
||||||
createBootstrapPlugin({siteDir, siteConfig}),
|
|
||||||
createMDXFallbackPlugin({siteDir, siteConfig}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load client modules.
|
// Load client modules.
|
||||||
const clientModules = loadClientModules(plugins);
|
const clientModules = loadClientModules(plugins);
|
||||||
const genClientModules = generate(
|
const genClientModules = generate(
|
||||||
|
@ -416,21 +232,7 @@ ${Object.entries(registry)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Version metadata.
|
// Version metadata.
|
||||||
const siteMetadata: DocusaurusSiteMetadata = {
|
const siteMetadata = await loadSiteMetadata({plugins, siteDir});
|
||||||
docusaurusVersion: (await getPackageJsonVersion(
|
|
||||||
path.join(__dirname, '../../package.json'),
|
|
||||||
))!,
|
|
||||||
siteVersion: await getPackageJsonVersion(
|
|
||||||
path.join(siteDir, 'package.json'),
|
|
||||||
),
|
|
||||||
pluginVersions: {},
|
|
||||||
};
|
|
||||||
plugins
|
|
||||||
.filter(({version: {type}}) => type !== 'synthetic')
|
|
||||||
.forEach(({name, version}) => {
|
|
||||||
siteMetadata.pluginVersions[name] = version;
|
|
||||||
});
|
|
||||||
checkDocusaurusPackagesVersion(siteMetadata);
|
|
||||||
const genSiteMetadata = generate(
|
const genSiteMetadata = generate(
|
||||||
generatedFilesDir,
|
generatedFilesDir,
|
||||||
'site-metadata.json',
|
'site-metadata.json',
|
||||||
|
@ -471,26 +273,3 @@ ${Object.entries(registry)
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want all @docusaurus/* packages to have the exact same version!
|
|
||||||
// See https://github.com/facebook/docusaurus/issues/3371
|
|
||||||
// See https://github.com/facebook/docusaurus/pull/3386
|
|
||||||
function checkDocusaurusPackagesVersion(siteMetadata: DocusaurusSiteMetadata) {
|
|
||||||
const {docusaurusVersion} = siteMetadata;
|
|
||||||
Object.entries(siteMetadata.pluginVersions).forEach(
|
|
||||||
([plugin, versionInfo]) => {
|
|
||||||
if (
|
|
||||||
versionInfo.type === 'package' &&
|
|
||||||
versionInfo.name?.startsWith('@docusaurus/') &&
|
|
||||||
versionInfo.version &&
|
|
||||||
versionInfo.version !== docusaurusVersion
|
|
||||||
) {
|
|
||||||
// should we throw instead?
|
|
||||||
// It still could work with different versions
|
|
||||||
logger.error`Invalid name=${plugin} version number=${versionInfo.version}.
|
|
||||||
All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${docusaurusVersion}).
|
|
||||||
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,112 +37,33 @@ exports[`loadPlugins loads plugins 1`] = `
|
||||||
"type": "local",
|
"type": "local",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"content": undefined,
|
||||||
|
"getClientModules": [Function],
|
||||||
|
"injectHtmlTags": [Function],
|
||||||
|
"name": "docusaurus-bootstrap-plugin",
|
||||||
|
"options": {
|
||||||
|
"id": "default",
|
||||||
|
},
|
||||||
|
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin",
|
||||||
|
"version": {
|
||||||
|
"type": "synthetic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configureWebpack": [Function],
|
||||||
|
"content": undefined,
|
||||||
|
"name": "docusaurus-mdx-fallback-plugin",
|
||||||
|
"options": {
|
||||||
|
"id": "default",
|
||||||
|
},
|
||||||
|
"path": ".",
|
||||||
|
"version": {
|
||||||
|
"type": "synthetic",
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"pluginsRouteConfigs": [],
|
"pluginsRouteConfigs": [],
|
||||||
"themeConfigTranslated": {},
|
"themeConfigTranslated": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`sortConfig sorts route config correctly 1`] = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/community",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/some-page",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/docs",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/docs/someDoc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/docs/someOtherDoc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/someDoc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/someOtherDoc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/subroute",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`sortConfig sorts route config given a baseURL 1`] = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/community",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/example",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/some-page",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/docs",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/docs/someDoc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/docs/someOtherDoc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/someDoc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"component": "",
|
|
||||||
"path": "/latest/someOtherDoc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`sortConfig sorts route config correctly 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/community",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/some-page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/docs",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/docs/someDoc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/docs/someOtherDoc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/someDoc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/someOtherDoc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/subroute",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`sortConfig sorts route config given a baseURL 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/community",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/example",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/some-page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/docs",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/docs/someDoc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/docs/someOtherDoc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/someDoc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "",
|
||||||
|
"path": "/latest/someOtherDoc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
|
@ -6,15 +6,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {loadPlugins, sortConfig} from '..';
|
import {loadPlugins} from '..';
|
||||||
import type {RouteConfig} from '@docusaurus/types';
|
|
||||||
|
|
||||||
describe('loadPlugins', () => {
|
describe('loadPlugins', () => {
|
||||||
it('loads plugins', async () => {
|
it('loads plugins', async () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__/site-with-plugin');
|
const siteDir = path.join(__dirname, '__fixtures__/site-with-plugin');
|
||||||
await expect(
|
await expect(
|
||||||
loadPlugins({
|
loadPlugins({
|
||||||
pluginConfigs: [
|
siteDir,
|
||||||
|
generatedFilesDir: path.join(siteDir, '.docusaurus'),
|
||||||
|
outDir: path.join(siteDir, 'build'),
|
||||||
|
// @ts-expect-error: good enough
|
||||||
|
siteConfig: {
|
||||||
|
baseUrl: '/',
|
||||||
|
trailingSlash: true,
|
||||||
|
themeConfig: {},
|
||||||
|
presets: [],
|
||||||
|
plugins: [
|
||||||
() => ({
|
() => ({
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
prop: 'a',
|
prop: 'a',
|
||||||
|
@ -26,6 +34,8 @@ describe('loadPlugins', () => {
|
||||||
actions.setGlobalData({content, prop: this.prop});
|
actions.setGlobalData({content, prop: this.prop});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
],
|
||||||
|
themes: [
|
||||||
() => ({
|
() => ({
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
configureWebpack() {
|
configureWebpack() {
|
||||||
|
@ -33,107 +43,9 @@ describe('loadPlugins', () => {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
context: {
|
|
||||||
siteDir,
|
|
||||||
generatedFilesDir: path.join(siteDir, '.docusaurus'),
|
|
||||||
outDir: path.join(siteDir, 'build'),
|
|
||||||
// @ts-expect-error: good enough
|
|
||||||
siteConfig: {
|
|
||||||
baseUrl: '/',
|
|
||||||
trailingSlash: true,
|
|
||||||
themeConfig: {},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
siteConfigPath: path.join(siteDir, 'docusaurus.config.js'),
|
siteConfigPath: path.join(siteDir, 'docusaurus.config.js'),
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchSnapshot();
|
).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sortConfig', () => {
|
|
||||||
it('sorts route config correctly', () => {
|
|
||||||
const routes: RouteConfig[] = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: '',
|
|
||||||
routes: [
|
|
||||||
{path: '/someDoc', component: ''},
|
|
||||||
{path: '/someOtherDoc', component: ''},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: '',
|
|
||||||
routes: [{path: '/subroute', component: ''}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/docs',
|
|
||||||
component: '',
|
|
||||||
routes: [
|
|
||||||
{path: '/docs/someDoc', component: ''},
|
|
||||||
{path: '/docs/someOtherDoc', component: ''},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/community',
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/some-page',
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
sortConfig(routes);
|
|
||||||
|
|
||||||
expect(routes).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorts route config given a baseURL', () => {
|
|
||||||
const baseURL = '/latest/';
|
|
||||||
const routes: RouteConfig[] = [
|
|
||||||
{
|
|
||||||
path: baseURL,
|
|
||||||
component: '',
|
|
||||||
routes: [
|
|
||||||
{path: `${baseURL}someDoc`, component: ''},
|
|
||||||
{path: `${baseURL}someOtherDoc`, component: ''},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${baseURL}example`,
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${baseURL}docs`,
|
|
||||||
component: '',
|
|
||||||
routes: [
|
|
||||||
{path: `${baseURL}docs/someDoc`, component: ''},
|
|
||||||
{path: `${baseURL}docs/someOtherDoc`, component: ''},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${baseURL}community`,
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${baseURL}some-page`,
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `${baseURL}`,
|
|
||||||
component: '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
sortConfig(routes, baseURL);
|
|
||||||
|
|
||||||
expect(routes).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -7,22 +7,14 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {
|
import {loadContext, type LoadContextOptions} from '../../index';
|
||||||
loadContext,
|
import {initPlugins} from '../init';
|
||||||
loadPluginConfigs,
|
|
||||||
type LoadContextOptions,
|
|
||||||
} from '../../index';
|
|
||||||
import initPlugins from '../init';
|
|
||||||
|
|
||||||
describe('initPlugins', () => {
|
describe('initPlugins', () => {
|
||||||
async function loadSite(options: LoadContextOptions = {}) {
|
async function loadSite(options: LoadContextOptions = {}) {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin');
|
const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin');
|
||||||
const context = await loadContext(siteDir, options);
|
const context = await loadContext(siteDir, options);
|
||||||
const pluginConfigs = await loadPluginConfigs(context);
|
const plugins = await initPlugins(context);
|
||||||
const plugins = await initPlugins({
|
|
||||||
pluginConfigs,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {siteDir, context, plugins};
|
return {siteDir, context, plugins};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import loadPresets from '../index';
|
import {loadPresets} from '../presets';
|
||||||
import type {LoadContext} from '@docusaurus/types';
|
import type {LoadContext} from '@docusaurus/types';
|
||||||
|
|
||||||
describe('loadPresets', () => {
|
describe('loadPresets', () => {
|
||||||
|
@ -31,7 +31,9 @@ describe('loadPresets', () => {
|
||||||
const context = {
|
const context = {
|
||||||
siteConfigPath: __dirname,
|
siteConfigPath: __dirname,
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [path.join(__dirname, '__fixtures__/preset-plugins.js')],
|
presets: [
|
||||||
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
} as LoadContext;
|
} as LoadContext;
|
||||||
const presets = await loadPresets(context);
|
const presets = await loadPresets(context);
|
||||||
|
@ -43,8 +45,8 @@ describe('loadPresets', () => {
|
||||||
siteConfigPath: __dirname,
|
siteConfigPath: __dirname,
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [
|
presets: [
|
||||||
path.join(__dirname, '__fixtures__/preset-plugins.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
path.join(__dirname, '__fixtures__/preset-themes.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-themes.js'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as LoadContext;
|
} as LoadContext;
|
||||||
|
@ -56,7 +58,9 @@ describe('loadPresets', () => {
|
||||||
const context = {
|
const context = {
|
||||||
siteConfigPath: __dirname,
|
siteConfigPath: __dirname,
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [[path.join(__dirname, '__fixtures__/preset-plugins.js')]],
|
presets: [
|
||||||
|
[path.join(__dirname, '__fixtures__/presets/preset-plugins.js')],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
} as Partial<LoadContext>;
|
} as Partial<LoadContext>;
|
||||||
const presets = await loadPresets(context);
|
const presets = await loadPresets(context);
|
||||||
|
@ -69,7 +73,7 @@ describe('loadPresets', () => {
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
path.join(__dirname, '__fixtures__/preset-plugins.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
{docs: {path: '../'}},
|
{docs: {path: '../'}},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -85,11 +89,11 @@ describe('loadPresets', () => {
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
path.join(__dirname, '__fixtures__/preset-plugins.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
{docs: {path: '../'}},
|
{docs: {path: '../'}},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
path.join(__dirname, '__fixtures__/preset-themes.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-themes.js'),
|
||||||
{algolia: {trackingID: 'foo'}},
|
{algolia: {trackingID: 'foo'}},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -105,10 +109,10 @@ describe('loadPresets', () => {
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
path.join(__dirname, '__fixtures__/preset-plugins.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
{docs: {path: '../'}},
|
{docs: {path: '../'}},
|
||||||
],
|
],
|
||||||
path.join(__dirname, '__fixtures__/preset-themes.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-themes.js'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as LoadContext;
|
} as LoadContext;
|
||||||
|
@ -122,11 +126,11 @@ describe('loadPresets', () => {
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
path.join(__dirname, '__fixtures__/preset-plugins.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-plugins.js'),
|
||||||
{docs: {path: '../'}},
|
{docs: {path: '../'}},
|
||||||
],
|
],
|
||||||
path.join(__dirname, '__fixtures__/preset-themes.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-themes.js'),
|
||||||
path.join(__dirname, '__fixtures__/preset-mixed.js'),
|
path.join(__dirname, '__fixtures__/presets/preset-mixed.js'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as LoadContext;
|
} as LoadContext;
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import applyRouteTrailingSlash from '../applyRouteTrailingSlash';
|
import {applyRouteTrailingSlash, sortConfig} from '../routeConfig';
|
||||||
import type {RouteConfig} from '@docusaurus/types';
|
import type {RouteConfig} from '@docusaurus/types';
|
||||||
import type {ApplyTrailingSlashParams} from '@docusaurus/utils-common';
|
import type {ApplyTrailingSlashParams} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
|
@ -163,3 +163,89 @@ describe('applyRouteTrailingSlash', () => {
|
||||||
).toEqual(route('/abc/?search#anchor', ['/abc/1?search', '/abc/2#anchor']));
|
).toEqual(route('/abc/?search#anchor', ['/abc/1?search', '/abc/2#anchor']));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sortConfig', () => {
|
||||||
|
it('sorts route config correctly', () => {
|
||||||
|
const routes: RouteConfig[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: '',
|
||||||
|
routes: [
|
||||||
|
{path: '/someDoc', component: ''},
|
||||||
|
{path: '/someOtherDoc', component: ''},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: '',
|
||||||
|
routes: [{path: '/subroute', component: ''}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/docs',
|
||||||
|
component: '',
|
||||||
|
routes: [
|
||||||
|
{path: '/docs/someDoc', component: ''},
|
||||||
|
{path: '/docs/someOtherDoc', component: ''},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/community',
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/some-page',
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
sortConfig(routes);
|
||||||
|
|
||||||
|
expect(routes).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts route config given a baseURL', () => {
|
||||||
|
const baseURL = '/latest/';
|
||||||
|
const routes: RouteConfig[] = [
|
||||||
|
{
|
||||||
|
path: baseURL,
|
||||||
|
component: '',
|
||||||
|
routes: [
|
||||||
|
{path: `${baseURL}someDoc`, component: ''},
|
||||||
|
{path: `${baseURL}someOtherDoc`, component: ''},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${baseURL}example`,
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${baseURL}docs`,
|
||||||
|
component: '',
|
||||||
|
routes: [
|
||||||
|
{path: `${baseURL}docs/someDoc`, component: ''},
|
||||||
|
{path: `${baseURL}docs/someOtherDoc`, component: ''},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${baseURL}community`,
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${baseURL}some-page`,
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${baseURL}`,
|
||||||
|
component: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
sortConfig(routes, baseURL);
|
||||||
|
|
||||||
|
expect(routes).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 type {RouteConfig} from '@docusaurus/types';
|
|
||||||
import {
|
|
||||||
applyTrailingSlash,
|
|
||||||
type ApplyTrailingSlashParams,
|
|
||||||
} from '@docusaurus/utils-common';
|
|
||||||
|
|
||||||
export default function applyRouteTrailingSlash(
|
|
||||||
route: RouteConfig,
|
|
||||||
params: ApplyTrailingSlashParams,
|
|
||||||
): RouteConfig {
|
|
||||||
return {
|
|
||||||
...route,
|
|
||||||
path: applyTrailingSlash(route.path, params),
|
|
||||||
...(route.routes && {
|
|
||||||
routes: route.routes.map((subroute) =>
|
|
||||||
applyRouteTrailingSlash(subroute, params),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
55
packages/docusaurus/src/server/plugins/configs.ts
Normal file
55
packages/docusaurus/src/server/plugins/configs.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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 {createRequire} from 'module';
|
||||||
|
import {loadPresets} from './presets';
|
||||||
|
import {resolveModuleName} from '../moduleShorthand';
|
||||||
|
import type {LoadContext, PluginConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
|
export async function loadPluginConfigs(
|
||||||
|
context: LoadContext,
|
||||||
|
): Promise<PluginConfig[]> {
|
||||||
|
const preset = await 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;
|
||||||
|
}
|
||||||
|
preset.plugins = preset.plugins.map((plugin) =>
|
||||||
|
normalizeShorthand(plugin, 'plugin'),
|
||||||
|
);
|
||||||
|
preset.themes = preset.themes.map((theme) =>
|
||||||
|
normalizeShorthand(theme, 'theme'),
|
||||||
|
);
|
||||||
|
const standalonePlugins = siteConfig.plugins.map((plugin) =>
|
||||||
|
normalizeShorthand(plugin, 'plugin'),
|
||||||
|
);
|
||||||
|
const standaloneThemes = siteConfig.themes.map((theme) =>
|
||||||
|
normalizeShorthand(theme, 'theme'),
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
...preset.plugins,
|
||||||
|
...preset.themes,
|
||||||
|
// Site config should be the highest priority.
|
||||||
|
...standalonePlugins,
|
||||||
|
...standaloneThemes,
|
||||||
|
];
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type {
|
import type {
|
||||||
LoadContext,
|
LoadContext,
|
||||||
PluginConfig,
|
|
||||||
PluginContentLoadedActions,
|
PluginContentLoadedActions,
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
AllContent,
|
AllContent,
|
||||||
|
@ -21,69 +20,26 @@ import type {
|
||||||
InitializedPlugin,
|
InitializedPlugin,
|
||||||
PluginRouteContext,
|
PluginRouteContext,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import initPlugins from './init';
|
import {initPlugins} from './init';
|
||||||
|
import {createBootstrapPlugin, createMDXFallbackPlugin} from './synthetic';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {localizePluginTranslationFile} from '../translations/translations';
|
import {localizePluginTranslationFile} from '../translations/translations';
|
||||||
import applyRouteTrailingSlash from './applyRouteTrailingSlash';
|
import {applyRouteTrailingSlash, sortConfig} from './routeConfig';
|
||||||
|
|
||||||
export function sortConfig(
|
export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
routeConfigs: RouteConfig[],
|
|
||||||
baseUrl: string = '/',
|
|
||||||
): void {
|
|
||||||
// Sort the route config. This ensures that route with nested
|
|
||||||
// routes is always placed last.
|
|
||||||
routeConfigs.sort((a, b) => {
|
|
||||||
// Root route should get placed last.
|
|
||||||
if (a.path === baseUrl && b.path !== baseUrl) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.path !== baseUrl && b.path === baseUrl) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.routes && !b.routes) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (!a.routes && b.routes) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Higher priority get placed first.
|
|
||||||
if (a.priority || b.priority) {
|
|
||||||
const priorityA = a.priority || 0;
|
|
||||||
const priorityB = b.priority || 0;
|
|
||||||
const score = priorityB - priorityA;
|
|
||||||
|
|
||||||
if (score !== 0) {
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.path.localeCompare(b.path);
|
|
||||||
});
|
|
||||||
|
|
||||||
routeConfigs.forEach((routeConfig) => {
|
|
||||||
routeConfig.routes?.sort((a, b) => a.path.localeCompare(b.path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadPlugins({
|
|
||||||
pluginConfigs,
|
|
||||||
context,
|
|
||||||
}: {
|
|
||||||
pluginConfigs: PluginConfig[];
|
|
||||||
context: LoadContext;
|
|
||||||
}): Promise<{
|
|
||||||
plugins: LoadedPlugin[];
|
plugins: LoadedPlugin[];
|
||||||
pluginsRouteConfigs: RouteConfig[];
|
pluginsRouteConfigs: RouteConfig[];
|
||||||
globalData: GlobalData;
|
globalData: GlobalData;
|
||||||
themeConfigTranslated: ThemeConfig;
|
themeConfigTranslated: ThemeConfig;
|
||||||
}> {
|
}> {
|
||||||
// 1. Plugin Lifecycle - Initialization/Constructor.
|
// 1. Plugin Lifecycle - Initialization/Constructor.
|
||||||
const plugins: InitializedPlugin[] = await initPlugins({
|
const plugins: InitializedPlugin[] = await initPlugins(context);
|
||||||
pluginConfigs,
|
|
||||||
context,
|
plugins.push(
|
||||||
});
|
createBootstrapPlugin(context),
|
||||||
|
createMDXFallbackPlugin(context),
|
||||||
|
);
|
||||||
|
|
||||||
// 2. Plugin Lifecycle - loadContent.
|
// 2. Plugin Lifecycle - loadContent.
|
||||||
// Currently plugins run lifecycle methods in parallel and are not
|
// Currently plugins run lifecycle methods in parallel and are not
|
||||||
|
|
|
@ -18,12 +18,13 @@ import type {
|
||||||
InitializedPlugin,
|
InitializedPlugin,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
||||||
import {getPluginVersion} from '../versions';
|
import {getPluginVersion} from '../siteMetadata';
|
||||||
import {ensureUniquePluginInstanceIds} from './pluginIds';
|
import {ensureUniquePluginInstanceIds} from './pluginIds';
|
||||||
import {
|
import {
|
||||||
normalizePluginOptions,
|
normalizePluginOptions,
|
||||||
normalizeThemeConfig,
|
normalizeThemeConfig,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
|
import {loadPluginConfigs} from './configs';
|
||||||
|
|
||||||
export type NormalizedPluginConfig = {
|
export type NormalizedPluginConfig = {
|
||||||
plugin: PluginModule;
|
plugin: PluginModule;
|
||||||
|
@ -134,16 +135,13 @@ function getThemeValidationFunction(
|
||||||
return normalizedPluginConfig.plugin.validateThemeConfig;
|
return normalizedPluginConfig.plugin.validateThemeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function initPlugins({
|
export async function initPlugins(
|
||||||
pluginConfigs,
|
context: LoadContext,
|
||||||
context,
|
): Promise<InitializedPlugin[]> {
|
||||||
}: {
|
|
||||||
pluginConfigs: PluginConfig[];
|
|
||||||
context: LoadContext;
|
|
||||||
}): Promise<InitializedPlugin[]> {
|
|
||||||
// We need to resolve plugins from the perspective of the siteDir, since the
|
// We need to resolve plugins from the perspective of the siteDir, since the
|
||||||
// siteDir's package.json declares the dependency on these plugins.
|
// siteDir's package.json declares the dependency on these plugins.
|
||||||
const pluginRequire = createRequire(context.siteConfigPath);
|
const pluginRequire = createRequire(context.siteConfigPath);
|
||||||
|
const pluginConfigs = await loadPluginConfigs(context);
|
||||||
const pluginConfigsNormalized = await normalizePluginConfigs(
|
const pluginConfigsNormalized = await normalizePluginConfigs(
|
||||||
pluginConfigs,
|
pluginConfigs,
|
||||||
context.siteConfigPath,
|
context.siteConfigPath,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import {resolveModuleName} from '../moduleShorthand';
|
import {resolveModuleName} from '../moduleShorthand';
|
||||||
|
|
||||||
export default async function loadPresets(context: LoadContext): Promise<{
|
export async function loadPresets(context: LoadContext): Promise<{
|
||||||
plugins: PluginConfig[];
|
plugins: PluginConfig[];
|
||||||
themes: PluginConfig[];
|
themes: PluginConfig[];
|
||||||
}> {
|
}> {
|
67
packages/docusaurus/src/server/plugins/routeConfig.ts
Normal file
67
packages/docusaurus/src/server/plugins/routeConfig.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* 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 type {RouteConfig} from '@docusaurus/types';
|
||||||
|
import {
|
||||||
|
applyTrailingSlash,
|
||||||
|
type ApplyTrailingSlashParams,
|
||||||
|
} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
|
export function applyRouteTrailingSlash(
|
||||||
|
route: RouteConfig,
|
||||||
|
params: ApplyTrailingSlashParams,
|
||||||
|
): RouteConfig {
|
||||||
|
return {
|
||||||
|
...route,
|
||||||
|
path: applyTrailingSlash(route.path, params),
|
||||||
|
...(route.routes && {
|
||||||
|
routes: route.routes.map((subroute) =>
|
||||||
|
applyRouteTrailingSlash(subroute, params),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortConfig(
|
||||||
|
routeConfigs: RouteConfig[],
|
||||||
|
baseUrl: string = '/',
|
||||||
|
): void {
|
||||||
|
// Sort the route config. This ensures that route with nested
|
||||||
|
// routes is always placed last.
|
||||||
|
routeConfigs.sort((a, b) => {
|
||||||
|
// Root route should get placed last.
|
||||||
|
if (a.path === baseUrl && b.path !== baseUrl) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.path !== baseUrl && b.path === baseUrl) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.routes && !b.routes) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!a.routes && b.routes) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Higher priority get placed first.
|
||||||
|
if (a.priority || b.priority) {
|
||||||
|
const priorityA = a.priority || 0;
|
||||||
|
const priorityB = b.priority || 0;
|
||||||
|
const score = priorityB - priorityA;
|
||||||
|
|
||||||
|
if (score !== 0) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.path.localeCompare(b.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
routeConfigs.forEach((routeConfig) => {
|
||||||
|
routeConfig.routes?.sort((a, b) => a.path.localeCompare(b.path));
|
||||||
|
});
|
||||||
|
}
|
129
packages/docusaurus/src/server/plugins/synthetic.ts
Normal file
129
packages/docusaurus/src/server/plugins/synthetic.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
* 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 admonitions from 'remark-admonitions';
|
||||||
|
import type {RuleSetRule} from 'webpack';
|
||||||
|
import type {HtmlTagObject, LoadedPlugin, LoadContext} from '@docusaurus/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a synthetic plugin to:
|
||||||
|
* - Inject site client modules
|
||||||
|
* - Inject scripts/stylesheets
|
||||||
|
*/
|
||||||
|
export function createBootstrapPlugin({
|
||||||
|
siteDir,
|
||||||
|
siteConfig,
|
||||||
|
}: LoadContext): LoadedPlugin {
|
||||||
|
const {
|
||||||
|
stylesheets,
|
||||||
|
scripts,
|
||||||
|
clientModules: siteConfigClientModules,
|
||||||
|
} = siteConfig;
|
||||||
|
return {
|
||||||
|
name: 'docusaurus-bootstrap-plugin',
|
||||||
|
content: null,
|
||||||
|
options: {
|
||||||
|
id: 'default',
|
||||||
|
},
|
||||||
|
version: {type: 'synthetic'},
|
||||||
|
path: siteDir,
|
||||||
|
getClientModules() {
|
||||||
|
return siteConfigClientModules;
|
||||||
|
},
|
||||||
|
injectHtmlTags: () => {
|
||||||
|
const stylesheetsTags = stylesheets.map((source) =>
|
||||||
|
typeof source === 'string'
|
||||||
|
? `<link rel="stylesheet" href="${source}">`
|
||||||
|
: ({
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
rel: 'stylesheet',
|
||||||
|
...source,
|
||||||
|
},
|
||||||
|
} as HtmlTagObject),
|
||||||
|
);
|
||||||
|
const scriptsTags = scripts.map((source) =>
|
||||||
|
typeof source === 'string'
|
||||||
|
? `<script src="${source}"></script>`
|
||||||
|
: ({
|
||||||
|
tagName: 'script',
|
||||||
|
attributes: {
|
||||||
|
...source,
|
||||||
|
},
|
||||||
|
} as HtmlTagObject),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
headTags: [...stylesheetsTags, ...scriptsTags],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Webpack fallback mdx loader for md/mdx files out of content-plugin
|
||||||
|
* folders. Adds a "fallback" mdx loader for mdx files that are not processed by
|
||||||
|
* content plugins. This allows to do things such as importing repo/README.md as
|
||||||
|
* a partial from another doc. Not ideal solution, but good enough for now
|
||||||
|
*/
|
||||||
|
export function createMDXFallbackPlugin({
|
||||||
|
siteDir,
|
||||||
|
siteConfig,
|
||||||
|
}: LoadContext): LoadedPlugin {
|
||||||
|
return {
|
||||||
|
name: 'docusaurus-mdx-fallback-plugin',
|
||||||
|
content: null,
|
||||||
|
options: {
|
||||||
|
id: 'default',
|
||||||
|
},
|
||||||
|
version: {type: 'synthetic'},
|
||||||
|
// Synthetic, the path doesn't matter much
|
||||||
|
path: '.',
|
||||||
|
configureWebpack(config, isServer, {getJSLoader}) {
|
||||||
|
// We need the mdx fallback loader to exclude files that were already
|
||||||
|
// processed by content plugins mdx loaders. This works, but a bit
|
||||||
|
// hacky... Not sure there's a way to handle that differently in webpack
|
||||||
|
function getMDXFallbackExcludedPaths(): string[] {
|
||||||
|
const rules: RuleSetRule[] = config?.module?.rules as RuleSetRule[];
|
||||||
|
return rules.flatMap((rule) => {
|
||||||
|
const isMDXRule =
|
||||||
|
rule.test instanceof RegExp && rule.test.test('x.mdx');
|
||||||
|
return isMDXRule ? (rule.include as string[]) : [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const mdxLoaderOptions = {
|
||||||
|
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
||||||
|
path.resolve(siteDir, dir),
|
||||||
|
),
|
||||||
|
siteDir,
|
||||||
|
// External MDX files are always meant to be imported as partials
|
||||||
|
isMDXPartial: () => true,
|
||||||
|
// External MDX files might have front matter, just disable the warning
|
||||||
|
isMDXPartialFrontMatterWarningDisabled: true,
|
||||||
|
remarkPlugins: [admonitions],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.mdx?$/i,
|
||||||
|
exclude: getMDXFallbackExcludedPaths(),
|
||||||
|
use: [
|
||||||
|
getJSLoader({isServer}),
|
||||||
|
{
|
||||||
|
loader: require.resolve('@docusaurus/mdx-loader'),
|
||||||
|
options: mdxLoaderOptions,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -119,7 +119,7 @@ function getModulePath(target: Module): string {
|
||||||
return `${target.path}${queryStr}`;
|
return `${target.path}${queryStr}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function loadRoutes(
|
export async function loadRoutes(
|
||||||
pluginsRouteConfigs: RouteConfig[],
|
pluginsRouteConfigs: RouteConfig[],
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
|
|
@ -5,11 +5,16 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {PluginVersionInformation} from '@docusaurus/types';
|
import type {
|
||||||
|
LoadedPlugin,
|
||||||
|
PluginVersionInformation,
|
||||||
|
DocusaurusSiteMetadata,
|
||||||
|
} from '@docusaurus/types';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import logger from '@docusaurus/logger';
|
||||||
|
|
||||||
export async function getPackageJsonVersion(
|
async function getPackageJsonVersion(
|
||||||
packageJsonPath: string,
|
packageJsonPath: string,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
@ -59,3 +64,52 @@ export async function getPluginVersion(
|
||||||
// package.json (e.g. inline plugin), we can only classify it as local.
|
// package.json (e.g. inline plugin), we can only classify it as local.
|
||||||
return {type: 'local'};
|
return {type: 'local'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We want all `@docusaurus/*` packages to have the exact same version!
|
||||||
|
* @see https://github.com/facebook/docusaurus/issues/3371
|
||||||
|
* @see https://github.com/facebook/docusaurus/pull/3386
|
||||||
|
*/
|
||||||
|
function checkDocusaurusPackagesVersion(siteMetadata: DocusaurusSiteMetadata) {
|
||||||
|
const {docusaurusVersion} = siteMetadata;
|
||||||
|
Object.entries(siteMetadata.pluginVersions).forEach(
|
||||||
|
([plugin, versionInfo]) => {
|
||||||
|
if (
|
||||||
|
versionInfo.type === 'package' &&
|
||||||
|
versionInfo.name?.startsWith('@docusaurus/') &&
|
||||||
|
versionInfo.version &&
|
||||||
|
versionInfo.version !== docusaurusVersion
|
||||||
|
) {
|
||||||
|
// should we throw instead?
|
||||||
|
// It still could work with different versions
|
||||||
|
logger.error`Invalid name=${plugin} version number=${versionInfo.version}.
|
||||||
|
All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${docusaurusVersion}).
|
||||||
|
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadSiteMetadata({
|
||||||
|
plugins,
|
||||||
|
siteDir,
|
||||||
|
}: {
|
||||||
|
plugins: LoadedPlugin[];
|
||||||
|
siteDir: string;
|
||||||
|
}): Promise<DocusaurusSiteMetadata> {
|
||||||
|
const siteMetadata: DocusaurusSiteMetadata = {
|
||||||
|
docusaurusVersion: (await getPackageJsonVersion(
|
||||||
|
path.join(__dirname, '../../package.json'),
|
||||||
|
))!,
|
||||||
|
siteVersion: await getPackageJsonVersion(
|
||||||
|
path.join(siteDir, 'package.json'),
|
||||||
|
),
|
||||||
|
pluginVersions: Object.fromEntries(
|
||||||
|
plugins
|
||||||
|
.filter(({version: {type}}) => type !== 'synthetic')
|
||||||
|
.map(({name, version}) => [name, version]),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
checkDocusaurusPackagesVersion(siteMetadata);
|
||||||
|
return siteMetadata;
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import themeAlias, {sortAliases} from '../alias';
|
import {themeAlias, sortAliases} from '../alias';
|
||||||
|
|
||||||
describe('sortAliases', () => {
|
describe('sortAliases', () => {
|
||||||
// https://github.com/facebook/docusaurus/issues/6878
|
// https://github.com/facebook/docusaurus/issues/6878
|
||||||
|
|
|
@ -26,7 +26,7 @@ export function sortAliases(aliases: ThemeAliases): ThemeAliases {
|
||||||
return Object.fromEntries(entries);
|
return Object.fromEntries(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function themeAlias(
|
export async function themeAlias(
|
||||||
themePath: string,
|
themePath: string,
|
||||||
addOriginalAlias: boolean,
|
addOriginalAlias: boolean,
|
||||||
): Promise<ThemeAliases> {
|
): Promise<ThemeAliases> {
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ThemeAliases, LoadedPlugin} from '@docusaurus/types';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {THEME_PATH} from '@docusaurus/utils';
|
import {THEME_PATH} from '@docusaurus/utils';
|
||||||
import themeAlias, {sortAliases} from './alias';
|
import {themeAlias, sortAliases} from './alias';
|
||||||
|
import type {ThemeAliases, LoadedPlugin} from '@docusaurus/types';
|
||||||
|
|
||||||
const ThemeFallbackDir = path.join(__dirname, '../../client/theme-fallback');
|
const ThemeFallbackDir = path.join(__dirname, '../../client/theme-fallback');
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,6 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import type {
|
|
||||||
TranslationFileContent,
|
|
||||||
TranslationFile,
|
|
||||||
TranslationMessage,
|
|
||||||
InitializedPlugin,
|
|
||||||
} from '@docusaurus/types';
|
|
||||||
import {
|
import {
|
||||||
getPluginI18nPath,
|
getPluginI18nPath,
|
||||||
toMessageRelativeFilePath,
|
toMessageRelativeFilePath,
|
||||||
|
@ -22,6 +16,12 @@ import {
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {Joi} from '@docusaurus/utils-validation';
|
import {Joi} from '@docusaurus/utils-validation';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
|
import type {
|
||||||
|
TranslationFileContent,
|
||||||
|
TranslationFile,
|
||||||
|
TranslationMessage,
|
||||||
|
InitializedPlugin,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
export type WriteTranslationsOptions = {
|
export type WriteTranslationsOptions = {
|
||||||
override?: boolean;
|
override?: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue