@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.
Files changed (38) hide show
  1. package/lib/client/clientEntry.js +7 -3
  2. package/lib/client/exports/Link.js +12 -2
  3. package/lib/client/exports/useBaseUrl.d.ts +8 -0
  4. package/lib/client/exports/useBaseUrl.js +11 -3
  5. package/lib/commands/build.js +35 -21
  6. package/lib/commands/serve.js +15 -4
  7. package/lib/commands/start/utils.d.ts +3 -2
  8. package/lib/commands/start/utils.js +7 -2
  9. package/lib/commands/start/webpack.js +3 -3
  10. package/lib/server/brokenLinks.js +9 -1
  11. package/lib/server/codegen/codegen.d.ts +2 -1
  12. package/lib/server/codegen/codegen.js +4 -0
  13. package/lib/server/codegen/codegenRoutes.d.ts +1 -0
  14. package/lib/server/codegen/codegenRoutes.js +20 -6
  15. package/lib/server/configValidation.d.ts +4 -1
  16. package/lib/server/configValidation.js +29 -1
  17. package/lib/server/htmlTags.d.ts +5 -2
  18. package/lib/server/htmlTags.js +15 -6
  19. package/lib/server/i18n.js +37 -8
  20. package/lib/server/site.js +13 -5
  21. package/lib/server/storage.d.ts +13 -0
  22. package/lib/server/storage.js +36 -0
  23. package/lib/ssg.d.ts +4 -0
  24. package/lib/ssg.js +9 -1
  25. package/lib/templates/templates.d.ts +3 -0
  26. package/lib/templates/templates.js +26 -1
  27. package/lib/webpack/base.js +1 -1
  28. package/lib/webpack/client.js +10 -18
  29. package/lib/webpack/configure.d.ts +25 -0
  30. package/lib/webpack/configure.js +100 -0
  31. package/lib/webpack/plugins/ForceTerminatePlugin.d.ts +10 -0
  32. package/lib/webpack/plugins/ForceTerminatePlugin.js +25 -0
  33. package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.d.ts +11 -0
  34. package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.js +39 -0
  35. package/lib/webpack/server.js +0 -28
  36. package/lib/webpack/utils.d.ts +0 -22
  37. package/lib/webpack/utils.js +1 -80
  38. 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
- <BrowserRouter>
26
+ <Router>
23
27
  <App />
24
- </BrowserRouter>
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: { trailingSlash, baseUrl }, } = useDocusaurusContext();
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 || isAnchorLink;
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: { baseUrl, url: siteUrl }, } = useDocusaurusContext();
31
- const withBaseUrl = useCallback((url, options) => addBaseUrl(siteUrl, baseUrl, url, options), [siteUrl, baseUrl]);
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
  };
@@ -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', () => (0, utils_2.compile)([clientConfig, serverConfig]));
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, utils_2.executePluginsConfigurePostCss)({
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, utils_2.executePluginsConfigureWebpack)({
229
+ config = (0, configure_1.executePluginsConfigureWebpack)({
216
230
  plugins,
217
231
  config,
218
232
  isServer: true,
@@ -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
- const normalizedUrl = (0, utils_common_1.applyTrailingSlash)(req.url, { trailingSlash, baseUrl });
56
- if (req.url !== normalizedUrl) {
57
- redirect(res, normalizedUrl);
58
- return;
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)([urls.localUrlForBrowser, baseUrl]);
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, utils_1.executePluginsConfigurePostCss)({ plugins, config });
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) || collectedLinks.get(decodeURI(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', `${(0, utils_1.docuHash)(route.path)}.json`);
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 propsModulePathPromise = route.props
218
+ const getPropsModulePathPromise = () => route.props
205
219
  ? generateRoutePropModule({
206
220
  generatedFilesDir,
207
221
  route,
208
222
  plugin,
209
223
  })
210
224
  : undefined;
211
- const subRoutesPromise = route.routes
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
- propsModulePathPromise,
222
- subRoutesPromise,
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),
@@ -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: LoadedPlugin[]): Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
12
+ export declare function loadHtmlTags({ plugins, router, }: {
13
+ plugins: LoadedPlugin[];
14
+ router: RouterType;
15
+ }): Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
@@ -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 htmlTagObjectToString(tag) {
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
- const value = tagAttributes[attr];
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) => (typeof val === 'string' ? val : htmlTagObjectToString(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
  }