mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-14 16:52:39 +02:00
feat(v2): docusaurus-plugin-client-redirects
This commit is contained in:
parent
05c3aa31f4
commit
fd47ca1925
11 changed files with 300 additions and 0 deletions
|
@ -13,6 +13,7 @@ packages/docusaurus-1.x/lib/core/__tests__/split-tab.test.js
|
|||
packages/docusaurus-utils/lib/
|
||||
packages/docusaurus/lib/
|
||||
packages/docusaurus-init/lib/
|
||||
packages/docusaurus-plugin-client-redirects/lib/
|
||||
packages/docusaurus-plugin-content-blog/lib/
|
||||
packages/docusaurus-plugin-content-docs/lib/
|
||||
packages/docusaurus-plugin-content-pages/lib/
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,6 +17,7 @@ types
|
|||
packages/docusaurus-utils/lib/
|
||||
packages/docusaurus/lib/
|
||||
packages/docusaurus-init/lib/
|
||||
packages/docusaurus-plugin-client-redirects/lib/
|
||||
packages/docusaurus-plugin-content-blog/lib/
|
||||
packages/docusaurus-plugin-content-docs/lib/
|
||||
packages/docusaurus-plugin-content-pages/lib/
|
||||
|
|
26
packages/docusaurus-plugin-client-redirects/package.json
Normal file
26
packages/docusaurus-plugin-client-redirects/package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "@docusaurus/plugin-client-redirects",
|
||||
"version": "2.0.0-alpha.55",
|
||||
"description": "Client redirects plugin for Docusaurus",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@docusaurus/types": "^2.0.0-alpha.55",
|
||||
"@docusaurus/utils": "^2.0.0-alpha.55",
|
||||
"globby": "^10.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@docusaurus/core": "^2.0.0",
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
type CreateRedirectPageOptions = {
|
||||
toUrl: string;
|
||||
};
|
||||
|
||||
export default function createRedirectPageContent({
|
||||
toUrl,
|
||||
}: CreateRedirectPageOptions) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0; url=${toUrl}">
|
||||
<script>
|
||||
const redirectLink = '${toUrl}' + location.search + location.hash;
|
||||
document.write('<link rel="canonical" href="' + redirectLink + '">');
|
||||
document.write('<title>Redirecting to ' + redirectLink + '</title>');
|
||||
document.write('</head>')
|
||||
document.write('<body>')
|
||||
document.write('If you are not redirected automatically, follow this <a href="' + redirectLink + '">link</a>.')
|
||||
document.write('</body>')
|
||||
setTimeout(() => {
|
||||
window.location.assign(redirectLink)
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
`;
|
||||
}
|
144
packages/docusaurus-plugin-client-redirects/src/index.ts
Normal file
144
packages/docusaurus-plugin-client-redirects/src/index.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* 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 path from 'path';
|
||||
import {flatten} from 'lodash';
|
||||
|
||||
import {LoadContext, Plugin, Props} from '@docusaurus/types';
|
||||
|
||||
import {PluginOptions, RedirectsCreator} from './types';
|
||||
import createRedirectPageContent from './createRedirectPageContent';
|
||||
import {addTrailingSlash, getFilePathForRoutePath} from './utils';
|
||||
import {
|
||||
fromExtensionsRedirectCreator,
|
||||
toExtensionsRedirectCreator,
|
||||
} from './redirectCreators';
|
||||
|
||||
const DEFAULT_OPTIONS: PluginOptions = {
|
||||
fromExtensions: [],
|
||||
toExtensions: [],
|
||||
};
|
||||
|
||||
type RedirectMetadata = {
|
||||
fromRoutePath: string;
|
||||
toRoutePath: string;
|
||||
toUrl: string;
|
||||
redirectPageContent: string;
|
||||
redirectAbsoluteFilePath: string;
|
||||
};
|
||||
|
||||
type PluginContext = {
|
||||
props: Props;
|
||||
options: PluginOptions;
|
||||
redirectsCreators: RedirectsCreator[];
|
||||
};
|
||||
|
||||
export default function pluginClientRedirectsPages(
|
||||
_context: LoadContext,
|
||||
opts: Partial<PluginOptions>,
|
||||
): Plugin<unknown> {
|
||||
const options = {...DEFAULT_OPTIONS, ...opts};
|
||||
return {
|
||||
name: 'docusaurus-plugin-client-redirects',
|
||||
async postBuild(props: Props) {
|
||||
const redirectsCreators: RedirectsCreator[] = buildRedirectCreators(
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 {RedirectsCreator} from './types';
|
||||
import {removeTrailingSlash} from './utils';
|
||||
|
||||
export function fromExtensionsRedirectCreator(
|
||||
extensions: string[],
|
||||
): RedirectsCreator {
|
||||
const dottedExtensions = extensions.map((ext) => `.${ext}`);
|
||||
return (fromRoutePath: string) => {
|
||||
const extensionMatch = dottedExtensions.find((ext) =>
|
||||
fromRoutePath.endsWith(`.${ext}`),
|
||||
);
|
||||
if (extensionMatch) {
|
||||
const routePathWithoutExtension = fromRoutePath.substr(
|
||||
0,
|
||||
fromRoutePath.length - extensionMatch.length - 1,
|
||||
);
|
||||
return [routePathWithoutExtension];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
}
|
||||
|
||||
export function toExtensionsRedirectCreator(
|
||||
extensions: string[],
|
||||
): RedirectsCreator {
|
||||
return (fromRoutePath: string) => {
|
||||
if (fromRoutePath === '/') {
|
||||
return [];
|
||||
} else {
|
||||
const fromRoutePathNoSlash = removeTrailingSlash(fromRoutePath);
|
||||
return extensions.map((ext) => `${fromRoutePathNoSlash}.${ext}`);
|
||||
}
|
||||
};
|
||||
}
|
18
packages/docusaurus-plugin-client-redirects/src/types.ts
Normal file
18
packages/docusaurus-plugin-client-redirects/src/types.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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 type PluginOptions = {
|
||||
fromExtensions: string[];
|
||||
toExtensions: string[];
|
||||
createRedirects?: RedirectsCreator;
|
||||
};
|
||||
|
||||
// For a given existing route path,
|
||||
// return all the paths from which we should redirect from
|
||||
export type RedirectsCreator = (
|
||||
routePath: string,
|
||||
) => string[] | null | undefined;
|
23
packages/docusaurus-plugin-client-redirects/src/utils.ts
Normal file
23
packages/docusaurus-plugin-client-redirects/src/utils.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
export function addTrailingSlash(str: string) {
|
||||
return str.endsWith('/') ? '' : '/';
|
||||
}
|
||||
|
||||
export function removeTrailingSlash(str: string) {
|
||||
return str.endsWith('/') ? str.substr(0, str.length - 2) : str;
|
||||
}
|
||||
|
||||
// TODO does this function already exist?
|
||||
export function getFilePathForRoutePath(routePath: string) {
|
||||
const fileName = path.basename(routePath);
|
||||
const filePath = path.dirname(routePath);
|
||||
return path.join(filePath, `${fileName}.html`);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ module.exports = {
|
|||
},
|
||||
themes: ['@docusaurus/theme-live-codeblock'],
|
||||
plugins: [
|
||||
['@docusaurus/plugin-client-redirects', {}],
|
||||
[
|
||||
'@docusaurus/plugin-ideal-image',
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-alpha.55",
|
||||
"@docusaurus/plugin-ideal-image": "^2.0.0-alpha.55",
|
||||
"@docusaurus/plugin-client-redirects": "^2.0.0-alpha.55",
|
||||
"@docusaurus/preset-classic": "^2.0.0-alpha.55",
|
||||
"classnames": "^2.2.6",
|
||||
"color": "^3.1.2",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue