mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-24 06:27:02 +02:00
fix(v2): never remove trailing slash from site root like '/baseUrl/' (#5082)
* never apply trailingSlash to site root ('/baseUrl/') => only subroutes * add deprecation comment for loadContext.baseUrl in favor of loadContext.siteConfig.baseUrl * commit typo * useless code
This commit is contained in:
parent
41b78466da
commit
7592982960
11 changed files with 254 additions and 102 deletions
|
@ -17,7 +17,10 @@ import {
|
||||||
createToExtensionsRedirects,
|
createToExtensionsRedirects,
|
||||||
} from './extensionRedirects';
|
} from './extensionRedirects';
|
||||||
import {validateRedirect} from './redirectValidation';
|
import {validateRedirect} from './redirectValidation';
|
||||||
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
import {
|
||||||
|
applyTrailingSlash,
|
||||||
|
ApplyTrailingSlashParams,
|
||||||
|
} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
@ -26,7 +29,12 @@ export default function collectRedirects(
|
||||||
trailingSlash: boolean | undefined,
|
trailingSlash: boolean | undefined,
|
||||||
): RedirectMetadata[] {
|
): RedirectMetadata[] {
|
||||||
let redirects = doCollectRedirects(pluginContext);
|
let redirects = doCollectRedirects(pluginContext);
|
||||||
redirects = applyRedirectsTrailingSlash(redirects, trailingSlash);
|
|
||||||
|
redirects = applyRedirectsTrailingSlash(redirects, {
|
||||||
|
trailingSlash,
|
||||||
|
baseUrl: pluginContext.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
validateCollectedRedirects(redirects, pluginContext);
|
validateCollectedRedirects(redirects, pluginContext);
|
||||||
return filterUnwantedRedirects(redirects, pluginContext);
|
return filterUnwantedRedirects(redirects, pluginContext);
|
||||||
}
|
}
|
||||||
|
@ -37,12 +45,12 @@ export default function collectRedirects(
|
||||||
// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
|
// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
|
||||||
function applyRedirectsTrailingSlash(
|
function applyRedirectsTrailingSlash(
|
||||||
redirects: RedirectMetadata[],
|
redirects: RedirectMetadata[],
|
||||||
trailingSlash: boolean | undefined,
|
params: ApplyTrailingSlashParams,
|
||||||
) {
|
) {
|
||||||
return redirects.map((redirect) => {
|
return redirects.map((redirect) => {
|
||||||
return {
|
return {
|
||||||
...redirect,
|
...redirect,
|
||||||
to: applyTrailingSlash(redirect.to, trailingSlash),
|
to: applyTrailingSlash(redirect.to, params),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,10 @@ export default async function createSitemap(
|
||||||
if (options.trailingSlash) {
|
if (options.trailingSlash) {
|
||||||
return addTrailingSlash(routePath);
|
return addTrailingSlash(routePath);
|
||||||
} else {
|
} else {
|
||||||
return applyTrailingSlash(routePath, siteConfig.trailingSlash);
|
return applyTrailingSlash(routePath, {
|
||||||
|
trailingSlash: siteConfig.trailingSlash,
|
||||||
|
baseUrl: siteConfig.baseUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
packages/docusaurus-types/src/index.d.ts
vendored
2
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -180,7 +180,7 @@ export interface LoadContext {
|
||||||
siteConfig: DocusaurusConfig;
|
siteConfig: DocusaurusConfig;
|
||||||
siteConfigPath: string;
|
siteConfigPath: string;
|
||||||
outDir: string;
|
outDir: string;
|
||||||
baseUrl: string;
|
baseUrl: string; // TODO to remove: useless, there's already siteConfig.baseUrl!
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
ssrTemplate?: string;
|
ssrTemplate?: string;
|
||||||
codeTranslations: Record<string, string>;
|
codeTranslations: Record<string, string>;
|
||||||
|
|
|
@ -5,116 +5,174 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import applyTrailingSlash from '../applyTrailingSlash';
|
import applyTrailingSlash, {
|
||||||
|
ApplyTrailingSlashParams,
|
||||||
|
} from '../applyTrailingSlash';
|
||||||
|
|
||||||
|
function params(
|
||||||
|
trailingSlash: boolean | undefined,
|
||||||
|
baseUrl: string = '/',
|
||||||
|
): ApplyTrailingSlashParams {
|
||||||
|
return {trailingSlash, baseUrl};
|
||||||
|
}
|
||||||
|
|
||||||
describe('applyTrailingSlash', () => {
|
describe('applyTrailingSlash', () => {
|
||||||
test('should apply to empty', () => {
|
test('should apply to empty', () => {
|
||||||
expect(applyTrailingSlash('', true)).toEqual('/');
|
expect(applyTrailingSlash('', params(true))).toEqual('/');
|
||||||
expect(applyTrailingSlash('', false)).toEqual('');
|
expect(applyTrailingSlash('', params(false))).toEqual('');
|
||||||
expect(applyTrailingSlash('', undefined)).toEqual('');
|
expect(applyTrailingSlash('', params(undefined))).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not apply to /', () => {
|
test('should not apply to /', () => {
|
||||||
expect(applyTrailingSlash('/', true)).toEqual('/');
|
expect(applyTrailingSlash('/', params(true))).toEqual('/');
|
||||||
expect(applyTrailingSlash('/', false)).toEqual('/');
|
expect(applyTrailingSlash('/', params(false))).toEqual('/');
|
||||||
expect(applyTrailingSlash('/', undefined)).toEqual('/');
|
expect(applyTrailingSlash('/', params(undefined))).toEqual('/');
|
||||||
|
|
||||||
expect(applyTrailingSlash('/?query#anchor', true)).toEqual(
|
expect(applyTrailingSlash('/?query#anchor', params(true))).toEqual(
|
||||||
'/?query#anchor',
|
'/?query#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/?query#anchor', false)).toEqual(
|
expect(applyTrailingSlash('/?query#anchor', params(false))).toEqual(
|
||||||
'/?query#anchor',
|
'/?query#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/?query#anchor', undefined)).toEqual(
|
expect(applyTrailingSlash('/?query#anchor', params(undefined))).toEqual(
|
||||||
'/?query#anchor',
|
'/?query#anchor',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not apply to /baseUrl/', () => {
|
||||||
|
const baseUrl = '/baseUrl/';
|
||||||
|
expect(applyTrailingSlash('/baseUrl/', params(true, baseUrl))).toEqual(
|
||||||
|
'/baseUrl/',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/baseUrl/', params(false, baseUrl))).toEqual(
|
||||||
|
'/baseUrl/',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/baseUrl/', params(undefined, baseUrl))).toEqual(
|
||||||
|
'/baseUrl/',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
applyTrailingSlash('/baseUrl/?query#anchor', params(true, baseUrl)),
|
||||||
|
).toEqual('/baseUrl/?query#anchor');
|
||||||
|
expect(
|
||||||
|
applyTrailingSlash('/baseUrl/?query#anchor', params(false, baseUrl)),
|
||||||
|
).toEqual('/baseUrl/?query#anchor');
|
||||||
|
expect(
|
||||||
|
applyTrailingSlash('/baseUrl/?query#anchor', params(undefined, baseUrl)),
|
||||||
|
).toEqual('/baseUrl/?query#anchor');
|
||||||
|
});
|
||||||
|
|
||||||
test('should not apply to #anchor links ', () => {
|
test('should not apply to #anchor links ', () => {
|
||||||
expect(applyTrailingSlash('#', true)).toEqual('#');
|
expect(applyTrailingSlash('#', params(true))).toEqual('#');
|
||||||
expect(applyTrailingSlash('#', false)).toEqual('#');
|
expect(applyTrailingSlash('#', params(false))).toEqual('#');
|
||||||
expect(applyTrailingSlash('#', undefined)).toEqual('#');
|
expect(applyTrailingSlash('#', params(undefined))).toEqual('#');
|
||||||
expect(applyTrailingSlash('#anchor', true)).toEqual('#anchor');
|
expect(applyTrailingSlash('#anchor', params(true))).toEqual('#anchor');
|
||||||
expect(applyTrailingSlash('#anchor', false)).toEqual('#anchor');
|
expect(applyTrailingSlash('#anchor', params(false))).toEqual('#anchor');
|
||||||
expect(applyTrailingSlash('#anchor', undefined)).toEqual('#anchor');
|
expect(applyTrailingSlash('#anchor', params(undefined))).toEqual('#anchor');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should apply to simple paths', () => {
|
test('should apply to simple paths', () => {
|
||||||
expect(applyTrailingSlash('abc', true)).toEqual('abc/');
|
expect(applyTrailingSlash('abc', params(true))).toEqual('abc/');
|
||||||
expect(applyTrailingSlash('abc', false)).toEqual('abc');
|
expect(applyTrailingSlash('abc', params(false))).toEqual('abc');
|
||||||
expect(applyTrailingSlash('abc', undefined)).toEqual('abc');
|
expect(applyTrailingSlash('abc', params(undefined))).toEqual('abc');
|
||||||
expect(applyTrailingSlash('abc/', true)).toEqual('abc/');
|
expect(applyTrailingSlash('abc/', params(true))).toEqual('abc/');
|
||||||
expect(applyTrailingSlash('abc/', false)).toEqual('abc');
|
expect(applyTrailingSlash('abc/', params(false))).toEqual('abc');
|
||||||
expect(applyTrailingSlash('abc/', undefined)).toEqual('abc/');
|
expect(applyTrailingSlash('abc/', params(undefined))).toEqual('abc/');
|
||||||
expect(applyTrailingSlash('/abc', true)).toEqual('/abc/');
|
expect(applyTrailingSlash('/abc', params(true))).toEqual('/abc/');
|
||||||
expect(applyTrailingSlash('/abc', false)).toEqual('/abc');
|
expect(applyTrailingSlash('/abc', params(false))).toEqual('/abc');
|
||||||
expect(applyTrailingSlash('/abc', undefined)).toEqual('/abc');
|
expect(applyTrailingSlash('/abc', params(undefined))).toEqual('/abc');
|
||||||
expect(applyTrailingSlash('/abc/', true)).toEqual('/abc/');
|
expect(applyTrailingSlash('/abc/', params(true))).toEqual('/abc/');
|
||||||
expect(applyTrailingSlash('/abc/', false)).toEqual('/abc');
|
expect(applyTrailingSlash('/abc/', params(false))).toEqual('/abc');
|
||||||
expect(applyTrailingSlash('/abc/', undefined)).toEqual('/abc/');
|
expect(applyTrailingSlash('/abc/', params(undefined))).toEqual('/abc/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should apply to path with #anchor', () => {
|
test('should apply to path with #anchor', () => {
|
||||||
expect(applyTrailingSlash('/abc#anchor', true)).toEqual('/abc/#anchor');
|
expect(applyTrailingSlash('/abc#anchor', params(true))).toEqual(
|
||||||
expect(applyTrailingSlash('/abc#anchor', false)).toEqual('/abc#anchor');
|
'/abc/#anchor',
|
||||||
expect(applyTrailingSlash('/abc#anchor', undefined)).toEqual('/abc#anchor');
|
);
|
||||||
expect(applyTrailingSlash('/abc/#anchor', true)).toEqual('/abc/#anchor');
|
expect(applyTrailingSlash('/abc#anchor', params(false))).toEqual(
|
||||||
expect(applyTrailingSlash('/abc/#anchor', false)).toEqual('/abc#anchor');
|
'/abc#anchor',
|
||||||
expect(applyTrailingSlash('/abc/#anchor', undefined)).toEqual(
|
);
|
||||||
|
expect(applyTrailingSlash('/abc#anchor', params(undefined))).toEqual(
|
||||||
|
'/abc#anchor',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/#anchor', params(true))).toEqual(
|
||||||
|
'/abc/#anchor',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/#anchor', params(false))).toEqual(
|
||||||
|
'/abc#anchor',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/#anchor', params(undefined))).toEqual(
|
||||||
'/abc/#anchor',
|
'/abc/#anchor',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should apply to path with ?search', () => {
|
test('should apply to path with ?search', () => {
|
||||||
expect(applyTrailingSlash('/abc?search', true)).toEqual('/abc/?search');
|
expect(applyTrailingSlash('/abc?search', params(true))).toEqual(
|
||||||
expect(applyTrailingSlash('/abc?search', false)).toEqual('/abc?search');
|
'/abc/?search',
|
||||||
expect(applyTrailingSlash('/abc?search', undefined)).toEqual('/abc?search');
|
);
|
||||||
expect(applyTrailingSlash('/abc/?search', true)).toEqual('/abc/?search');
|
expect(applyTrailingSlash('/abc?search', params(false))).toEqual(
|
||||||
expect(applyTrailingSlash('/abc/?search', false)).toEqual('/abc?search');
|
'/abc?search',
|
||||||
expect(applyTrailingSlash('/abc/?search', undefined)).toEqual(
|
);
|
||||||
|
expect(applyTrailingSlash('/abc?search', params(undefined))).toEqual(
|
||||||
|
'/abc?search',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/?search', params(true))).toEqual(
|
||||||
|
'/abc/?search',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/?search', params(false))).toEqual(
|
||||||
|
'/abc?search',
|
||||||
|
);
|
||||||
|
expect(applyTrailingSlash('/abc/?search', params(undefined))).toEqual(
|
||||||
'/abc/?search',
|
'/abc/?search',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should apply to path with ?search#anchor', () => {
|
test('should apply to path with ?search#anchor', () => {
|
||||||
expect(applyTrailingSlash('/abc?search#anchor', true)).toEqual(
|
expect(applyTrailingSlash('/abc?search#anchor', params(true))).toEqual(
|
||||||
'/abc/?search#anchor',
|
'/abc/?search#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/abc?search#anchor', false)).toEqual(
|
expect(applyTrailingSlash('/abc?search#anchor', params(false))).toEqual(
|
||||||
'/abc?search#anchor',
|
'/abc?search#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/abc?search#anchor', undefined)).toEqual(
|
expect(applyTrailingSlash('/abc?search#anchor', params(undefined))).toEqual(
|
||||||
'/abc?search#anchor',
|
'/abc?search#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/abc/?search#anchor', true)).toEqual(
|
expect(applyTrailingSlash('/abc/?search#anchor', params(true))).toEqual(
|
||||||
'/abc/?search#anchor',
|
'/abc/?search#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/abc/?search#anchor', false)).toEqual(
|
expect(applyTrailingSlash('/abc/?search#anchor', params(false))).toEqual(
|
||||||
'/abc?search#anchor',
|
'/abc?search#anchor',
|
||||||
);
|
);
|
||||||
expect(applyTrailingSlash('/abc/?search#anchor', undefined)).toEqual(
|
expect(
|
||||||
'/abc/?search#anchor',
|
applyTrailingSlash('/abc/?search#anchor', params(undefined)),
|
||||||
);
|
).toEqual('/abc/?search#anchor');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should apply to fully qualified urls', () => {
|
test('should apply to fully qualified urls', () => {
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc?search#anchor', true),
|
applyTrailingSlash('https://xyz.com/abc?search#anchor', params(true)),
|
||||||
).toEqual('https://xyz.com/abc/?search#anchor');
|
).toEqual('https://xyz.com/abc/?search#anchor');
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc?search#anchor', false),
|
applyTrailingSlash('https://xyz.com/abc?search#anchor', params(false)),
|
||||||
).toEqual('https://xyz.com/abc?search#anchor');
|
).toEqual('https://xyz.com/abc?search#anchor');
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc?search#anchor', undefined),
|
applyTrailingSlash(
|
||||||
|
'https://xyz.com/abc?search#anchor',
|
||||||
|
params(undefined),
|
||||||
|
),
|
||||||
).toEqual('https://xyz.com/abc?search#anchor');
|
).toEqual('https://xyz.com/abc?search#anchor');
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc/?search#anchor', true),
|
applyTrailingSlash('https://xyz.com/abc/?search#anchor', params(true)),
|
||||||
).toEqual('https://xyz.com/abc/?search#anchor');
|
).toEqual('https://xyz.com/abc/?search#anchor');
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc/?search#anchor', false),
|
applyTrailingSlash('https://xyz.com/abc/?search#anchor', params(false)),
|
||||||
).toEqual('https://xyz.com/abc?search#anchor');
|
).toEqual('https://xyz.com/abc?search#anchor');
|
||||||
expect(
|
expect(
|
||||||
applyTrailingSlash('https://xyz.com/abc/?search#anchor', undefined),
|
applyTrailingSlash(
|
||||||
|
'https://xyz.com/abc/?search#anchor',
|
||||||
|
params(undefined),
|
||||||
|
),
|
||||||
).toEqual('https://xyz.com/abc/?search#anchor');
|
).toEqual('https://xyz.com/abc/?search#anchor');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,10 +5,20 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {DocusaurusConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
|
export type ApplyTrailingSlashParams = Pick<
|
||||||
|
DocusaurusConfig,
|
||||||
|
'trailingSlash' | 'baseUrl'
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Trailing slash handling depends in some site configuration options
|
||||||
export default function applyTrailingSlash(
|
export default function applyTrailingSlash(
|
||||||
path: string,
|
path: string,
|
||||||
trailingSlash: boolean | undefined,
|
options: ApplyTrailingSlashParams,
|
||||||
): string {
|
): string {
|
||||||
|
const {trailingSlash, baseUrl} = options;
|
||||||
|
|
||||||
if (path.startsWith('#')) {
|
if (path.startsWith('#')) {
|
||||||
// Never apply trailing slash to an anchor link
|
// Never apply trailing slash to an anchor link
|
||||||
return path;
|
return path;
|
||||||
|
@ -34,7 +44,14 @@ export default function applyTrailingSlash(
|
||||||
const [pathname] = path.split(/[#?]/);
|
const [pathname] = path.split(/[#?]/);
|
||||||
|
|
||||||
// Never transform '/' to ''
|
// Never transform '/' to ''
|
||||||
const newPathname =
|
// Never remove the baseUrl trailing slash!
|
||||||
pathname === '/' ? '/' : handleTrailingSlash(pathname, trailingSlash);
|
// If baseUrl = /myBase/, we want to emit /myBase/index.html and not /myBase.html !
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/5077
|
||||||
|
const shouldNotApply = pathname === '/' || pathname === baseUrl;
|
||||||
|
|
||||||
|
const newPathname = shouldNotApply
|
||||||
|
? pathname
|
||||||
|
: handleTrailingSlash(pathname, trailingSlash);
|
||||||
|
|
||||||
return path.replace(pathname, newPathname);
|
return path.replace(pathname, newPathname);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {default as applyTrailingSlash} from './applyTrailingSlash';
|
export {default as applyTrailingSlash} from './applyTrailingSlash';
|
||||||
|
export type {ApplyTrailingSlashParams} from './applyTrailingSlash';
|
||||||
|
|
||||||
export {default as uniq} from './uniq';
|
export {default as uniq} from './uniq';
|
||||||
|
|
|
@ -42,7 +42,7 @@ function Link({
|
||||||
...props
|
...props
|
||||||
}: LinkProps): JSX.Element {
|
}: LinkProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
siteConfig: {trailingSlash},
|
siteConfig: {trailingSlash, baseUrl},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
const {withBaseUrl} = useBaseUrlUtils();
|
||||||
const linksCollector = useLinksCollector();
|
const linksCollector = useLinksCollector();
|
||||||
|
@ -80,7 +80,7 @@ function Link({
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (targetLink && isInternal) {
|
if (targetLink && isInternal) {
|
||||||
targetLink = applyTrailingSlash(targetLink, trailingSlash);
|
targetLink = applyTrailingSlash(targetLink, {trailingSlash, baseUrl});
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloaded = useRef(false);
|
const preloaded = useRef(false);
|
||||||
|
|
|
@ -116,7 +116,7 @@ export async function loadContext(
|
||||||
siteConfig,
|
siteConfig,
|
||||||
siteConfigPath,
|
siteConfigPath,
|
||||||
outDir,
|
outDir,
|
||||||
baseUrl,
|
baseUrl, // TODO to remove: useless, there's already siteConfig.baseUrl! (and yes, it's the same value, cf code above)
|
||||||
i18n,
|
i18n,
|
||||||
ssrTemplate,
|
ssrTemplate,
|
||||||
codeTranslations,
|
codeTranslations,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import applyRouteTrailingSlash from '../applyRouteTrailingSlash';
|
import applyRouteTrailingSlash from '../applyRouteTrailingSlash';
|
||||||
import {RouteConfig} from '@docusaurus/types';
|
import {RouteConfig} from '@docusaurus/types';
|
||||||
|
import {ApplyTrailingSlashParams} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
function route(path: string, subRoutes?: string[]): RouteConfig {
|
function route(path: string, subRoutes?: string[]): RouteConfig {
|
||||||
const result: RouteConfig = {path, component: 'any'};
|
const result: RouteConfig = {path, component: 'any'};
|
||||||
|
@ -18,76 +19,126 @@ function route(path: string, subRoutes?: string[]): RouteConfig {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function params(
|
||||||
|
trailingSlash: boolean | undefined,
|
||||||
|
baseUrl: string = '/',
|
||||||
|
): ApplyTrailingSlashParams {
|
||||||
|
return {trailingSlash, baseUrl};
|
||||||
|
}
|
||||||
|
|
||||||
describe('applyRouteTrailingSlash', () => {
|
describe('applyRouteTrailingSlash', () => {
|
||||||
test('apply to empty', () => {
|
test('apply to empty', () => {
|
||||||
expect(applyRouteTrailingSlash(route(''), true)).toEqual(route('/'));
|
expect(applyRouteTrailingSlash(route(''), params(true))).toEqual(
|
||||||
expect(applyRouteTrailingSlash(route(''), false)).toEqual(route(''));
|
route('/'),
|
||||||
expect(applyRouteTrailingSlash(route(''), undefined)).toEqual(route(''));
|
);
|
||||||
|
expect(applyRouteTrailingSlash(route(''), params(false))).toEqual(
|
||||||
|
route(''),
|
||||||
|
);
|
||||||
|
expect(applyRouteTrailingSlash(route(''), params(undefined))).toEqual(
|
||||||
|
route(''),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to /', () => {
|
test('apply to /', () => {
|
||||||
expect(applyRouteTrailingSlash(route('/'), true)).toEqual(route('/'));
|
expect(applyRouteTrailingSlash(route('/'), params(true))).toEqual(
|
||||||
expect(applyRouteTrailingSlash(route('/'), false)).toEqual(route('/'));
|
route('/'),
|
||||||
expect(applyRouteTrailingSlash(route('/'), undefined)).toEqual(route('/'));
|
);
|
||||||
|
expect(applyRouteTrailingSlash(route('/'), params(false))).toEqual(
|
||||||
|
route('/'),
|
||||||
|
);
|
||||||
|
expect(applyRouteTrailingSlash(route('/'), params(undefined))).toEqual(
|
||||||
|
route('/'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to /abc', () => {
|
test('apply to /abc', () => {
|
||||||
expect(applyRouteTrailingSlash(route('/abc'), true)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc'), params(true))).toEqual(
|
||||||
route('/abc/'),
|
route('/abc/'),
|
||||||
);
|
);
|
||||||
expect(applyRouteTrailingSlash(route('/abc'), false)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc'), params(false))).toEqual(
|
||||||
route('/abc'),
|
route('/abc'),
|
||||||
);
|
);
|
||||||
expect(applyRouteTrailingSlash(route('/abc'), undefined)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc'), params(undefined))).toEqual(
|
||||||
route('/abc'),
|
route('/abc'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to /abc/', () => {
|
test('apply to /abc/', () => {
|
||||||
expect(applyRouteTrailingSlash(route('/abc/'), true)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc/'), params(true))).toEqual(
|
||||||
route('/abc/'),
|
route('/abc/'),
|
||||||
);
|
);
|
||||||
expect(applyRouteTrailingSlash(route('/abc/'), false)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc/'), params(false))).toEqual(
|
||||||
route('/abc'),
|
route('/abc'),
|
||||||
);
|
);
|
||||||
expect(applyRouteTrailingSlash(route('/abc/'), undefined)).toEqual(
|
expect(applyRouteTrailingSlash(route('/abc/'), params(undefined))).toEqual(
|
||||||
route('/abc/'),
|
route('/abc/'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to /abc?search#anchor', () => {
|
test('apply to /abc?search#anchor', () => {
|
||||||
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), true)).toEqual(
|
|
||||||
route('/abc/?search#anchor'),
|
|
||||||
);
|
|
||||||
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), false)).toEqual(
|
|
||||||
route('/abc?search#anchor'),
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc?search#anchor'), undefined),
|
applyRouteTrailingSlash(route('/abc?search#anchor'), params(true)),
|
||||||
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(route('/abc?search#anchor'), params(false)),
|
||||||
|
).toEqual(route('/abc?search#anchor'));
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(route('/abc?search#anchor'), params(undefined)),
|
||||||
).toEqual(route('/abc?search#anchor'));
|
).toEqual(route('/abc?search#anchor'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to /abc/?search#anchor', () => {
|
test('apply to /abc/?search#anchor', () => {
|
||||||
expect(applyRouteTrailingSlash(route('/abc/?search#anchor'), true)).toEqual(
|
|
||||||
route('/abc/?search#anchor'),
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc/?search#anchor'), false),
|
applyRouteTrailingSlash(route('/abc/?search#anchor'), params(true)),
|
||||||
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(route('/abc/?search#anchor'), params(false)),
|
||||||
).toEqual(route('/abc?search#anchor'));
|
).toEqual(route('/abc?search#anchor'));
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc/?search#anchor'), undefined),
|
applyRouteTrailingSlash(route('/abc/?search#anchor'), params(undefined)),
|
||||||
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not apply to /abc/?search#anchor when baseUrl=/abc/', () => {
|
||||||
|
const baseUrl = '/abc/';
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc/?search#anchor'),
|
||||||
|
params(true, baseUrl),
|
||||||
|
),
|
||||||
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc/?search#anchor'),
|
||||||
|
params(false, baseUrl),
|
||||||
|
),
|
||||||
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc/?search#anchor'),
|
||||||
|
params(undefined, baseUrl),
|
||||||
|
),
|
||||||
).toEqual(route('/abc/?search#anchor'));
|
).toEqual(route('/abc/?search#anchor'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apply to subroutes', () => {
|
test('apply to subroutes', () => {
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), true),
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc', ['/abc/1', '/abc/2']),
|
||||||
|
params(true),
|
||||||
|
),
|
||||||
).toEqual(route('/abc/', ['/abc/1/', '/abc/2/']));
|
).toEqual(route('/abc/', ['/abc/1/', '/abc/2/']));
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), false),
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc', ['/abc/1', '/abc/2']),
|
||||||
|
params(false),
|
||||||
|
),
|
||||||
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), undefined),
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc', ['/abc/1', '/abc/2']),
|
||||||
|
params(undefined),
|
||||||
|
),
|
||||||
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,10 +146,20 @@ describe('applyRouteTrailingSlash', () => {
|
||||||
expect(
|
expect(
|
||||||
applyRouteTrailingSlash(
|
applyRouteTrailingSlash(
|
||||||
route('/abc?search#anchor', ['/abc/1?search', '/abc/2#anchor']),
|
route('/abc?search#anchor', ['/abc/1?search', '/abc/2#anchor']),
|
||||||
true,
|
params(true),
|
||||||
),
|
),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
route('/abc/?search#anchor', ['/abc/1/?search', '/abc/2/#anchor']),
|
route('/abc/?search#anchor', ['/abc/1/?search', '/abc/2/#anchor']),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('apply for complex case with baseUrl', () => {
|
||||||
|
const baseUrl = '/abc/';
|
||||||
|
expect(
|
||||||
|
applyRouteTrailingSlash(
|
||||||
|
route('/abc/?search#anchor', ['/abc/1?search', '/abc/2#anchor']),
|
||||||
|
params(false, baseUrl),
|
||||||
|
),
|
||||||
|
).toEqual(route('/abc/?search#anchor', ['/abc/1?search', '/abc/2#anchor']));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,18 +6,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RouteConfig} from '@docusaurus/types';
|
import {RouteConfig} from '@docusaurus/types';
|
||||||
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
import {
|
||||||
|
applyTrailingSlash,
|
||||||
|
ApplyTrailingSlashParams,
|
||||||
|
} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
export default function applyRouteTrailingSlash(
|
export default function applyRouteTrailingSlash(
|
||||||
route: RouteConfig,
|
route: RouteConfig,
|
||||||
trailingSlash: boolean | undefined,
|
params: ApplyTrailingSlashParams,
|
||||||
): RouteConfig {
|
): RouteConfig {
|
||||||
return {
|
return {
|
||||||
...route,
|
...route,
|
||||||
path: applyTrailingSlash(route.path, trailingSlash),
|
path: applyTrailingSlash(route.path, params),
|
||||||
...(route.routes && {
|
...(route.routes && {
|
||||||
routes: route.routes.map((subroute) =>
|
routes: route.routes.map((subroute) =>
|
||||||
applyRouteTrailingSlash(subroute, trailingSlash),
|
applyRouteTrailingSlash(subroute, params),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -142,10 +142,10 @@ export async function loadPlugins({
|
||||||
initialRouteConfig,
|
initialRouteConfig,
|
||||||
) => {
|
) => {
|
||||||
// Trailing slash behavior is handled in a generic way for all plugins
|
// Trailing slash behavior is handled in a generic way for all plugins
|
||||||
const finalRouteConfig = applyRouteTrailingSlash(
|
const finalRouteConfig = applyRouteTrailingSlash(initialRouteConfig, {
|
||||||
initialRouteConfig,
|
trailingSlash: context.siteConfig.trailingSlash,
|
||||||
context.siteConfig.trailingSlash,
|
baseUrl: context.siteConfig.baseUrl,
|
||||||
);
|
});
|
||||||
pluginsRouteConfigs.push(finalRouteConfig);
|
pluginsRouteConfigs.push(finalRouteConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue