split collectRedirects and writeRedirectFiles in separate files

This commit is contained in:
slorber 2020-05-25 18:21:41 +02:00
parent 2695548b93
commit 9579ac10fc
4 changed files with 146 additions and 115 deletions

View File

@ -0,0 +1,89 @@
/**
* 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 {flatten} from 'lodash';
import {
RedirectsCreator,
PluginContext,
RedirectMetadata,
PluginOptions,
} from './types';
import createRedirectPageContent from './createRedirectPageContent';
import {addTrailingSlash, getFilePathForRoutePath} from './utils';
import {
fromExtensionsRedirectCreator,
toExtensionsRedirectCreator,
} from './redirectCreators';
export default function collectRedirects(
pluginContext: PluginContext,
): RedirectMetadata[] {
const redirectsCreators: RedirectsCreator[] = buildRedirectCreators(
pluginContext.options,
);
return flatten(
redirectsCreators.map((redirectCreator) => {
return createRoutesPathsRedirects(redirectCreator, pluginContext);
}),
);
}
function buildRedirectCreators(options: PluginOptions): RedirectsCreator[] {
const noopRedirectCreator: RedirectsCreator = (_routePath: string) => [];
return [
fromExtensionsRedirectCreator(options.fromExtensions),
toExtensionsRedirectCreator(options.toExtensions),
options.createRedirects ?? noopRedirectCreator,
];
}
// Create all redirects for a list of route path
function createRoutesPathsRedirects(
redirectCreator: RedirectsCreator,
pluginContext: PluginContext,
): RedirectMetadata[] {
return flatten(
pluginContext.routesPaths.map((routePath) =>
createRoutePathRedirects(routePath, redirectCreator, pluginContext),
),
);
}
// Create all redirects for a single route path
function createRoutePathRedirects(
routePath: string,
redirectCreator: RedirectsCreator,
{siteConfig, outDir}: PluginContext,
): RedirectMetadata[] {
// TODO do we receive absolute urls???
if (!path.isAbsolute(routePath)) {
return [];
}
// TODO addTrailingSlash ?
const toUrl = addTrailingSlash(`${siteConfig.url}${routePath}`);
const redirectPageContent = createRedirectPageContent({toUrl});
const fromRoutePaths: string[] = redirectCreator(routePath) ?? [];
return fromRoutePaths.map((fromRoutePath) => {
const redirectAbsoluteFilePath = path.join(
outDir,
getFilePathForRoutePath(fromRoutePath),
);
return {
fromRoutePath,
toRoutePath: routePath,
toUrl,
redirectPageContent,
redirectAbsoluteFilePath,
};
});
}

View File

@ -5,137 +5,33 @@
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs-extra';
import path from 'path';
import {flatten} from 'lodash';
import {LoadContext, Plugin, Props} from '@docusaurus/types';
import {UserPluginOptions, PluginContext, RedirectMetadata} from './types';
import {PluginOptions, UserPluginOptions, RedirectsCreator} from './types';
import createRedirectPageContent from './createRedirectPageContent';
import normalizePluginOptions from './normalizePluginOptions';
import {addTrailingSlash, getFilePathForRoutePath} from './utils';
import {
fromExtensionsRedirectCreator,
toExtensionsRedirectCreator,
} from './redirectCreators';
type RedirectMetadata = {
fromRoutePath: string;
toRoutePath: string;
toUrl: string;
redirectPageContent: string;
redirectAbsoluteFilePath: string;
};
type PluginContext = {
props: Props;
options: PluginOptions;
redirectsCreators: RedirectsCreator[];
};
import collectRedirects from './collectRedirects';
import writeRedirectFiles from './writeRedirectFiles';
export default function pluginClientRedirectsPages(
_context: LoadContext,
opts: UserPluginOptions,
): Plugin<unknown> {
const options = normalizePluginOptions(opts);
return {
name: 'docusaurus-plugin-client-redirects',
async postBuild(props: Props) {
const redirectsCreators: RedirectsCreator[] = buildRedirectCreators(
const pluginContext: PluginContext = {
routesPaths: props.routesPaths,
siteConfig: props.siteConfig,
outDir: props.outDir,
options,
);
};
const pluginContext: PluginContext = {props, options, redirectsCreators};
// Process in 2 steps, to make code more easy to test
const redirects: RedirectMetadata[] = collectRoutePathRedirects(
pluginContext,
);
console.log('redirects=', redirects);
const redirects: RedirectMetadata[] = collectRedirects(pluginContext);
// Write files only at the end: make code more easy to test without IO
await writeRedirectFiles(redirects);
},
};
}
function buildRedirectCreators(options: PluginOptions): RedirectsCreator[] {
const noopRedirectCreator: RedirectsCreator = (_routePath: string) => [];
return [
fromExtensionsRedirectCreator(options.fromExtensions),
toExtensionsRedirectCreator(options.toExtensions),
options.createRedirects ?? noopRedirectCreator,
];
}
function collectRoutePathRedirects(
pluginContext: PluginContext,
): RedirectMetadata[] {
return flatten(
pluginContext.redirectsCreators.map((redirectCreator) => {
return createRoutesPathsRedirects(redirectCreator, pluginContext);
}),
);
}
// Create all redirects for a list of route path
function createRoutesPathsRedirects(
redirectCreator: RedirectsCreator,
pluginContext: PluginContext,
): RedirectMetadata[] {
return flatten(
pluginContext.props.routesPaths.map((routePath) =>
createRoutePathRedirects(routePath, redirectCreator, pluginContext),
),
);
}
// Create all redirects for a single route path
function createRoutePathRedirects(
routePath: string,
redirectCreator: RedirectsCreator,
{props}: PluginContext,
): RedirectMetadata[] {
const {siteConfig, outDir} = props;
// TODO do we receive absolute urls???
if (!path.isAbsolute(routePath)) {
return [];
}
// TODO addTrailingSlash ?
const toUrl = addTrailingSlash(`${siteConfig.url}${routePath}`);
const redirectPageContent = createRedirectPageContent({toUrl});
const fromRoutePaths: string[] = redirectCreator(routePath) ?? [];
return fromRoutePaths.map((fromRoutePath) => {
const redirectAbsoluteFilePath = path.join(
outDir,
getFilePathForRoutePath(fromRoutePath),
);
return {
fromRoutePath,
toRoutePath: routePath,
toUrl,
redirectPageContent,
redirectAbsoluteFilePath,
};
});
}
async function writeRedirectFiles(redirects: RedirectMetadata[]) {
async function writeRedirectFile(redirect: RedirectMetadata) {
try {
await fs.writeFile(
redirect.redirectAbsoluteFilePath,
redirect.redirectPageContent,
);
} catch (err) {
throw new Error(`Redirect file creation error: ${err}`);
}
}
await Promise.all(redirects.map(writeRedirectFile));
}

View File

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import {Props} from '@docusaurus/types';
export type PluginOptions = {
fromExtensions: string[];
toExtensions: string[];
@ -18,3 +20,19 @@ export type UserPluginOptions = Partial<PluginOptions>;
export type RedirectsCreator = (
routePath: string,
) => string[] | null | undefined;
// Having an in-memory representation of wanted redirects is easier to test
export type RedirectMetadata = {
fromRoutePath: string;
toRoutePath: string;
toUrl: string;
redirectPageContent: string;
redirectAbsoluteFilePath: string;
};
export type PluginContext = Pick<
Props,
'routesPaths' | 'siteConfig' | 'outDir'
> & {
options: PluginOptions;
};

View File

@ -0,0 +1,28 @@
/**
* 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 fs from 'fs-extra';
import {RedirectMetadata} from './types';
type RedirectFile = Pick<
RedirectMetadata,
'redirectAbsoluteFilePath' | 'redirectPageContent'
>;
export default async function writeRedirectFiles(redirects: RedirectFile[]) {
async function writeRedirectFile(redirect: RedirectFile) {
try {
await fs.writeFile(
redirect.redirectAbsoluteFilePath,
redirect.redirectPageContent,
);
} catch (err) {
throw new Error(`Redirect file creation error: ${err}`);
}
}
await Promise.all(redirects.map(writeRedirectFile));
}