@docusaurus/core 3.3.2 → 3.4.0
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/client/clientEntry.js +7 -3
- package/lib/client/exports/Link.js +12 -2
- package/lib/client/exports/useBaseUrl.d.ts +8 -0
- package/lib/client/exports/useBaseUrl.js +11 -3
- package/lib/commands/build.js +35 -21
- package/lib/commands/serve.js +15 -4
- package/lib/commands/start/utils.d.ts +3 -2
- package/lib/commands/start/utils.js +7 -2
- package/lib/commands/start/webpack.js +3 -3
- package/lib/server/brokenLinks.js +9 -1
- package/lib/server/codegen/codegen.d.ts +2 -1
- package/lib/server/codegen/codegen.js +4 -0
- package/lib/server/codegen/codegenRoutes.d.ts +1 -0
- package/lib/server/codegen/codegenRoutes.js +20 -6
- package/lib/server/configValidation.d.ts +4 -1
- package/lib/server/configValidation.js +29 -1
- package/lib/server/htmlTags.d.ts +5 -2
- package/lib/server/htmlTags.js +15 -6
- package/lib/server/i18n.js +37 -8
- package/lib/server/site.js +13 -5
- package/lib/server/storage.d.ts +13 -0
- package/lib/server/storage.js +36 -0
- package/lib/ssg.d.ts +4 -0
- package/lib/ssg.js +9 -1
- package/lib/templates/templates.d.ts +3 -0
- package/lib/templates/templates.js +26 -1
- package/lib/webpack/base.js +1 -1
- package/lib/webpack/client.js +10 -18
- package/lib/webpack/configure.d.ts +25 -0
- package/lib/webpack/configure.js +100 -0
- package/lib/webpack/plugins/ForceTerminatePlugin.d.ts +10 -0
- package/lib/webpack/plugins/ForceTerminatePlugin.js +25 -0
- package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.d.ts +11 -0
- package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.js +39 -0
- package/lib/webpack/server.js +0 -28
- package/lib/webpack/utils.d.ts +0 -22
- package/lib/webpack/utils.js +1 -80
- package/package.json +10 -10
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import React, { startTransition } from 'react';
|
|
8
8
|
import ReactDOM from 'react-dom/client';
|
|
9
|
-
import { BrowserRouter } from 'react-router-dom';
|
|
10
9
|
import { HelmetProvider } from 'react-helmet-async';
|
|
10
|
+
import { BrowserRouter, HashRouter } from 'react-router-dom';
|
|
11
|
+
import siteConfig from '@generated/docusaurus.config';
|
|
11
12
|
import ExecutionEnvironment from './exports/ExecutionEnvironment';
|
|
12
13
|
import App from './App';
|
|
13
14
|
import preload from './preload';
|
|
14
15
|
import docusaurus from './docusaurus';
|
|
16
|
+
function Router({ children }) {
|
|
17
|
+
return siteConfig.future.experimental_router === 'hash' ? (<HashRouter>{children}</HashRouter>) : (<BrowserRouter>{children}</BrowserRouter>);
|
|
18
|
+
}
|
|
15
19
|
const hydrate = Boolean(process.env.HYDRATE_CLIENT_ENTRY);
|
|
16
20
|
// Client-side render (e.g: running in browser) to become single-page
|
|
17
21
|
// application (SPA).
|
|
@@ -19,9 +23,9 @@ if (ExecutionEnvironment.canUseDOM) {
|
|
|
19
23
|
window.docusaurus = docusaurus;
|
|
20
24
|
const container = document.getElementById('__docusaurus');
|
|
21
25
|
const app = (<HelmetProvider>
|
|
22
|
-
<
|
|
26
|
+
<Router>
|
|
23
27
|
<App />
|
|
24
|
-
</
|
|
28
|
+
</Router>
|
|
25
29
|
</HelmetProvider>);
|
|
26
30
|
const onRecoverableError = (error, errorInfo) => {
|
|
27
31
|
console.error('Docusaurus React Root onRecoverableError:', error, errorInfo);
|
|
@@ -19,7 +19,9 @@ import { useBaseUrlUtils } from './useBaseUrl';
|
|
|
19
19
|
// like "introduction" to "/baseUrl/introduction" => bad behavior to fix
|
|
20
20
|
const shouldAddBaseUrlAutomatically = (to) => to.startsWith('/');
|
|
21
21
|
function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLinkCheck': noBrokenLinkCheck, autoAddBaseUrl = true, ...props }, forwardedRef) {
|
|
22
|
-
const { siteConfig
|
|
22
|
+
const { siteConfig } = useDocusaurusContext();
|
|
23
|
+
const { trailingSlash, baseUrl } = siteConfig;
|
|
24
|
+
const router = siteConfig.future.experimental_router;
|
|
23
25
|
const { withBaseUrl } = useBaseUrlUtils();
|
|
24
26
|
const brokenLinks = useBrokenLinks();
|
|
25
27
|
const innerRef = useRef(null);
|
|
@@ -47,6 +49,14 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
47
49
|
let targetLink = typeof targetLinkWithoutPathnameProtocol !== 'undefined'
|
|
48
50
|
? maybeAddBaseUrl(targetLinkWithoutPathnameProtocol)
|
|
49
51
|
: undefined;
|
|
52
|
+
// TODO find a way to solve this problem properly
|
|
53
|
+
// Fix edge case when useBaseUrl is used on a link
|
|
54
|
+
// "./" is useful for images and other resources
|
|
55
|
+
// But we don't need it for <Link>
|
|
56
|
+
// unfortunately we can't really make the difference :/
|
|
57
|
+
if (router === 'hash' && targetLink?.startsWith('./')) {
|
|
58
|
+
targetLink = targetLink?.slice(1);
|
|
59
|
+
}
|
|
50
60
|
if (targetLink && isInternal) {
|
|
51
61
|
targetLink = applyTrailingSlash(targetLink, { trailingSlash, baseUrl });
|
|
52
62
|
}
|
|
@@ -103,7 +113,7 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
103
113
|
// https://github.com/remix-run/react-router/blob/v5/packages/react-router-dom/modules/Link.js#L47
|
|
104
114
|
const hasInternalTarget = !props.target || props.target === '_self';
|
|
105
115
|
// Should we use a regular <a> tag instead of React-Router Link component?
|
|
106
|
-
const isRegularHtmlLink = !targetLink || !isInternal || !hasInternalTarget
|
|
116
|
+
const isRegularHtmlLink = !targetLink || !isInternal || !hasInternalTarget;
|
|
107
117
|
if (!noBrokenLinkCheck && (isAnchorLink || !isRegularHtmlLink)) {
|
|
108
118
|
brokenLinks.collectLink(targetLink);
|
|
109
119
|
}
|
|
@@ -6,5 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/// <reference types="@docusaurus/module-type-aliases" />
|
|
8
8
|
import type { BaseUrlOptions, BaseUrlUtils } from '@docusaurus/useBaseUrl';
|
|
9
|
+
import type { RouterType } from '@docusaurus/types';
|
|
10
|
+
export declare function addBaseUrl({ siteUrl, baseUrl, url, options: { forcePrependBaseUrl, absolute }, router, }: {
|
|
11
|
+
siteUrl: string;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
url: string;
|
|
14
|
+
router: RouterType;
|
|
15
|
+
options?: BaseUrlOptions;
|
|
16
|
+
}): string;
|
|
9
17
|
export declare function useBaseUrlUtils(): BaseUrlUtils;
|
|
10
18
|
export default function useBaseUrl(url: string, options?: BaseUrlOptions): string;
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
import { useCallback } from 'react';
|
|
8
8
|
import useDocusaurusContext from './useDocusaurusContext';
|
|
9
9
|
import { hasProtocol } from './isInternalUrl';
|
|
10
|
-
function addBaseUrl(siteUrl, baseUrl, url, { forcePrependBaseUrl = false, absolute = false } = {}) {
|
|
10
|
+
export function addBaseUrl({ siteUrl, baseUrl, url, options: { forcePrependBaseUrl = false, absolute = false } = {}, router, }) {
|
|
11
11
|
// It never makes sense to add base url to a local anchor url, or one with a
|
|
12
12
|
// protocol
|
|
13
13
|
if (!url || url.startsWith('#') || hasProtocol(url)) {
|
|
14
14
|
return url;
|
|
15
15
|
}
|
|
16
|
+
// TODO hash router + /baseUrl/ is unlikely to work well in all situations
|
|
17
|
+
// This will support most cases, but not all
|
|
18
|
+
// See https://github.com/facebook/docusaurus/pull/9859
|
|
19
|
+
if (router === 'hash') {
|
|
20
|
+
return url.startsWith('/') ? `.${url}` : `./${url}`;
|
|
21
|
+
}
|
|
16
22
|
if (forcePrependBaseUrl) {
|
|
17
23
|
return baseUrl + url.replace(/^\//, '');
|
|
18
24
|
}
|
|
@@ -27,8 +33,10 @@ function addBaseUrl(siteUrl, baseUrl, url, { forcePrependBaseUrl = false, absolu
|
|
|
27
33
|
return absolute ? siteUrl + basePath : basePath;
|
|
28
34
|
}
|
|
29
35
|
export function useBaseUrlUtils() {
|
|
30
|
-
const { siteConfig
|
|
31
|
-
const
|
|
36
|
+
const { siteConfig } = useDocusaurusContext();
|
|
37
|
+
const { baseUrl, url: siteUrl } = siteConfig;
|
|
38
|
+
const router = siteConfig.future.experimental_router;
|
|
39
|
+
const withBaseUrl = useCallback((url, options) => addBaseUrl({ siteUrl, baseUrl, url, options, router }), [siteUrl, baseUrl, router]);
|
|
32
40
|
return {
|
|
33
41
|
withBaseUrl,
|
|
34
42
|
};
|
package/lib/commands/build.js
CHANGED
|
@@ -17,6 +17,7 @@ const site_1 = require("../server/site");
|
|
|
17
17
|
const brokenLinks_1 = require("../server/brokenLinks");
|
|
18
18
|
const client_1 = require("../webpack/client");
|
|
19
19
|
const server_1 = tslib_1.__importDefault(require("../webpack/server"));
|
|
20
|
+
const configure_1 = require("../webpack/configure");
|
|
20
21
|
const utils_2 = require("../webpack/utils");
|
|
21
22
|
const utils_3 = require("../utils");
|
|
22
23
|
const i18n_1 = require("../server/i18n");
|
|
@@ -110,7 +111,8 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
|
|
|
110
111
|
localizePath: cliOptions.locale ? false : undefined,
|
|
111
112
|
}));
|
|
112
113
|
const { props } = site;
|
|
113
|
-
const { outDir, plugins } = props;
|
|
114
|
+
const { outDir, plugins, siteConfig } = props;
|
|
115
|
+
const router = siteConfig.future.experimental_router;
|
|
114
116
|
// We can build the 2 configs in parallel
|
|
115
117
|
const [{ clientConfig, clientManifestPath }, { serverConfig, serverBundlePath }] = await utils_3.PerfLogger.async('Creating webpack configs', () => Promise.all([
|
|
116
118
|
getBuildClientConfig({
|
|
@@ -122,11 +124,19 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
|
|
|
122
124
|
}),
|
|
123
125
|
]));
|
|
124
126
|
// Run webpack to build JS bundle (client) and static html files (server).
|
|
125
|
-
await utils_3.PerfLogger.async('Bundling with Webpack', () =>
|
|
127
|
+
await utils_3.PerfLogger.async('Bundling with Webpack', () => {
|
|
128
|
+
if (router === 'hash') {
|
|
129
|
+
return (0, utils_2.compile)([clientConfig]);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return (0, utils_2.compile)([clientConfig, serverConfig]);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
126
135
|
const { collectedData } = await utils_3.PerfLogger.async('SSG', () => executeSSG({
|
|
127
136
|
props,
|
|
128
137
|
serverBundlePath,
|
|
129
138
|
clientManifestPath,
|
|
139
|
+
router,
|
|
130
140
|
}));
|
|
131
141
|
// Remove server.bundle.js because it is not needed.
|
|
132
142
|
await utils_3.PerfLogger.async('Deleting server bundle', () => ensureUnlink(serverBundlePath));
|
|
@@ -137,27 +147,35 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
|
|
|
137
147
|
logger_1.default.success `Generated static files in path=${path_1.default.relative(process.cwd(), outDir)}.`;
|
|
138
148
|
return outDir;
|
|
139
149
|
}
|
|
140
|
-
async function executeSSG({ props, serverBundlePath, clientManifestPath, }) {
|
|
150
|
+
async function executeSSG({ props, serverBundlePath, clientManifestPath, router, }) {
|
|
141
151
|
const manifest = await utils_3.PerfLogger.async('Read client manifest', () => fs_extra_1.default.readJSON(clientManifestPath, 'utf-8'));
|
|
142
152
|
const ssrTemplate = await utils_3.PerfLogger.async('Compile SSR template', () => (0, templates_1.compileSSRTemplate)(props.siteConfig.ssrTemplate ?? ssr_html_template_1.default));
|
|
153
|
+
const params = {
|
|
154
|
+
trailingSlash: props.siteConfig.trailingSlash,
|
|
155
|
+
outDir: props.outDir,
|
|
156
|
+
baseUrl: props.baseUrl,
|
|
157
|
+
manifest,
|
|
158
|
+
headTags: props.headTags,
|
|
159
|
+
preBodyTags: props.preBodyTags,
|
|
160
|
+
postBodyTags: props.postBodyTags,
|
|
161
|
+
ssrTemplate,
|
|
162
|
+
noIndex: props.siteConfig.noIndex,
|
|
163
|
+
DOCUSAURUS_VERSION: utils_1.DOCUSAURUS_VERSION,
|
|
164
|
+
};
|
|
165
|
+
if (router === 'hash') {
|
|
166
|
+
utils_3.PerfLogger.start('Generate Hash Router entry point');
|
|
167
|
+
const content = (0, templates_1.renderHashRouterTemplate)({ params });
|
|
168
|
+
await (0, ssg_1.generateHashRouterEntrypoint)({ content, params });
|
|
169
|
+
utils_3.PerfLogger.end('Generate Hash Router entry point');
|
|
170
|
+
return { collectedData: {} };
|
|
171
|
+
}
|
|
143
172
|
const renderer = await utils_3.PerfLogger.async('Load App renderer', () => (0, ssg_1.loadAppRenderer)({
|
|
144
173
|
serverBundlePath,
|
|
145
174
|
}));
|
|
146
175
|
const ssgResult = await utils_3.PerfLogger.async('Generate static files', () => (0, ssg_1.generateStaticFiles)({
|
|
147
176
|
pathnames: props.routesPaths,
|
|
148
177
|
renderer,
|
|
149
|
-
params
|
|
150
|
-
trailingSlash: props.siteConfig.trailingSlash,
|
|
151
|
-
outDir: props.outDir,
|
|
152
|
-
baseUrl: props.baseUrl,
|
|
153
|
-
manifest,
|
|
154
|
-
headTags: props.headTags,
|
|
155
|
-
preBodyTags: props.preBodyTags,
|
|
156
|
-
postBodyTags: props.postBodyTags,
|
|
157
|
-
ssrTemplate,
|
|
158
|
-
noIndex: props.siteConfig.noIndex,
|
|
159
|
-
DOCUSAURUS_VERSION: utils_1.DOCUSAURUS_VERSION,
|
|
160
|
-
},
|
|
178
|
+
params,
|
|
161
179
|
}));
|
|
162
180
|
return ssgResult;
|
|
163
181
|
}
|
|
@@ -194,11 +212,7 @@ async function getBuildClientConfig({ props, cliOptions, }) {
|
|
|
194
212
|
bundleAnalyzer: cliOptions.bundleAnalyzer ?? false,
|
|
195
213
|
});
|
|
196
214
|
let { config } = result;
|
|
197
|
-
config = (0,
|
|
198
|
-
plugins,
|
|
199
|
-
config,
|
|
200
|
-
});
|
|
201
|
-
config = (0, utils_2.executePluginsConfigureWebpack)({
|
|
215
|
+
config = (0, configure_1.executePluginsConfigureWebpack)({
|
|
202
216
|
plugins,
|
|
203
217
|
config,
|
|
204
218
|
isServer: false,
|
|
@@ -212,7 +226,7 @@ async function getBuildServerConfig({ props }) {
|
|
|
212
226
|
props,
|
|
213
227
|
});
|
|
214
228
|
let { config } = result;
|
|
215
|
-
config = (0,
|
|
229
|
+
config = (0, configure_1.executePluginsConfigureWebpack)({
|
|
216
230
|
plugins,
|
|
217
231
|
config,
|
|
218
232
|
isServer: true,
|
package/lib/commands/serve.js
CHANGED
|
@@ -52,10 +52,21 @@ async function serve(siteDirParam = '.', cliOptions = {}) {
|
|
|
52
52
|
}
|
|
53
53
|
// We do the redirect ourselves for a good reason
|
|
54
54
|
// server-handler is annoying and won't include /baseUrl/ in redirects
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// See https://github.com/facebook/docusaurus/issues/10078#issuecomment-2084932934
|
|
56
|
+
if (baseUrl !== '/') {
|
|
57
|
+
// Not super robust, but should be good enough for our use case
|
|
58
|
+
// See https://github.com/facebook/docusaurus/pull/10090
|
|
59
|
+
const looksLikeAsset = !!req.url.match(/.[a-zA-Z\d]{1,4}$/);
|
|
60
|
+
if (!looksLikeAsset) {
|
|
61
|
+
const normalizedUrl = (0, utils_common_1.applyTrailingSlash)(req.url, {
|
|
62
|
+
trailingSlash,
|
|
63
|
+
baseUrl,
|
|
64
|
+
});
|
|
65
|
+
if (req.url !== normalizedUrl) {
|
|
66
|
+
redirect(res, normalizedUrl);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
59
70
|
}
|
|
60
71
|
// Remove baseUrl before calling serveHandler, because /baseUrl/ should
|
|
61
72
|
// serve /build/index.html, not /build/baseUrl/index.html (does not exist)
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import _ from 'lodash';
|
|
8
8
|
import type { StartCLIOptions } from './start';
|
|
9
|
-
import type { LoadedPlugin } from '@docusaurus/types';
|
|
9
|
+
import type { LoadedPlugin, RouterType } from '@docusaurus/types';
|
|
10
10
|
export type OpenUrlContext = {
|
|
11
11
|
host: string;
|
|
12
12
|
port: number;
|
|
13
|
-
getOpenUrl: ({ baseUrl }: {
|
|
13
|
+
getOpenUrl: ({ baseUrl, router, }: {
|
|
14
14
|
baseUrl: string;
|
|
15
|
+
router: RouterType;
|
|
15
16
|
}) => string;
|
|
16
17
|
};
|
|
17
18
|
export declare function createOpenUrlContext({ cliOptions, }: {
|
|
@@ -23,9 +23,13 @@ async function createOpenUrlContext({ cliOptions, }) {
|
|
|
23
23
|
if (port === null) {
|
|
24
24
|
return process.exit();
|
|
25
25
|
}
|
|
26
|
-
const getOpenUrl = ({ baseUrl }) => {
|
|
26
|
+
const getOpenUrl = ({ baseUrl, router }) => {
|
|
27
27
|
const urls = (0, WebpackDevServerUtils_1.prepareUrls)(protocol, host, port);
|
|
28
|
-
return (0, utils_1.normalizeUrl)([
|
|
28
|
+
return (0, utils_1.normalizeUrl)([
|
|
29
|
+
urls.localUrlForBrowser,
|
|
30
|
+
router === 'hash' ? '/#/' : '',
|
|
31
|
+
baseUrl,
|
|
32
|
+
]);
|
|
29
33
|
};
|
|
30
34
|
return { host, port, getOpenUrl };
|
|
31
35
|
}
|
|
@@ -47,6 +51,7 @@ async function createReloadableSite(startParams) {
|
|
|
47
51
|
const get = () => site;
|
|
48
52
|
const getOpenUrl = () => openUrlContext.getOpenUrl({
|
|
49
53
|
baseUrl: site.props.baseUrl,
|
|
54
|
+
router: site.props.siteConfig.future.experimental_router,
|
|
50
55
|
});
|
|
51
56
|
const printOpenUrlMessage = () => {
|
|
52
57
|
logger_1.default.success `Docusaurus website is running at: url=${getOpenUrl()}`;
|
|
@@ -16,6 +16,7 @@ const webpack_dev_server_1 = tslib_1.__importDefault(require("webpack-dev-server
|
|
|
16
16
|
const evalSourceMapMiddleware_1 = tslib_1.__importDefault(require("react-dev-utils/evalSourceMapMiddleware"));
|
|
17
17
|
const watcher_1 = require("./watcher");
|
|
18
18
|
const utils_1 = require("../../webpack/utils");
|
|
19
|
+
const configure_1 = require("../../webpack/configure");
|
|
19
20
|
const client_1 = require("../../webpack/client");
|
|
20
21
|
// E2E_TEST=true docusaurus start
|
|
21
22
|
// Makes "docusaurus start" exit immediately on success/error, for E2E test
|
|
@@ -60,7 +61,7 @@ async function createDevServerConfig({ cliOptions, props, host, port, }) {
|
|
|
60
61
|
'access-control-allow-origin': '*',
|
|
61
62
|
},
|
|
62
63
|
devMiddleware: {
|
|
63
|
-
publicPath: baseUrl,
|
|
64
|
+
publicPath: siteConfig.future.experimental_router === 'hash' ? 'auto' : baseUrl,
|
|
64
65
|
// Reduce log verbosity, see https://github.com/facebook/docusaurus/pull/5420#issuecomment-906613105
|
|
65
66
|
stats: 'summary',
|
|
66
67
|
},
|
|
@@ -103,8 +104,7 @@ async function getStartClientConfig({ props, minify, poll, }) {
|
|
|
103
104
|
minify,
|
|
104
105
|
poll,
|
|
105
106
|
});
|
|
106
|
-
config = (0,
|
|
107
|
-
config = (0, utils_1.executePluginsConfigureWebpack)({
|
|
107
|
+
config = (0, configure_1.executePluginsConfigureWebpack)({
|
|
108
108
|
plugins,
|
|
109
109
|
config,
|
|
110
110
|
isServer: false,
|
|
@@ -78,7 +78,15 @@ function createBrokenLinksHelper({ collectedLinks, routes, }) {
|
|
|
78
78
|
if (hash === '') {
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
|
-
const targetPage = collectedLinks.get(pathname)
|
|
81
|
+
const targetPage = collectedLinks.get(pathname) ??
|
|
82
|
+
collectedLinks.get(decodeURI(pathname)) ??
|
|
83
|
+
// The broken link checker should not care about a trailing slash
|
|
84
|
+
// Those are already covered by the broken pathname checker
|
|
85
|
+
// See https://github.com/facebook/docusaurus/issues/10116
|
|
86
|
+
collectedLinks.get((0, utils_common_1.addTrailingSlash)(pathname)) ??
|
|
87
|
+
collectedLinks.get((0, utils_common_1.addTrailingSlash)(decodeURI(pathname))) ??
|
|
88
|
+
collectedLinks.get((0, utils_common_1.removeTrailingSlash)(pathname)) ??
|
|
89
|
+
collectedLinks.get((0, utils_common_1.removeTrailingSlash)(decodeURI(pathname)));
|
|
82
90
|
// link with anchor to a page that does not exist (or did not collect any
|
|
83
91
|
// link/anchor) is considered as a broken anchor
|
|
84
92
|
if (!targetPage) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import type { CodeTranslations, DocusaurusConfig, GlobalData, I18n, PluginRouteConfig, SiteMetadata } from '@docusaurus/types';
|
|
7
|
+
import type { CodeTranslations, DocusaurusConfig, GlobalData, I18n, PluginRouteConfig, SiteMetadata, SiteStorage } from '@docusaurus/types';
|
|
8
8
|
type CodegenParams = {
|
|
9
9
|
generatedFilesDir: string;
|
|
10
10
|
siteConfig: DocusaurusConfig;
|
|
@@ -14,6 +14,7 @@ type CodegenParams = {
|
|
|
14
14
|
i18n: I18n;
|
|
15
15
|
codeTranslations: CodeTranslations;
|
|
16
16
|
siteMetadata: SiteMetadata;
|
|
17
|
+
siteStorage: SiteStorage;
|
|
17
18
|
routes: PluginRouteConfig[];
|
|
18
19
|
};
|
|
19
20
|
export declare function generateSiteFiles(params: CodegenParams): Promise<void>;
|
|
@@ -50,6 +50,9 @@ function genCodeTranslations({ generatedFilesDir, codeTranslations, }) {
|
|
|
50
50
|
function genSiteMetadata({ generatedFilesDir, siteMetadata, }) {
|
|
51
51
|
return (0, utils_1.generate)(generatedFilesDir, 'site-metadata.json', JSON.stringify(siteMetadata, null, 2));
|
|
52
52
|
}
|
|
53
|
+
function genSiteStorage({ generatedFilesDir, siteStorage, }) {
|
|
54
|
+
return (0, utils_1.generate)(generatedFilesDir, 'site-storage.json', JSON.stringify(siteStorage, null, 2));
|
|
55
|
+
}
|
|
53
56
|
async function generateSiteFiles(params) {
|
|
54
57
|
await Promise.all([
|
|
55
58
|
genWarning(params),
|
|
@@ -58,6 +61,7 @@ async function generateSiteFiles(params) {
|
|
|
58
61
|
(0, codegenRoutes_1.generateRouteFiles)(params),
|
|
59
62
|
genGlobalData(params),
|
|
60
63
|
genSiteMetadata(params),
|
|
64
|
+
genSiteStorage(params),
|
|
61
65
|
genI18n(params),
|
|
62
66
|
genCodeTranslations(params),
|
|
63
67
|
]);
|
|
@@ -45,5 +45,6 @@ type GenerateRouteFilesParams = {
|
|
|
45
45
|
routes: PluginRouteConfig[];
|
|
46
46
|
baseUrl: string;
|
|
47
47
|
};
|
|
48
|
+
export declare function generateRoutePropFilename(route: RouteConfig): string;
|
|
48
49
|
export declare function generateRouteFiles({ generatedFilesDir, routes: initialRoutes, }: GenerateRouteFilesParams): Promise<void>;
|
|
49
50
|
export {};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.generateRouteFiles = exports.generateRoutesCode = exports.genChunkName = void 0;
|
|
9
|
+
exports.generateRouteFiles = exports.generateRoutePropFilename = exports.generateRoutesCode = exports.genChunkName = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
11
|
const querystring_1 = tslib_1.__importDefault(require("querystring"));
|
|
12
12
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
@@ -180,12 +180,26 @@ ${Object.entries(registry)
|
|
|
180
180
|
`);
|
|
181
181
|
const genRoutesChunkNames = ({ generatedFilesDir, routesChunkNames, }) => (0, utils_1.generate)(generatedFilesDir, 'routesChunkNames.json', JSON.stringify(routesChunkNames, null, 2));
|
|
182
182
|
const genRoutes = ({ generatedFilesDir, routesConfig, }) => (0, utils_1.generate)(generatedFilesDir, 'routes.js', routesConfig);
|
|
183
|
+
// The generated filename per route must be unique to avoid conflicts
|
|
184
|
+
// See also https://github.com/facebook/docusaurus/issues/10125
|
|
185
|
+
function generateRoutePropFilename(route) {
|
|
186
|
+
// TODO if possible, we could try to shorten the filename by removing
|
|
187
|
+
// the plugin routeBasePath prefix from the name
|
|
188
|
+
return `${(0, utils_1.docuHash)(route.path,
|
|
189
|
+
// Note: using hash(route.path + route.component) is not technically
|
|
190
|
+
// as robust as hashing the entire prop content object.
|
|
191
|
+
// But it's faster and should be good enough considering it's very unlikely
|
|
192
|
+
// anyone would have 2 routes on the same path also rendering the exact
|
|
193
|
+
// same component.
|
|
194
|
+
{ hashExtra: route.component })}.json`;
|
|
195
|
+
}
|
|
196
|
+
exports.generateRoutePropFilename = generateRoutePropFilename;
|
|
183
197
|
async function generateRoutePropModule({ generatedFilesDir, route, plugin, }) {
|
|
184
198
|
ensureNoPropsConflict(route);
|
|
185
199
|
const moduleContent = JSON.stringify(route.props);
|
|
186
200
|
// TODO we should aim to reduce this path length
|
|
187
201
|
// This adds bytes to the global module registry
|
|
188
|
-
const relativePath = path_1.default.posix.join(plugin.name, plugin.id, 'p',
|
|
202
|
+
const relativePath = path_1.default.posix.join(plugin.name, plugin.id, 'p', generateRoutePropFilename(route));
|
|
189
203
|
const modulePath = path_1.default.posix.join(generatedFilesDir, relativePath);
|
|
190
204
|
const aliasedPath = path_1.default.posix.join('@generated', relativePath);
|
|
191
205
|
await (0, utils_1.generate)(generatedFilesDir, modulePath, moduleContent);
|
|
@@ -201,14 +215,14 @@ function ensureNoPropsConflict(route) {
|
|
|
201
215
|
}
|
|
202
216
|
}
|
|
203
217
|
async function preprocessRouteProps({ generatedFilesDir, route, plugin, }) {
|
|
204
|
-
const
|
|
218
|
+
const getPropsModulePathPromise = () => route.props
|
|
205
219
|
? generateRoutePropModule({
|
|
206
220
|
generatedFilesDir,
|
|
207
221
|
route,
|
|
208
222
|
plugin,
|
|
209
223
|
})
|
|
210
224
|
: undefined;
|
|
211
|
-
const
|
|
225
|
+
const getSubRoutesPromise = () => route.routes
|
|
212
226
|
? Promise.all(route.routes.map((subRoute) => {
|
|
213
227
|
return preprocessRouteProps({
|
|
214
228
|
generatedFilesDir,
|
|
@@ -218,8 +232,8 @@ async function preprocessRouteProps({ generatedFilesDir, route, plugin, }) {
|
|
|
218
232
|
}))
|
|
219
233
|
: undefined;
|
|
220
234
|
const [propsModulePath, subRoutes] = await Promise.all([
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
getPropsModulePathPromise(),
|
|
236
|
+
getSubRoutesPromise(),
|
|
223
237
|
]);
|
|
224
238
|
const newRoute = {
|
|
225
239
|
...route,
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { Joi } from '@docusaurus/utils-validation';
|
|
8
|
+
import type { FutureConfig, StorageConfig } from '@docusaurus/types/src/config';
|
|
8
9
|
import type { DocusaurusConfig, I18nConfig, MarkdownConfig } from '@docusaurus/types';
|
|
9
10
|
export declare const DEFAULT_I18N_CONFIG: I18nConfig;
|
|
11
|
+
export declare const DEFAULT_STORAGE_CONFIG: StorageConfig;
|
|
12
|
+
export declare const DEFAULT_FUTURE_CONFIG: FutureConfig;
|
|
10
13
|
export declare const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig;
|
|
11
|
-
export declare const DEFAULT_CONFIG: Pick<DocusaurusConfig, 'i18n' | 'onBrokenLinks' | 'onBrokenAnchors' | 'onBrokenMarkdownLinks' | 'onDuplicateRoutes' | 'plugins' | 'themes' | 'presets' | 'headTags' | 'stylesheets' | 'scripts' | 'clientModules' | 'customFields' | 'themeConfig' | 'titleDelimiter' | 'noIndex' | 'tagline' | 'baseUrlIssueBanner' | 'staticDirectories' | 'markdown'>;
|
|
14
|
+
export declare const DEFAULT_CONFIG: Pick<DocusaurusConfig, 'i18n' | 'future' | 'onBrokenLinks' | 'onBrokenAnchors' | 'onBrokenMarkdownLinks' | 'onDuplicateRoutes' | 'plugins' | 'themes' | 'presets' | 'headTags' | 'stylesheets' | 'scripts' | 'clientModules' | 'customFields' | 'themeConfig' | 'titleDelimiter' | 'noIndex' | 'tagline' | 'baseUrlIssueBanner' | 'staticDirectories' | 'markdown'>;
|
|
12
15
|
export declare const ConfigSchema: Joi.ObjectSchema<DocusaurusConfig>;
|
|
13
16
|
export declare function validateConfig(config: unknown, siteConfigPath: string): DocusaurusConfig;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.validateConfig = exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0;
|
|
9
|
+
exports.validateConfig = exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_FUTURE_CONFIG = exports.DEFAULT_STORAGE_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0;
|
|
10
10
|
const utils_1 = require("@docusaurus/utils");
|
|
11
11
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
12
12
|
const utils_common_1 = require("@docusaurus/utils-common");
|
|
@@ -17,6 +17,14 @@ exports.DEFAULT_I18N_CONFIG = {
|
|
|
17
17
|
locales: [DEFAULT_I18N_LOCALE],
|
|
18
18
|
localeConfigs: {},
|
|
19
19
|
};
|
|
20
|
+
exports.DEFAULT_STORAGE_CONFIG = {
|
|
21
|
+
type: 'localStorage',
|
|
22
|
+
namespace: false,
|
|
23
|
+
};
|
|
24
|
+
exports.DEFAULT_FUTURE_CONFIG = {
|
|
25
|
+
experimental_storage: exports.DEFAULT_STORAGE_CONFIG,
|
|
26
|
+
experimental_router: 'browser',
|
|
27
|
+
};
|
|
20
28
|
exports.DEFAULT_MARKDOWN_CONFIG = {
|
|
21
29
|
format: 'mdx', // TODO change this to "detect" in Docusaurus v4?
|
|
22
30
|
mermaid: false,
|
|
@@ -34,6 +42,7 @@ exports.DEFAULT_MARKDOWN_CONFIG = {
|
|
|
34
42
|
};
|
|
35
43
|
exports.DEFAULT_CONFIG = {
|
|
36
44
|
i18n: exports.DEFAULT_I18N_CONFIG,
|
|
45
|
+
future: exports.DEFAULT_FUTURE_CONFIG,
|
|
37
46
|
onBrokenLinks: 'throw',
|
|
38
47
|
onBrokenAnchors: 'warn', // TODO Docusaurus v4: change to throw
|
|
39
48
|
onBrokenMarkdownLinks: 'warn',
|
|
@@ -120,6 +129,24 @@ const I18N_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
|
120
129
|
})
|
|
121
130
|
.optional()
|
|
122
131
|
.default(exports.DEFAULT_I18N_CONFIG);
|
|
132
|
+
const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
133
|
+
type: utils_validation_1.Joi.string()
|
|
134
|
+
.equal('localStorage', 'sessionStorage')
|
|
135
|
+
.default(exports.DEFAULT_STORAGE_CONFIG.type),
|
|
136
|
+
namespace: utils_validation_1.Joi.alternatives()
|
|
137
|
+
.try(utils_validation_1.Joi.string(), utils_validation_1.Joi.boolean())
|
|
138
|
+
.default(exports.DEFAULT_STORAGE_CONFIG.namespace),
|
|
139
|
+
})
|
|
140
|
+
.optional()
|
|
141
|
+
.default(exports.DEFAULT_STORAGE_CONFIG);
|
|
142
|
+
const FUTURE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
143
|
+
experimental_storage: STORAGE_CONFIG_SCHEMA,
|
|
144
|
+
experimental_router: utils_validation_1.Joi.string()
|
|
145
|
+
.equal('browser', 'hash')
|
|
146
|
+
.default(exports.DEFAULT_FUTURE_CONFIG.experimental_router),
|
|
147
|
+
})
|
|
148
|
+
.optional()
|
|
149
|
+
.default(exports.DEFAULT_FUTURE_CONFIG);
|
|
123
150
|
const SiteUrlSchema = utils_validation_1.Joi.string()
|
|
124
151
|
.required()
|
|
125
152
|
.custom((value, helpers) => {
|
|
@@ -152,6 +179,7 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
|
|
|
152
179
|
url: SiteUrlSchema,
|
|
153
180
|
trailingSlash: utils_validation_1.Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior!
|
|
154
181
|
i18n: I18N_CONFIG_SCHEMA,
|
|
182
|
+
future: FUTURE_CONFIG_SCHEMA,
|
|
155
183
|
onBrokenLinks: utils_validation_1.Joi.string()
|
|
156
184
|
.equal('ignore', 'log', 'warn', 'throw')
|
|
157
185
|
.default(exports.DEFAULT_CONFIG.onBrokenLinks),
|
package/lib/server/htmlTags.d.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import type { Props, LoadedPlugin } from '@docusaurus/types';
|
|
7
|
+
import type { Props, LoadedPlugin, RouterType } from '@docusaurus/types';
|
|
8
8
|
/**
|
|
9
9
|
* Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into
|
|
10
10
|
* directly render-able HTML markup.
|
|
11
11
|
*/
|
|
12
|
-
export declare function loadHtmlTags(plugins
|
|
12
|
+
export declare function loadHtmlTags({ plugins, router, }: {
|
|
13
|
+
plugins: LoadedPlugin[];
|
|
14
|
+
router: RouterType;
|
|
15
|
+
}): Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
|
package/lib/server/htmlTags.js
CHANGED
|
@@ -23,16 +23,25 @@ function assertIsHtmlTagObject(val) {
|
|
|
23
23
|
throw new Error(`Error loading ${JSON.stringify(val)}, "${val.tagName}" is not a valid HTML tag.`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
function
|
|
26
|
+
function hashRouterAbsoluteToRelativeTagAttribute(name, value) {
|
|
27
|
+
if ((name === 'src' || name === 'href') && value.startsWith('/')) {
|
|
28
|
+
return `.${value}`;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function htmlTagObjectToString({ tag, router, }) {
|
|
27
33
|
assertIsHtmlTagObject(tag);
|
|
28
34
|
const isVoidTag = void_1.default.includes(tag.tagName);
|
|
29
35
|
const tagAttributes = tag.attributes ?? {};
|
|
30
36
|
const attributes = Object.keys(tagAttributes)
|
|
31
37
|
.map((attr) => {
|
|
32
|
-
|
|
38
|
+
let value = tagAttributes[attr];
|
|
33
39
|
if (typeof value === 'boolean') {
|
|
34
40
|
return value ? attr : undefined;
|
|
35
41
|
}
|
|
42
|
+
if (router === 'hash') {
|
|
43
|
+
value = hashRouterAbsoluteToRelativeTagAttribute(attr, value);
|
|
44
|
+
}
|
|
36
45
|
return `${attr}="${(0, escape_html_1.default)(value)}"`;
|
|
37
46
|
})
|
|
38
47
|
.filter((str) => Boolean(str));
|
|
@@ -41,21 +50,21 @@ function htmlTagObjectToString(tag) {
|
|
|
41
50
|
const closingTag = isVoidTag ? '' : `</${tag.tagName}>`;
|
|
42
51
|
return openingTag + innerHTML + closingTag;
|
|
43
52
|
}
|
|
44
|
-
function createHtmlTagsString(tags) {
|
|
53
|
+
function createHtmlTagsString({ tags, router, }) {
|
|
45
54
|
return (Array.isArray(tags) ? tags : [tags])
|
|
46
55
|
.filter(Boolean)
|
|
47
|
-
.map((val) =>
|
|
56
|
+
.map((val) => typeof val === 'string' ? val : htmlTagObjectToString({ tag: val, router }))
|
|
48
57
|
.join('\n');
|
|
49
58
|
}
|
|
50
59
|
/**
|
|
51
60
|
* Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into
|
|
52
61
|
* directly render-able HTML markup.
|
|
53
62
|
*/
|
|
54
|
-
function loadHtmlTags(plugins) {
|
|
63
|
+
function loadHtmlTags({ plugins, router, }) {
|
|
55
64
|
const pluginHtmlTags = plugins.map((plugin) => plugin.injectHtmlTags?.({ content: plugin.content }) ?? {});
|
|
56
65
|
const tagTypes = ['headTags', 'preBodyTags', 'postBodyTags'];
|
|
57
66
|
return Object.fromEntries(lodash_1.default.zip(tagTypes, tagTypes.map((type) => pluginHtmlTags
|
|
58
|
-
.map((tags) => createHtmlTagsString(tags[type]))
|
|
67
|
+
.map((tags) => createHtmlTagsString({ tags: tags[type], router }))
|
|
59
68
|
.join('\n')
|
|
60
69
|
.trim())));
|
|
61
70
|
}
|