fix(v2): sitemap plugin should handle siteConfig.trailingSlash automatically (#4950)

* create new @docusaurus/utils-common and move applyTrailingSlash there

* sitemap plugin should handle siteConfig.trailingSlash automatically

* typo
This commit is contained in:
Sébastien Lorber 2021-06-14 20:04:39 +02:00 committed by GitHub
parent 4e5f0febb9
commit aeb8e9da51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 127 additions and 35 deletions

View file

@ -20,6 +20,7 @@
"@docusaurus/core": "2.0.0-beta.0",
"@docusaurus/types": "2.0.0-beta.0",
"@docusaurus/utils": "2.0.0-beta.0",
"@docusaurus/utils-common": "2.0.0-beta.0",
"@docusaurus/utils-validation": "2.0.0-beta.0",
"fs-extra": "^10.0.0",
"sitemap": "^7.0.0",

View file

@ -30,8 +30,13 @@ describe('normalizeSitemapPluginOptions', () => {
priority: 0.9,
trailingSlash: false,
};
const {value} = await PluginOptionSchema.validate(userOptions);
const {value, warning} = await PluginOptionSchema.validate(userOptions);
expect(value).toEqual(userOptions);
expect(warning?.details?.length).toEqual(1);
expect(warning?.details[0].message).toMatchInlineSnapshot(
`"Option \\"trailingSlash\\" of the sitemap plugin is deprecated: Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it."`,
);
});
test('should reject out-of-range priority inputs', () => {

View file

@ -9,6 +9,7 @@ import {SitemapStream, streamToPromise} from 'sitemap';
import {PluginOptions} from './types';
import {DocusaurusConfig} from '@docusaurus/types';
import {addTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common';
export default async function createSitemap(
siteConfig: DocusaurusConfig,
@ -25,11 +26,21 @@ export default async function createSitemap(
hostname,
});
function applySitemapTrailingSlash(routePath: string): string {
// kept for retrocompatibility
// TODO remove deprecated trailingSlash option before 2022
if (options.trailingSlash) {
return addTrailingSlash(routePath);
} else {
return applyTrailingSlash(routePath, trailingSlash);
}
}
routesPaths
.filter((route) => !route.endsWith('404.html'))
.map((routePath) =>
sitemapStream.write({
url: trailingSlash ? addTrailingSlash(routePath) : routePath,
url: applySitemapTrailingSlash(routePath),
changefreq,
priority,
}),

View file

@ -25,5 +25,11 @@ export const PluginOptionSchema = Joi.object({
.valid(...Object.values(EnumChangefreq))
.default(DEFAULT_OPTIONS.changefreq),
priority: Joi.number().min(0).max(1).default(DEFAULT_OPTIONS.priority),
trailingSlash: Joi.bool().default(false),
trailingSlash: Joi.bool().default(false).warning('deprecate.error', {
msg:
'Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it.',
}),
}).messages({
'deprecate.error':
'Option {#label} of the sitemap plugin is deprecated: {#msg}',
});

View file

@ -0,0 +1,3 @@
# `@docusaurus/utils`
Common (Node/Browser) utility functions for Docusaurus packages.

View file

@ -0,0 +1,27 @@
{
"name": "@docusaurus/utils-common",
"version": "2.0.0-beta.0",
"description": "Common (Node/Browser) utility functions for Docusaurus packages.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/docusaurus.git",
"directory": "packages/docusaurus-utils-common"
},
"license": "MIT",
"dependencies": {
"@docusaurus/types": "2.0.0-beta.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=12.13.0"
}
}

View file

@ -86,4 +86,25 @@ describe('applyTrailingSlash', () => {
'/abc/?search#anchor',
);
});
test('should apply to fully qualified urls', () => {
expect(
applyTrailingSlash('https://xyz.com/abc?search#anchor', true),
).toEqual('https://xyz.com/abc/?search#anchor');
expect(
applyTrailingSlash('https://xyz.com/abc?search#anchor', false),
).toEqual('https://xyz.com/abc?search#anchor');
expect(
applyTrailingSlash('https://xyz.com/abc?search#anchor', undefined),
).toEqual('https://xyz.com/abc?search#anchor');
expect(
applyTrailingSlash('https://xyz.com/abc/?search#anchor', true),
).toEqual('https://xyz.com/abc/?search#anchor');
expect(
applyTrailingSlash('https://xyz.com/abc/?search#anchor', false),
).toEqual('https://xyz.com/abc?search#anchor');
expect(
applyTrailingSlash('https://xyz.com/abc/?search#anchor', undefined),
).toEqual('https://xyz.com/abc/?search#anchor');
});
});

View file

@ -14,12 +14,14 @@ export default function applyTrailingSlash(
return path;
}
// TODO deduplicate: also present in @docusaurus/utils
function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
function removeTrailingSlash(str: string): string {
return str.endsWith('/') ? str.slice(0, -1) : str;
}
// undefined = legacy retrocompatible behavior
if (typeof trailingSlash === 'undefined') {
return path;

View file

@ -0,0 +1,8 @@
/**
* 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.
*/
export {default as applyTrailingSlash} from './applyTrailingSlash';

View file

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"rootDir": "src",
"outDir": "lib",
"noEmitHelpers": false
}
}

