@docusaurus/plugin-client-redirects 2.0.0-beta.138b4c997 → 2.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/collectRedirects.js +25 -30
- package/lib/createRedirectPageContent.js +3 -5
- package/lib/extensionRedirects.js +17 -7
- package/lib/index.js +8 -16
- package/lib/normalizePluginOptions.js +2 -2
- package/lib/writeRedirectFiles.js +31 -9
- package/package.json +15 -12
- package/src/collectRedirects.ts +32 -36
- package/src/createRedirectPageContent.ts +3 -3
- package/src/extensionRedirects.ts +22 -7
- package/src/index.ts +0 -13
- package/src/normalizePluginOptions.ts +1 -1
- package/src/plugin-client-redirects.d.ts +8 -0
- package/src/writeRedirectFiles.ts +34 -6
- package/lib/.tsbuildinfo +0 -1
- package/src/__tests__/__snapshots__/collectRedirects.test.ts.snap +0 -27
- package/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap +0 -29
- package/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap +0 -35
- package/src/__tests__/__snapshots__/redirectValidation.test.ts.snap +0 -11
- package/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap +0 -147
- package/src/__tests__/collectRedirects.test.ts +0 -340
- package/src/__tests__/createRedirectPageContent.test.ts +0 -26
- package/src/__tests__/extensionRedirects.test.ts +0 -140
- package/src/__tests__/normalizePluginOptions.test.ts +0 -76
- package/src/__tests__/redirectValidation.test.ts +0 -66
- package/src/__tests__/writeRedirectFiles.test.ts +0 -203
- package/tsconfig.json +0 -10
package/lib/collectRedirects.js
CHANGED
|
@@ -11,10 +11,13 @@ const lodash_1 = require("lodash");
|
|
|
11
11
|
const extensionRedirects_1 = require("./extensionRedirects");
|
|
12
12
|
const redirectValidation_1 = require("./redirectValidation");
|
|
13
13
|
const utils_common_1 = require("@docusaurus/utils-common");
|
|
14
|
-
const
|
|
14
|
+
const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
|
|
15
15
|
function collectRedirects(pluginContext, trailingSlash) {
|
|
16
16
|
let redirects = doCollectRedirects(pluginContext);
|
|
17
|
-
redirects = applyRedirectsTrailingSlash(redirects,
|
|
17
|
+
redirects = applyRedirectsTrailingSlash(redirects, {
|
|
18
|
+
trailingSlash,
|
|
19
|
+
baseUrl: pluginContext.baseUrl,
|
|
20
|
+
});
|
|
18
21
|
validateCollectedRedirects(redirects, pluginContext);
|
|
19
22
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
20
23
|
}
|
|
@@ -23,19 +26,17 @@ exports.default = collectRedirects;
|
|
|
23
26
|
// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid path now)
|
|
24
27
|
// => we want to redirect to=/abc/ without the user having to change all its redirect plugin options
|
|
25
28
|
// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
|
|
26
|
-
function applyRedirectsTrailingSlash(redirects,
|
|
27
|
-
return redirects.map((redirect) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
});
|
|
29
|
+
function applyRedirectsTrailingSlash(redirects, params) {
|
|
30
|
+
return redirects.map((redirect) => ({
|
|
31
|
+
...redirect,
|
|
32
|
+
to: (0, utils_common_1.applyTrailingSlash)(redirect.to, params),
|
|
33
|
+
}));
|
|
33
34
|
}
|
|
34
35
|
function validateCollectedRedirects(redirects, pluginContext) {
|
|
35
36
|
const redirectValidationErrors = redirects
|
|
36
37
|
.map((redirect) => {
|
|
37
38
|
try {
|
|
38
|
-
redirectValidation_1.validateRedirect(redirect);
|
|
39
|
+
(0, redirectValidation_1.validateRedirect)(redirect);
|
|
39
40
|
return undefined;
|
|
40
41
|
}
|
|
41
42
|
catch (e) {
|
|
@@ -50,7 +51,7 @@ function validateCollectedRedirects(redirects, pluginContext) {
|
|
|
50
51
|
}
|
|
51
52
|
const allowedToPaths = pluginContext.relativeRoutesPaths;
|
|
52
53
|
const toPaths = redirects.map((redirect) => redirect.to);
|
|
53
|
-
const illegalToPaths = lodash_1.difference(toPaths, allowedToPaths);
|
|
54
|
+
const illegalToPaths = (0, lodash_1.difference)(toPaths, allowedToPaths);
|
|
54
55
|
if (illegalToPaths.length > 0) {
|
|
55
56
|
throw new Error(`You are trying to create client-side redirections to paths that do not exist:
|
|
56
57
|
- ${illegalToPaths.join('\n- ')}
|
|
@@ -63,29 +64,25 @@ Valid paths you can redirect to:
|
|
|
63
64
|
function filterUnwantedRedirects(redirects, pluginContext) {
|
|
64
65
|
// we don't want to create twice the same redirect
|
|
65
66
|
// that would lead to writing twice the same html redirection file
|
|
66
|
-
Object.entries(lodash_1.groupBy(redirects, (redirect) => redirect.from)).forEach(([from, groupedFromRedirects]) => {
|
|
67
|
+
Object.entries((0, lodash_1.groupBy)(redirects, (redirect) => redirect.from)).forEach(([from, groupedFromRedirects]) => {
|
|
67
68
|
if (groupedFromRedirects.length > 1) {
|
|
68
|
-
|
|
69
|
-
It is not possible to redirect the same pathname to multiple destinations:
|
|
70
|
-
- ${groupedFromRedirects.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
71
|
-
`));
|
|
69
|
+
logger_1.default.error `name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from}
|
|
70
|
+
It is not possible to redirect the same pathname to multiple destinations: ${groupedFromRedirects.map((r) => JSON.stringify(r))}`;
|
|
72
71
|
}
|
|
73
72
|
});
|
|
74
|
-
const collectedRedirects = lodash_1.uniqBy(redirects, (redirect) => redirect.from);
|
|
73
|
+
const collectedRedirects = (0, lodash_1.uniqBy)(redirects, (redirect) => redirect.from);
|
|
75
74
|
// We don't want to override an already existing route with a redirect file!
|
|
76
75
|
const redirectsOverridingExistingPath = collectedRedirects.filter((redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from));
|
|
77
76
|
if (redirectsOverridingExistingPath.length > 0) {
|
|
78
|
-
|
|
79
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
80
|
-
`));
|
|
77
|
+
logger_1.default.error `name=${'@docusaurus/plugin-client-redirects'}: some redirects would override existing paths, and will be ignored: ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r))}`;
|
|
81
78
|
}
|
|
82
79
|
return collectedRedirects.filter((redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from));
|
|
83
80
|
}
|
|
84
81
|
// For each plugin config option, create the appropriate redirects
|
|
85
82
|
function doCollectRedirects(pluginContext) {
|
|
86
83
|
return [
|
|
87
|
-
...extensionRedirects_1.createFromExtensionsRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions),
|
|
88
|
-
...extensionRedirects_1.createToExtensionsRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions),
|
|
84
|
+
...(0, extensionRedirects_1.createFromExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.fromExtensions),
|
|
85
|
+
...(0, extensionRedirects_1.createToExtensionsRedirects)(pluginContext.relativeRoutesPaths, pluginContext.options.toExtensions),
|
|
89
86
|
...createRedirectsOptionRedirects(pluginContext.options.redirects),
|
|
90
87
|
...createCreateRedirectsOptionRedirects(pluginContext.relativeRoutesPaths, pluginContext.options.createRedirects),
|
|
91
88
|
];
|
|
@@ -101,7 +98,7 @@ function createRedirectsOptionRedirects(redirectsOption) {
|
|
|
101
98
|
to: option.to,
|
|
102
99
|
}));
|
|
103
100
|
}
|
|
104
|
-
return
|
|
101
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
105
102
|
}
|
|
106
103
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
107
104
|
function createCreateRedirectsOptionRedirects(paths, createRedirects) {
|
|
@@ -110,12 +107,10 @@ function createCreateRedirectsOptionRedirects(paths, createRedirects) {
|
|
|
110
107
|
? createRedirects(path) || []
|
|
111
108
|
: [];
|
|
112
109
|
const froms = typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
113
|
-
return froms.map((from) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
118
|
-
});
|
|
110
|
+
return froms.map((from) => ({
|
|
111
|
+
from,
|
|
112
|
+
to: path,
|
|
113
|
+
}));
|
|
119
114
|
}
|
|
120
|
-
return
|
|
115
|
+
return paths.flatMap(createPathRedirects);
|
|
121
116
|
}
|
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const tslib_1 = require("tslib");
|
|
10
|
-
const eta = tslib_1.__importStar(require("eta"));
|
|
11
|
-
const redirectPage_template_html_1 = tslib_1.__importDefault(require("./templates/redirectPage.template.html"));
|
|
10
|
+
const eta = (0, tslib_1.__importStar)(require("eta"));
|
|
11
|
+
const redirectPage_template_html_1 = (0, tslib_1.__importDefault)(require("./templates/redirectPage.template.html"));
|
|
12
12
|
const lodash_1 = require("lodash");
|
|
13
|
-
const getCompiledRedirectPageTemplate = lodash_1.memoize(() =>
|
|
14
|
-
return eta.compile(redirectPage_template_html_1.default.trim());
|
|
15
|
-
});
|
|
13
|
+
const getCompiledRedirectPageTemplate = (0, lodash_1.memoize)(() => eta.compile(redirectPage_template_html_1.default.trim()));
|
|
16
14
|
function renderRedirectPageTemplate(data) {
|
|
17
15
|
const compiled = getCompiledRedirectPageTemplate();
|
|
18
16
|
return compiled(data, eta.defaultConfig);
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createFromExtensionsRedirects = exports.createToExtensionsRedirects = void 0;
|
|
10
|
-
const lodash_1 = require("lodash");
|
|
11
10
|
const utils_1 = require("@docusaurus/utils");
|
|
12
11
|
const ExtensionAdditionalMessage = 'If the redirect extension system is not good enough for your usecase, you can create redirects yourself with the "createRedirects" plugin option.';
|
|
13
12
|
const validateExtension = (ext) => {
|
|
@@ -32,7 +31,7 @@ function createToExtensionsRedirects(paths, extensions) {
|
|
|
32
31
|
const createPathRedirects = (path) => {
|
|
33
32
|
const extensionFound = dottedExtensions.find((ext) => path.endsWith(ext));
|
|
34
33
|
if (extensionFound) {
|
|
35
|
-
const routePathWithoutExtension = utils_1.removeSuffix(path, extensionFound);
|
|
34
|
+
const routePathWithoutExtension = (0, utils_1.removeSuffix)(path, extensionFound);
|
|
36
35
|
return [routePathWithoutExtension].map((from) => ({
|
|
37
36
|
from,
|
|
38
37
|
to: path,
|
|
@@ -40,23 +39,34 @@ function createToExtensionsRedirects(paths, extensions) {
|
|
|
40
39
|
}
|
|
41
40
|
return [];
|
|
42
41
|
};
|
|
43
|
-
return
|
|
42
|
+
return paths.flatMap(createPathRedirects);
|
|
44
43
|
}
|
|
45
44
|
exports.createToExtensionsRedirects = createToExtensionsRedirects;
|
|
46
|
-
// Create new /path.html that redirects to existing an /path
|
|
45
|
+
// Create new /path.html/index.html that redirects to existing an /path
|
|
46
|
+
// The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
|
|
47
47
|
function createFromExtensionsRedirects(paths, extensions) {
|
|
48
48
|
extensions.forEach(validateExtension);
|
|
49
49
|
const dottedExtensions = extensions.map(addLeadingDot);
|
|
50
50
|
const alreadyEndsWithAnExtension = (str) => dottedExtensions.some((ext) => str.endsWith(ext));
|
|
51
51
|
const createPathRedirects = (path) => {
|
|
52
|
-
if (path === '' || path
|
|
52
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
53
53
|
return [];
|
|
54
54
|
}
|
|
55
|
+
// /path => /path.html
|
|
56
|
+
// /path/ => /path.html/
|
|
57
|
+
function getFrom(ext) {
|
|
58
|
+
if (path.endsWith('/')) {
|
|
59
|
+
return (0, utils_1.addTrailingSlash)(`${(0, utils_1.removeTrailingSlash)(path)}.${ext}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return `${path}.${ext}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
55
65
|
return extensions.map((ext) => ({
|
|
56
|
-
from:
|
|
66
|
+
from: getFrom(ext),
|
|
57
67
|
to: path,
|
|
58
68
|
}));
|
|
59
69
|
};
|
|
60
|
-
return
|
|
70
|
+
return paths.flatMap(createPathRedirects);
|
|
61
71
|
}
|
|
62
72
|
exports.createFromExtensionsRedirects = createFromExtensionsRedirects;
|
package/lib/index.js
CHANGED
|
@@ -7,34 +7,26 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const tslib_1 = require("tslib");
|
|
10
|
-
const normalizePluginOptions_1 = tslib_1.__importDefault(require("./normalizePluginOptions"));
|
|
11
|
-
const collectRedirects_1 = tslib_1.__importDefault(require("./collectRedirects"));
|
|
12
|
-
const writeRedirectFiles_1 = tslib_1.__importStar(require("./writeRedirectFiles"));
|
|
10
|
+
const normalizePluginOptions_1 = (0, tslib_1.__importDefault)(require("./normalizePluginOptions"));
|
|
11
|
+
const collectRedirects_1 = (0, tslib_1.__importDefault)(require("./collectRedirects"));
|
|
12
|
+
const writeRedirectFiles_1 = (0, tslib_1.__importStar)(require("./writeRedirectFiles"));
|
|
13
13
|
const utils_1 = require("@docusaurus/utils");
|
|
14
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
15
14
|
function pluginClientRedirectsPages(context, opts) {
|
|
16
15
|
const { trailingSlash } = context.siteConfig;
|
|
17
|
-
const options = normalizePluginOptions_1.default(opts);
|
|
18
|
-
// Special case, when using trailingSlash=false we output /xyz.html files instead of /xyz/index.html
|
|
19
|
-
// It makes no sense to use option fromExtensions=["html"]: the redirect files would be the same as the original files
|
|
20
|
-
if (options.fromExtensions.includes('html') && trailingSlash === false) {
|
|
21
|
-
console.warn(chalk_1.default.yellow(`Using the Docusaurus redirect plugin with fromExtensions=['html'] and siteConfig.trailingSlash=false is prevented.
|
|
22
|
-
It would lead the redirect plugin to override all the page.html files created by Docusaurus.`));
|
|
23
|
-
options.fromExtensions = options.fromExtensions.filter((ext) => ext !== 'html');
|
|
24
|
-
}
|
|
16
|
+
const options = (0, normalizePluginOptions_1.default)(opts);
|
|
25
17
|
return {
|
|
26
18
|
name: 'docusaurus-plugin-client-redirects',
|
|
27
19
|
async postBuild(props) {
|
|
28
20
|
const pluginContext = {
|
|
29
|
-
relativeRoutesPaths: props.routesPaths.map((path) => `${utils_1.addLeadingSlash(utils_1.removePrefix(path, props.baseUrl))}`),
|
|
21
|
+
relativeRoutesPaths: props.routesPaths.map((path) => `${(0, utils_1.addLeadingSlash)((0, utils_1.removePrefix)(path, props.baseUrl))}`),
|
|
30
22
|
baseUrl: props.baseUrl,
|
|
31
23
|
outDir: props.outDir,
|
|
32
24
|
options,
|
|
33
25
|
};
|
|
34
|
-
const redirects = collectRedirects_1.default(pluginContext, trailingSlash);
|
|
35
|
-
const redirectFiles = writeRedirectFiles_1.toRedirectFilesMetadata(redirects, pluginContext, trailingSlash);
|
|
26
|
+
const redirects = (0, collectRedirects_1.default)(pluginContext, trailingSlash);
|
|
27
|
+
const redirectFiles = (0, writeRedirectFiles_1.toRedirectFilesMetadata)(redirects, pluginContext, trailingSlash);
|
|
36
28
|
// Write files only at the end: make code more easy to test without IO
|
|
37
|
-
await writeRedirectFiles_1.default(redirectFiles);
|
|
29
|
+
await (0, writeRedirectFiles_1.default)(redirectFiles);
|
|
38
30
|
},
|
|
39
31
|
};
|
|
40
32
|
}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.DefaultPluginOptions = void 0;
|
|
10
10
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
11
|
-
const
|
|
11
|
+
const utils_1 = require("@docusaurus/utils");
|
|
12
12
|
exports.DefaultPluginOptions = {
|
|
13
|
-
id:
|
|
13
|
+
id: utils_1.DEFAULT_PLUGIN_ID,
|
|
14
14
|
fromExtensions: [],
|
|
15
15
|
toExtensions: [],
|
|
16
16
|
redirects: [],
|
|
@@ -8,25 +8,47 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.writeRedirectFile = exports.toRedirectFilesMetadata = exports.createToUrl = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
|
-
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
|
+
const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
|
|
12
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
13
13
|
const lodash_1 = require("lodash");
|
|
14
|
-
const createRedirectPageContent_1 = tslib_1.__importDefault(require("./createRedirectPageContent"));
|
|
14
|
+
const createRedirectPageContent_1 = (0, tslib_1.__importDefault)(require("./createRedirectPageContent"));
|
|
15
15
|
const utils_1 = require("@docusaurus/utils");
|
|
16
16
|
function createToUrl(baseUrl, to) {
|
|
17
|
-
return utils_1.normalizeUrl([baseUrl, to]);
|
|
17
|
+
return (0, utils_1.normalizeUrl)([baseUrl, to]);
|
|
18
18
|
}
|
|
19
19
|
exports.createToUrl = createToUrl;
|
|
20
|
+
// Create redirect file path
|
|
21
|
+
// Make sure this path has lower precedence over the original file path when served by host providers!
|
|
22
|
+
// Otherwise it can produce infinite redirect loops!
|
|
23
|
+
//
|
|
24
|
+
// See https://github.com/facebook/docusaurus/issues/5055
|
|
25
|
+
// See https://github.com/facebook/docusaurus/pull/5085
|
|
26
|
+
// See https://github.com/facebook/docusaurus/pull/5102
|
|
27
|
+
function getRedirectFilePath(fromPath, trailingSlash) {
|
|
28
|
+
const fileName = path_1.default.basename(fromPath);
|
|
29
|
+
const filePath = path_1.default.dirname(fromPath);
|
|
30
|
+
// Edge case for https://github.com/facebook/docusaurus/pull/5102
|
|
31
|
+
// If the redirect source path is /xyz, with file /xyz.html
|
|
32
|
+
// We can't write the redirect file at /xyz.html/index.html because for Unix FS, a file/folder can't have the same name "xyz.html"
|
|
33
|
+
// The only possible solution for a redirect file is thus /xyz.html.html (I know, looks suspicious)
|
|
34
|
+
if (trailingSlash === false && fileName.endsWith('.html')) {
|
|
35
|
+
return path_1.default.join(filePath, `${fileName}.html`);
|
|
36
|
+
}
|
|
37
|
+
// If the target path is /xyz, with file /xyz/index.html, we don't want the redirect file to be /xyz.html
|
|
38
|
+
// otherwise it would be picked in priority and the redirect file would redirect to itself
|
|
39
|
+
// We prefer the redirect file to be /xyz.html/index.html, served with lower priority for most static hosting tools
|
|
40
|
+
else {
|
|
41
|
+
return path_1.default.join(filePath, `${fileName}/index.html`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
20
44
|
function toRedirectFilesMetadata(redirects, pluginContext, trailingSlash) {
|
|
21
45
|
// Perf: avoid rendering the template twice with the exact same "props"
|
|
22
46
|
// We might create multiple redirect pages for the same destination url
|
|
23
47
|
// note: the first fn arg is the cache key!
|
|
24
|
-
const createPageContentMemoized = lodash_1.memoize((toUrl) => {
|
|
25
|
-
return createRedirectPageContent_1.default({ toUrl });
|
|
26
|
-
});
|
|
48
|
+
const createPageContentMemoized = (0, lodash_1.memoize)((toUrl) => (0, createRedirectPageContent_1.default)({ toUrl }));
|
|
27
49
|
const createFileMetadata = (redirect) => {
|
|
28
|
-
const
|
|
29
|
-
const fileAbsolutePath = path_1.default.join(pluginContext.outDir,
|
|
50
|
+
const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
|
|
51
|
+
const fileAbsolutePath = path_1.default.join(pluginContext.outDir, fileRelativePath);
|
|
30
52
|
const toUrl = createToUrl(pluginContext.baseUrl, redirect.to);
|
|
31
53
|
const fileContent = createPageContentMemoized(toUrl);
|
|
32
54
|
return {
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-client-redirects",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.14",
|
|
4
4
|
"description": "Client redirects plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"types": "src/plugin-client-redirects.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc",
|
|
8
9
|
"watch": "tsc --watch"
|
|
@@ -17,24 +18,26 @@
|
|
|
17
18
|
},
|
|
18
19
|
"license": "MIT",
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@docusaurus/core": "2.0.0-beta.
|
|
21
|
-
"@docusaurus/
|
|
22
|
-
"@docusaurus/utils": "2.0.0-beta.
|
|
23
|
-
"@docusaurus/utils-common": "2.0.0-beta.
|
|
24
|
-
"@docusaurus/utils-validation": "2.0.0-beta.
|
|
25
|
-
"chalk": "^4.1.
|
|
26
|
-
"eta": "^1.
|
|
21
|
+
"@docusaurus/core": "2.0.0-beta.14",
|
|
22
|
+
"@docusaurus/logger": "2.0.0-beta.14",
|
|
23
|
+
"@docusaurus/utils": "2.0.0-beta.14",
|
|
24
|
+
"@docusaurus/utils-common": "2.0.0-beta.14",
|
|
25
|
+
"@docusaurus/utils-validation": "2.0.0-beta.14",
|
|
26
|
+
"chalk": "^4.1.2",
|
|
27
|
+
"eta": "^1.12.3",
|
|
27
28
|
"fs-extra": "^10.0.0",
|
|
28
|
-
"globby": "^11.0.2",
|
|
29
29
|
"lodash": "^4.17.20",
|
|
30
|
-
"tslib": "^2.
|
|
30
|
+
"tslib": "^2.3.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@docusaurus/types": "2.0.0-beta.14"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"react": "^16.8.4 || ^17.0.0",
|
|
34
37
|
"react-dom": "^16.8.4 || ^17.0.0"
|
|
35
38
|
},
|
|
36
39
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
40
|
+
"node": ">=14"
|
|
38
41
|
},
|
|
39
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "c4824a8937d8f1aa0806667749cbc74058e2b294"
|
|
40
43
|
}
|
package/src/collectRedirects.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {uniqBy, difference, groupBy} from 'lodash';
|
|
9
9
|
import {
|
|
10
10
|
PluginContext,
|
|
11
11
|
RedirectMetadata,
|
|
@@ -17,16 +17,24 @@ import {
|
|
|
17
17
|
createToExtensionsRedirects,
|
|
18
18
|
} from './extensionRedirects';
|
|
19
19
|
import {validateRedirect} from './redirectValidation';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
applyTrailingSlash,
|
|
22
|
+
ApplyTrailingSlashParams,
|
|
23
|
+
} from '@docusaurus/utils-common';
|
|
21
24
|
|
|
22
|
-
import
|
|
25
|
+
import logger from '@docusaurus/logger';
|
|
23
26
|
|
|
24
27
|
export default function collectRedirects(
|
|
25
28
|
pluginContext: PluginContext,
|
|
26
29
|
trailingSlash: boolean | undefined,
|
|
27
30
|
): RedirectMetadata[] {
|
|
28
31
|
let redirects = doCollectRedirects(pluginContext);
|
|
29
|
-
|
|
32
|
+
|
|
33
|
+
redirects = applyRedirectsTrailingSlash(redirects, {
|
|
34
|
+
trailingSlash,
|
|
35
|
+
baseUrl: pluginContext.baseUrl,
|
|
36
|
+
});
|
|
37
|
+
|
|
30
38
|
validateCollectedRedirects(redirects, pluginContext);
|
|
31
39
|
return filterUnwantedRedirects(redirects, pluginContext);
|
|
32
40
|
}
|
|
@@ -37,27 +45,25 @@ export default function collectRedirects(
|
|
|
37
45
|
// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs
|
|
38
46
|
function applyRedirectsTrailingSlash(
|
|
39
47
|
redirects: RedirectMetadata[],
|
|
40
|
-
|
|
48
|
+
params: ApplyTrailingSlashParams,
|
|
41
49
|
) {
|
|
42
|
-
return redirects.map((redirect) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
});
|
|
50
|
+
return redirects.map((redirect) => ({
|
|
51
|
+
...redirect,
|
|
52
|
+
to: applyTrailingSlash(redirect.to, params),
|
|
53
|
+
}));
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
function validateCollectedRedirects(
|
|
51
57
|
redirects: RedirectMetadata[],
|
|
52
58
|
pluginContext: PluginContext,
|
|
53
59
|
) {
|
|
54
|
-
const redirectValidationErrors
|
|
60
|
+
const redirectValidationErrors = redirects
|
|
55
61
|
.map((redirect) => {
|
|
56
62
|
try {
|
|
57
63
|
validateRedirect(redirect);
|
|
58
64
|
return undefined;
|
|
59
65
|
} catch (e) {
|
|
60
|
-
return e.message;
|
|
66
|
+
return (e as Error).message;
|
|
61
67
|
}
|
|
62
68
|
})
|
|
63
69
|
.filter(Boolean);
|
|
@@ -93,14 +99,10 @@ function filterUnwantedRedirects(
|
|
|
93
99
|
Object.entries(groupBy(redirects, (redirect) => redirect.from)).forEach(
|
|
94
100
|
([from, groupedFromRedirects]) => {
|
|
95
101
|
if (groupedFromRedirects.length > 1) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- ${groupedFromRedirects.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
101
|
-
`,
|
|
102
|
-
),
|
|
103
|
-
);
|
|
102
|
+
logger.error`name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from}
|
|
103
|
+
It is not possible to redirect the same pathname to multiple destinations: ${groupedFromRedirects.map(
|
|
104
|
+
(r) => JSON.stringify(r),
|
|
105
|
+
)}`;
|
|
104
106
|
}
|
|
105
107
|
},
|
|
106
108
|
);
|
|
@@ -111,13 +113,9 @@ It is not possible to redirect the same pathname to multiple destinations:
|
|
|
111
113
|
(redirect) => pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
112
114
|
);
|
|
113
115
|
if (redirectsOverridingExistingPath.length > 0) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- ${redirectsOverridingExistingPath.map((r) => JSON.stringify(r)).join('\n- ')}
|
|
118
|
-
`,
|
|
119
|
-
),
|
|
120
|
-
);
|
|
116
|
+
logger.error`name=${'@docusaurus/plugin-client-redirects'}: some redirects would override existing paths, and will be ignored: ${redirectsOverridingExistingPath.map(
|
|
117
|
+
(r) => JSON.stringify(r),
|
|
118
|
+
)}`;
|
|
121
119
|
}
|
|
122
120
|
return collectedRedirects.filter(
|
|
123
121
|
(redirect) => !pluginContext.relativeRoutesPaths.includes(redirect.from),
|
|
@@ -157,7 +155,7 @@ function createRedirectsOptionRedirects(
|
|
|
157
155
|
}));
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
return
|
|
158
|
+
return redirectsOption.flatMap(optionToRedirects);
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
// Create redirects from the "createRedirects" fn provided by the user
|
|
@@ -173,13 +171,11 @@ function createCreateRedirectsOptionRedirects(
|
|
|
173
171
|
const froms: string[] =
|
|
174
172
|
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
|
175
173
|
|
|
176
|
-
return froms.map((from) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
};
|
|
181
|
-
});
|
|
174
|
+
return froms.map((from) => ({
|
|
175
|
+
from,
|
|
176
|
+
to: path,
|
|
177
|
+
}));
|
|
182
178
|
}
|
|
183
179
|
|
|
184
|
-
return
|
|
180
|
+
return paths.flatMap(createPathRedirects);
|
|
185
181
|
}
|
|
@@ -13,9 +13,9 @@ type CreateRedirectPageOptions = {
|
|
|
13
13
|
toUrl: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const getCompiledRedirectPageTemplate = memoize(() =>
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const getCompiledRedirectPageTemplate = memoize(() =>
|
|
17
|
+
eta.compile(redirectPageTemplate.trim()),
|
|
18
|
+
);
|
|
19
19
|
|
|
20
20
|
function renderRedirectPageTemplate(data: Record<string, unknown>) {
|
|
21
21
|
const compiled = getCompiledRedirectPageTemplate();
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
addTrailingSlash,
|
|
10
|
+
removeSuffix,
|
|
11
|
+
removeTrailingSlash,
|
|
12
|
+
} from '@docusaurus/utils';
|
|
10
13
|
import {RedirectMetadata} from './types';
|
|
11
14
|
|
|
12
15
|
const ExtensionAdditionalMessage =
|
|
@@ -58,10 +61,11 @@ export function createToExtensionsRedirects(
|
|
|
58
61
|
return [];
|
|
59
62
|
};
|
|
60
63
|
|
|
61
|
-
return
|
|
64
|
+
return paths.flatMap(createPathRedirects);
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
// Create new /path.html that redirects to existing an /path
|
|
67
|
+
// Create new /path.html/index.html that redirects to existing an /path
|
|
68
|
+
// The filename pattern might look weird but it's on purpose (see https://github.com/facebook/docusaurus/issues/5055)
|
|
65
69
|
export function createFromExtensionsRedirects(
|
|
66
70
|
paths: string[],
|
|
67
71
|
extensions: string[],
|
|
@@ -74,14 +78,25 @@ export function createFromExtensionsRedirects(
|
|
|
74
78
|
dottedExtensions.some((ext) => str.endsWith(ext));
|
|
75
79
|
|
|
76
80
|
const createPathRedirects = (path: string): RedirectMetadata[] => {
|
|
77
|
-
if (path === '' || path
|
|
81
|
+
if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) {
|
|
78
82
|
return [];
|
|
79
83
|
}
|
|
84
|
+
|
|
85
|
+
// /path => /path.html
|
|
86
|
+
// /path/ => /path.html/
|
|
87
|
+
function getFrom(ext: string) {
|
|
88
|
+
if (path.endsWith('/')) {
|
|
89
|
+
return addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`);
|
|
90
|
+
} else {
|
|
91
|
+
return `${path}.${ext}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
return extensions.map((ext) => ({
|
|
81
|
-
from:
|
|
96
|
+
from: getFrom(ext),
|
|
82
97
|
to: path,
|
|
83
98
|
}));
|
|
84
99
|
};
|
|
85
100
|
|
|
86
|
-
return
|
|
101
|
+
return paths.flatMap(createPathRedirects);
|
|
87
102
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import writeRedirectFiles, {
|
|
|
15
15
|
RedirectFileMetadata,
|
|
16
16
|
} from './writeRedirectFiles';
|
|
17
17
|
import {removePrefix, addLeadingSlash} from '@docusaurus/utils';
|
|
18
|
-
import chalk from 'chalk';
|
|
19
18
|
|
|
20
19
|
export default function pluginClientRedirectsPages(
|
|
21
20
|
context: LoadContext,
|
|
@@ -25,18 +24,6 @@ export default function pluginClientRedirectsPages(
|
|
|
25
24
|
|
|
26
25
|
const options = normalizePluginOptions(opts);
|
|
27
26
|
|
|
28
|
-
// Special case, when using trailingSlash=false we output /xyz.html files instead of /xyz/index.html
|
|
29
|
-
// It makes no sense to use option fromExtensions=["html"]: the redirect files would be the same as the original files
|
|
30
|
-
if (options.fromExtensions.includes('html') && trailingSlash === false) {
|
|
31
|
-
console.warn(
|
|
32
|
-
chalk.yellow(`Using the Docusaurus redirect plugin with fromExtensions=['html'] and siteConfig.trailingSlash=false is prevented.
|
|
33
|
-
It would lead the redirect plugin to override all the page.html files created by Docusaurus.`),
|
|
34
|
-
);
|
|
35
|
-
options.fromExtensions = options.fromExtensions.filter(
|
|
36
|
-
(ext) => ext !== 'html',
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
27
|
return {
|
|
41
28
|
name: 'docusaurus-plugin-client-redirects',
|
|
42
29
|
async postBuild(props: Props) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {PluginOptions, RedirectOption, UserPluginOptions} from './types';
|
|
9
9
|
import {Joi, PathnameSchema} from '@docusaurus/utils-validation';
|
|
10
|
-
import {DEFAULT_PLUGIN_ID} from '@docusaurus/
|
|
10
|
+
import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
|
|
11
11
|
|
|
12
12
|
export const DefaultPluginOptions: PluginOptions = {
|
|
13
13
|
id: DEFAULT_PLUGIN_ID, // TODO temporary
|