Skip to content

Commit

Permalink
refactor(client-redirects): elaborate documentation, minor refactor (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena committed Jun 13, 2022
1 parent 27834dc commit fb3138d
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 109 deletions.
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`toRedirectFilesMetadata creates appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = `
exports[`toRedirectFiles creates appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = `
[
"<!DOCTYPE html>
<html>
Expand All @@ -16,7 +16,7 @@ exports[`toRedirectFilesMetadata creates appropriate metadata for empty baseUrl:
]
`;
exports[`toRedirectFilesMetadata creates appropriate metadata for root baseUrl: fileContent baseUrl=/ 1`] = `
exports[`toRedirectFiles creates appropriate metadata for root baseUrl: fileContent baseUrl=/ 1`] = `
[
"<!DOCTYPE html>
<html>
Expand All @@ -32,7 +32,7 @@ exports[`toRedirectFilesMetadata creates appropriate metadata for root baseUrl:
]
`;
exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=false: fileContent 1`] = `
exports[`toRedirectFiles creates appropriate metadata trailingSlash=false: fileContent 1`] = `
[
"<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -70,7 +70,7 @@ exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=fals
]
`;
exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=true: fileContent 1`] = `
exports[`toRedirectFiles creates appropriate metadata trailingSlash=true: fileContent 1`] = `
[
"<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -108,7 +108,7 @@ exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=true
]
`;
exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=undefined: fileContent 1`] = `
exports[`toRedirectFiles creates appropriate metadata trailingSlash=undefined: fileContent 1`] = `
[
"<!DOCTYPE html>
<html>
Expand Down
Expand Up @@ -9,7 +9,7 @@ import fs from 'fs-extra';
import path from 'path';

import writeRedirectFiles, {
toRedirectFilesMetadata,
toRedirectFiles,
createToUrl,
} from '../writeRedirectFiles';

Expand Down Expand Up @@ -42,14 +42,14 @@ describe('createToUrl', () => {
});
});

describe('toRedirectFilesMetadata', () => {
describe('toRedirectFiles', () => {
it('creates appropriate metadata trailingSlash=undefined', () => {
const pluginContext = {
outDir: '/tmp/someFixedOutDir',
baseUrl: 'https://docusaurus.io',
};

const redirectFiles = toRedirectFilesMetadata(
const redirectFiles = toRedirectFiles(
[
{from: '/abc.html', to: '/abc'},
{from: '/def', to: '/def.html'},
Expand All @@ -76,7 +76,7 @@ describe('toRedirectFilesMetadata', () => {
baseUrl: 'https://docusaurus.io',
};

const redirectFiles = toRedirectFilesMetadata(
const redirectFiles = toRedirectFiles(
[
{from: '/abc.html', to: '/abc'},
{from: '/def', to: '/def.html'},
Expand All @@ -103,7 +103,7 @@ describe('toRedirectFilesMetadata', () => {
baseUrl: 'https://docusaurus.io',
};

const redirectFiles = toRedirectFilesMetadata(
const redirectFiles = toRedirectFiles(
[
{from: '/abc.html', to: '/abc'},
{from: '/def', to: '/def.html'},
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('toRedirectFilesMetadata', () => {
outDir: '/tmp/someFixedOutDir',
baseUrl: '/',
};
const redirectFiles = toRedirectFilesMetadata(
const redirectFiles = toRedirectFiles(
[{from: '/abc.html', to: '/abc'}],
pluginContext,
undefined,
Expand All @@ -147,7 +147,7 @@ describe('toRedirectFilesMetadata', () => {
outDir: '/tmp/someFixedOutDir',
baseUrl: '',
};
const redirectFiles = toRedirectFilesMetadata(
const redirectFiles = toRedirectFiles(
[{from: '/abc.html', to: '/abc'}],
pluginContext,
undefined,
Expand Down
106 changes: 43 additions & 63 deletions packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts
Expand Up @@ -7,52 +7,57 @@

import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
applyTrailingSlash,
type ApplyTrailingSlashParams,
} from '@docusaurus/utils-common';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {
createFromExtensionsRedirects,
createToExtensionsRedirects,
} from './extensionRedirects';
import {validateRedirect} from './redirectValidation';
import type {PluginOptions, RedirectOption} from './options';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginContext, RedirectItem} from './types';

export default function collectRedirects(
pluginContext: PluginContext,
trailingSlash: boolean | undefined,
): RedirectMetadata[] {
let redirects = doCollectRedirects(pluginContext);

redirects = applyRedirectsTrailingSlash(redirects, {
trailingSlash,
baseUrl: pluginContext.baseUrl,
});
): RedirectItem[] {
// For each plugin config option, create the appropriate redirects
const redirects = [
...createFromExtensionsRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.fromExtensions,
),
...createToExtensionsRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.toExtensions,
),
...createRedirectsOptionRedirects(pluginContext.options.redirects),
...createCreateRedirectsOptionRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.createRedirects,
),
].map((redirect) => ({
...redirect,
// Given a redirect with `to: "/abc"` and `trailingSlash` enabled:
//
// - We don't want to reject `to: "/abc"`, as that unambiguously points to
// `/abc/` now;
// - We want to redirect `to: /abc/` without the user having to change all
// her redirect plugin options
//
// It should be easy to toggle `trailingSlash` option without having to
// change other configs
to: applyTrailingSlash(redirect.to, {
trailingSlash,
baseUrl: pluginContext.baseUrl,
}),
}));

validateCollectedRedirects(redirects, pluginContext);
return filterUnwantedRedirects(redirects, pluginContext);
}

// If users wants to redirect to=/abc and they enable trailingSlash=true then
// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid
// path now)
// => we want to redirect to=/abc/ without the user having to change all its
// redirect plugin options
// It should be easy to toggle siteConfig.trailingSlash option without having to
// change other configs
function applyRedirectsTrailingSlash(
redirects: RedirectMetadata[],
params: ApplyTrailingSlashParams,
) {
return redirects.map((redirect) => ({
...redirect,
to: applyTrailingSlash(redirect.to, params),
}));
}

function validateCollectedRedirects(
redirects: RedirectMetadata[],
redirects: RedirectItem[],
pluginContext: PluginContext,
) {
const redirectValidationErrors = redirects
Expand Down Expand Up @@ -89,9 +94,9 @@ Valid paths you can redirect to:
}

function filterUnwantedRedirects(
redirects: RedirectMetadata[],
redirects: RedirectItem[],
pluginContext: PluginContext,
): RedirectMetadata[] {
): RedirectItem[] {
// We don't want to create the same redirect twice, since that would lead to
// writing the same html redirection file twice.
Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach(
Expand Down Expand Up @@ -120,37 +125,15 @@ It is not possible to redirect the same pathname to multiple destinations: ${gro
);
}

// For each plugin config option, create the appropriate redirects
function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
return [
...createFromExtensionsRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.fromExtensions,
),
...createToExtensionsRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.toExtensions,
),
...createRedirectsOptionRedirects(pluginContext.options.redirects),
...createCreateRedirectsOptionRedirects(
pluginContext.relativeRoutesPaths,
pluginContext.options.createRedirects,
),
];
}

function createRedirectsOptionRedirects(
redirectsOption: PluginOptions['redirects'],
): RedirectMetadata[] {
): RedirectItem[] {
// For convenience, user can use a string or a string[]
function optionToRedirects(option: RedirectOption): RedirectMetadata[] {
function optionToRedirects(option: RedirectOption): RedirectItem[] {
if (typeof option.from === 'string') {
return [{from: option.from, to: option.to}];
}
return option.from.map((from) => ({
from,
to: option.to,
}));
return option.from.map((from) => ({from, to: option.to}));
}

return redirectsOption.flatMap(optionToRedirects);
Expand All @@ -160,17 +143,14 @@ function createRedirectsOptionRedirects(
function createCreateRedirectsOptionRedirects(
paths: string[],
createRedirects: PluginOptions['createRedirects'],
): RedirectMetadata[] {
function createPathRedirects(path: string): RedirectMetadata[] {
): RedirectItem[] {
function createPathRedirects(path: string): RedirectItem[] {
const fromsMixed: string | string[] = createRedirects?.(path) ?? [];

const froms: string[] =
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;

return froms.map((from) => ({
from,
to: path,
}));
return froms.map((from) => ({from, to: path}));
}

return paths.flatMap(createPathRedirects);
Expand Down
Expand Up @@ -10,7 +10,7 @@ import {
removeSuffix,
removeTrailingSlash,
} from '@docusaurus/utils';
import type {RedirectMetadata} from './types';
import type {RedirectItem} from './types';

const ExtensionAdditionalMessage =
'If the redirect extension system is not good enough for your use case, you can create redirects yourself with the "createRedirects" plugin option.';
Expand Down Expand Up @@ -40,44 +40,45 @@ const validateExtension = (ext: string) => {

const addLeadingDot = (extension: string) => `.${extension}`;

// Create new /path that redirects to existing an /path.html
/**
* Create new `/path` that redirects to existing an `/path.html`
*/
export function createToExtensionsRedirects(
paths: string[],
extensions: string[],
): RedirectMetadata[] {
): RedirectItem[] {
extensions.forEach(validateExtension);

const dottedExtensions = extensions.map(addLeadingDot);

const createPathRedirects = (path: string): RedirectMetadata[] => {
const createPathRedirects = (path: string): RedirectItem[] => {
const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
if (extensionFound) {
const routePathWithoutExtension = removeSuffix(path, extensionFound);
return [routePathWithoutExtension].map((from) => ({
from,
to: path,
}));
return [{from: removeSuffix(path, extensionFound), to: path}];
}
return [];
};

return paths.flatMap(createPathRedirects);
}

// Create new /path.html/index.html that redirects to existing an /path
// The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
/**
* Create new `/path.html/index.html` that redirects to existing an `/path`
* The filename pattern might look weird but it's on purpose (see
* https://github.com/facebook/docusaurus/issues/5055)
*/
export function createFromExtensionsRedirects(
paths: string[],
extensions: string[],
): RedirectMetadata[] {
): RedirectItem[] {
extensions.forEach(validateExtension);

const dottedExtensions = extensions.map(addLeadingDot);

const alreadyEndsWithAnExtension = (str: string) =>
dottedExtensions.some((ext) => str.endsWith(ext));

const createPathRedirects = (path: string): RedirectMetadata[] => {
const createPathRedirects = (path: string): RedirectItem[] => {
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
return [];
}
Expand Down
10 changes: 5 additions & 5 deletions packages/docusaurus-plugin-client-redirects/src/index.ts
Expand Up @@ -8,11 +8,11 @@
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
import collectRedirects from './collectRedirects';
import writeRedirectFiles, {
toRedirectFilesMetadata,
type RedirectFileMetadata,
toRedirectFiles,
type RedirectFile,
} from './writeRedirectFiles';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {PluginContext, RedirectMetadata} from './types';
import type {PluginContext, RedirectItem} from './types';
import type {PluginOptions, Options} from './options';

export default function pluginClientRedirectsPages(
Expand All @@ -33,12 +33,12 @@ export default function pluginClientRedirectsPages(
options,
};

const redirects: RedirectMetadata[] = collectRedirects(
const redirects: RedirectItem[] = collectRedirects(
pluginContext,
trailingSlash,
);

const redirectFiles: RedirectFileMetadata[] = toRedirectFilesMetadata(
const redirectFiles: RedirectFile[] = toRedirectFiles(
redirects,
pluginContext,
trailingSlash,
Expand Down
6 changes: 5 additions & 1 deletion packages/docusaurus-plugin-client-redirects/src/options.ts
Expand Up @@ -9,7 +9,9 @@ import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
import type {OptionValidationContext} from '@docusaurus/types';

export type RedirectOption = {
/** Pathname of an existing Docusaurus page */
to: string;
/** Pathname of the new page(s) we should create */
from: string | string[];
};

Expand All @@ -23,7 +25,9 @@ export type PluginOptions = {
/** The list of redirect rules, each one with multiple `from`s → one `to`. */
redirects: RedirectOption[];
/**
* A callback to create a redirect rule.
* A callback to create a redirect rule. Docusaurus query this callback
* against every path it has created, and use its return value to output more
* paths.
* @returns All the paths from which we should redirect to `path`
*/
createRedirects?: (
Expand Down

0 comments on commit fb3138d

Please sign in to comment.