View file

@ -35,6 +35,15 @@ export const logValidationBugReportHint = (): void => {
);
};
function printWarning(warning?: Joi.ValidationError) {
if (warning) {
const warningMessages = warning.details
.map(({message}) => message)
.join('\n');
console.log(chalk.yellow(warningMessages));
}
}
export function normalizePluginOptions<T extends {id?: string}>(
schema: Joi.ObjectSchema<T>,
options: Partial<T>,
@ -44,9 +53,12 @@ export function normalizePluginOptions<T extends {id?: string}>(
const finalSchema = schema.append({
id: PluginIdSchema,
});
const {error, value} = finalSchema.validate(options, {
const {error, warning, value} = finalSchema.validate(options, {
convert: false,
});
printWarning(warning);
if (error) {
logValidationBugReportHint();
if (isValidationDisabledEscapeHatch) {
@ -56,6 +68,7 @@ export function normalizePluginOptions<T extends {id?: string}>(
throw error;
}
}
return value;
}
@ -68,10 +81,12 @@ export function normalizeThemeConfig<T>(
// otherwise one theme would fail validating the data of another theme
const finalSchema = schema.unknown();
const {error, value} = finalSchema.validate(themeConfig, {
const {error, warning, value} = finalSchema.validate(themeConfig, {
convert: false,
});
printWarning(warning);
if (error) {
logValidationBugReportHint();
if (isValidationDisabledEscapeHatch) {
@ -112,6 +127,8 @@ export function validateFrontMatter<T>(
abortEarly: false,
});
printWarning(warning);
if (error) {
const frontMatterString = JSON.stringify(frontMatter, null, 2);
const errorDetails = error.details;
@ -132,12 +149,5 @@ export function validateFrontMatter<T>(
throw error;
}
if (warning) {
const warningMessages = warning.details
.map(({message}) => message)
.join('\n');
console.log(chalk.yellow(warningMessages));
}
return value;
}

View file

@ -1,3 +1,3 @@
# `@docusaurus/utils`
Node validation utility functions for Docusaurus packages.
Node utility functions for Docusaurus packages.

View file

@ -313,13 +313,15 @@ export function resolvePathname(to: string, from?: string): string {
export function addLeadingSlash(str: string): string {
return str.startsWith('/') ? str : `/${str}`;
}
export function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
export function addTrailingPathSeparator(str: string): string {
return str.endsWith(path.sep) ? str : `${str}${path.sep}`;
}
// TODO deduplicate: also present in @docusaurus/utils-common
export function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
export function removeTrailingSlash(str: string): string {
return removeSuffix(str, '/');
}

View file

@ -51,6 +51,7 @@
"@docusaurus/react-loadable": "5.5.0",
"@docusaurus/types": "2.0.0-beta.0",
"@docusaurus/utils": "2.0.0-beta.0",
"@docusaurus/utils-common": "2.0.0-beta.0",
"@docusaurus/utils-validation": "2.0.0-beta.0",
"@slorber/static-site-generator-webpack-plugin": "^4.0.0",
"@svgr/webpack": "^5.5.0",

View file

@ -13,7 +13,7 @@ import isInternalUrl from './isInternalUrl';
import ExecutionEnvironment from './ExecutionEnvironment';
import {useLinksCollector} from '../LinksCollector';
import {useBaseUrlUtils} from './useBaseUrl';
import applyTrailingSlash from './applyTrailingSlash';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import type {LinkProps} from '@docusaurus/Link';
import type docusaurus from '../docusaurus';

View file

@ -6,7 +6,7 @@
*/
import {RouteConfig} from '@docusaurus/types';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
import {applyTrailingSlash} from '@docusaurus/utils-common';
export default function applyRouteTrailingSlash(
route: RouteConfig,
@ -17,23 +17,9 @@ export default function applyRouteTrailingSlash(
return route;
}
function getNewRoutePath() {
// undefined = legacy retrocompatible behavior
if (typeof trailingSlash === 'undefined') {
return route.path;
}
// The trailing slash should be handled before the ?search#hash !
// For routing #anchor is normally not possible, but querystring remains possible
const [pathname] = route.path.split(/[#?]/);
const newPathname = trailingSlash
? addTrailingSlash(pathname)
: removeTrailingSlash(pathname);
return route.path.replace(pathname, newPathname);
}
return {
...route,
path: getNewRoutePath(),
path: applyTrailingSlash(route.path, trailingSlash),
...(route.routes && {
routes: route.routes.map((subroute) =>
applyRouteTrailingSlash(subroute, trailingSlash),

View file

@ -10,7 +10,6 @@ Object {
"@docusaurus/Link": "../../client/exports/Link.tsx",
"@docusaurus/Noop": "../../client/exports/Noop.ts",
"@docusaurus/Translate": "../../client/exports/Translate.tsx",
"@docusaurus/applyTrailingSlash": "../../client/exports/applyTrailingSlash.tsx",
"@docusaurus/constants": "../../client/exports/constants.ts",
"@docusaurus/context": "../../client/exports/context.ts",
"@docusaurus/isInternalUrl": "../../client/exports/isInternalUrl.ts",