@docusaurus/core 2.0.0-beta.18 → 2.0.0-beta.19

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 (130) hide show
  1. package/bin/beforeCli.mjs +12 -7
  2. package/bin/docusaurus.mjs +21 -72
  3. package/lib/client/.eslintrc.js +2 -3
  4. package/lib/client/App.d.ts +1 -1
  5. package/lib/client/App.js +9 -5
  6. package/lib/client/{baseUrlIssueBanner/BaseUrlIssueBanner.d.ts → BaseUrlIssueBanner/index.d.ts} +10 -5
  7. package/lib/client/{baseUrlIssueBanner/BaseUrlIssueBanner.js → BaseUrlIssueBanner/index.js} +14 -9
  8. package/lib/client/{baseUrlIssueBanner → BaseUrlIssueBanner}/styles.module.css +0 -0
  9. package/lib/client/ClientLifecyclesDispatcher.d.ts +16 -0
  10. package/lib/client/ClientLifecyclesDispatcher.js +34 -0
  11. package/lib/client/LinksCollector.js +1 -2
  12. package/lib/client/PendingNavigation.d.ts +8 -17
  13. package/lib/client/PendingNavigation.js +39 -70
  14. package/lib/client/clientEntry.js +1 -2
  15. package/lib/client/docusaurus.d.ts +5 -5
  16. package/lib/client/docusaurus.js +25 -29
  17. package/lib/client/exports/BrowserOnly.d.ts +3 -4
  18. package/lib/client/exports/BrowserOnly.js +1 -1
  19. package/lib/client/exports/ComponentCreator.js +51 -46
  20. package/lib/client/exports/ErrorBoundary.d.ts +2 -2
  21. package/lib/client/exports/Interpolate.js +16 -39
  22. package/lib/client/exports/Link.d.ts +3 -15
  23. package/lib/client/exports/Link.js +21 -26
  24. package/lib/client/exports/useBaseUrl.js +3 -9
  25. package/lib/client/exports/useGlobalData.d.ts +3 -3
  26. package/lib/client/exports/useGlobalData.js +5 -5
  27. package/lib/client/flat.d.ts +10 -2
  28. package/lib/client/flat.js +11 -3
  29. package/lib/client/normalizeLocation.js +14 -5
  30. package/lib/client/prefetch.js +7 -25
  31. package/lib/client/preload.d.ts +1 -3
  32. package/lib/client/preload.js +2 -2
  33. package/lib/client/routeContext.js +1 -1
  34. package/lib/client/serverEntry.js +12 -11
  35. package/lib/client/theme-fallback/Error/index.js +2 -0
  36. package/lib/client/theme-fallback/Loading/index.js +2 -0
  37. package/lib/client/theme-fallback/NotFound/index.js +2 -0
  38. package/lib/commands/build.d.ts +6 -2
  39. package/lib/commands/build.js +35 -15
  40. package/lib/commands/clear.d.ts +1 -1
  41. package/lib/commands/clear.js +3 -2
  42. package/lib/commands/deploy.d.ts +5 -2
  43. package/lib/commands/deploy.js +12 -9
  44. package/lib/commands/external.d.ts +1 -1
  45. package/lib/commands/external.js +5 -6
  46. package/lib/commands/serve.d.ts +7 -2
  47. package/lib/commands/serve.js +12 -12
  48. package/lib/commands/start.d.ts +8 -2
  49. package/lib/commands/start.js +14 -9
  50. package/lib/commands/swizzle/actions.d.ts +2 -2
  51. package/lib/commands/swizzle/actions.js +5 -4
  52. package/lib/commands/swizzle/common.d.ts +3 -3
  53. package/lib/commands/swizzle/components.js +41 -3
  54. package/lib/commands/swizzle/config.js +14 -11
  55. package/lib/commands/swizzle/context.js +6 -10
  56. package/lib/commands/swizzle/index.d.ts +2 -2
  57. package/lib/commands/swizzle/index.js +4 -3
  58. package/lib/commands/writeHeadingIds.d.ts +1 -1
  59. package/lib/commands/writeHeadingIds.js +5 -8
  60. package/lib/commands/writeTranslations.d.ts +3 -4
  61. package/lib/commands/writeTranslations.js +7 -9
  62. package/lib/index.d.ts +9 -10
  63. package/lib/index.js +18 -19
  64. package/lib/server/brokenLinks.js +1 -2
  65. package/lib/server/{client-modules/index.d.ts → clientModules.d.ts} +5 -1
  66. package/lib/server/{client-modules/index.js → clientModules.js} +6 -1
  67. package/lib/server/config.d.ts +5 -2
  68. package/lib/server/config.js +11 -6
  69. package/lib/server/configValidation.js +6 -5
  70. package/lib/server/getHostPort.d.ts +14 -0
  71. package/lib/{choosePort.js → server/getHostPort.js} +21 -35
  72. package/lib/server/htmlTags.d.ts +12 -0
  73. package/lib/server/htmlTags.js +62 -0
  74. package/lib/server/i18n.d.ts +2 -11
  75. package/lib/server/i18n.js +4 -19
  76. package/lib/server/index.d.ts +28 -13
  77. package/lib/server/index.js +42 -210
  78. package/lib/server/plugins/configs.d.ts +51 -0
  79. package/lib/server/plugins/configs.js +101 -0
  80. package/lib/server/plugins/index.d.ts +8 -7
  81. package/lib/server/plugins/index.js +59 -134
  82. package/lib/server/plugins/init.d.ts +6 -19
  83. package/lib/server/plugins/init.js +16 -68
  84. package/lib/server/{moduleShorthand.d.ts → plugins/moduleShorthand.d.ts} +0 -0
  85. package/lib/server/{moduleShorthand.js → plugins/moduleShorthand.js} +0 -0
  86. package/lib/server/plugins/pluginIds.d.ts +4 -0
  87. package/lib/server/plugins/pluginIds.js +4 -2
  88. package/lib/server/plugins/presets.d.ts +12 -0
  89. package/lib/server/{presets/index.js → plugins/presets.js} +14 -6
  90. package/lib/server/plugins/{applyRouteTrailingSlash.d.ts → routeConfig.d.ts} +3 -1
  91. package/lib/server/plugins/routeConfig.js +54 -0
  92. package/lib/server/plugins/synthetic.d.ts +20 -0
  93. package/lib/server/plugins/synthetic.js +112 -0
  94. package/lib/server/routes.d.ts +42 -8
  95. package/lib/server/routes.js +150 -92
  96. package/lib/server/{versions/index.d.ts → siteMetadata.d.ts} +5 -2
  97. package/lib/server/{versions/index.js → siteMetadata.js} +36 -3
  98. package/lib/server/translations/translations.d.ts +5 -13
  99. package/lib/server/translations/translations.js +5 -8
  100. package/lib/server/translations/translationsExtractor.d.ts +2 -4
  101. package/lib/webpack/aliases/index.d.ts +34 -0
  102. package/lib/webpack/aliases/index.js +106 -0
  103. package/lib/webpack/base.d.ts +0 -3
  104. package/lib/webpack/base.js +8 -25
  105. package/lib/webpack/client.js +1 -1
  106. package/lib/webpack/plugins/CleanWebpackPlugin.d.ts +2 -2
  107. package/lib/webpack/plugins/WaitPlugin.d.ts +2 -2
  108. package/lib/webpack/server.d.ts +2 -2
  109. package/lib/webpack/server.js +5 -3
  110. package/lib/webpack/utils.d.ts +3 -3
  111. package/lib/webpack/utils.js +3 -3
  112. package/package.json +33 -36
  113. package/lib/choosePort.d.ts +0 -11
  114. package/lib/client/client-lifecycles-dispatcher.d.ts +0 -9
  115. package/lib/client/client-lifecycles-dispatcher.js +0 -23
  116. package/lib/client/nprogress.css +0 -36
  117. package/lib/commands/commandUtils.d.ts +0 -9
  118. package/lib/commands/commandUtils.js +0 -21
  119. package/lib/server/duplicateRoutes.d.ts +0 -8
  120. package/lib/server/duplicateRoutes.js +0 -42
  121. package/lib/server/html-tags/htmlTags.d.ts +0 -7
  122. package/lib/server/html-tags/htmlTags.js +0 -38
  123. package/lib/server/html-tags/index.d.ts +0 -8
  124. package/lib/server/html-tags/index.js +0 -42
  125. package/lib/server/plugins/applyRouteTrailingSlash.js +0 -19
  126. package/lib/server/presets/index.d.ts +0 -11
  127. package/lib/server/themes/alias.d.ts +0 -9
  128. package/lib/server/themes/alias.js +0 -50
  129. package/lib/server/themes/index.d.ts +0 -12
  130. package/lib/server/themes/index.js +0 -47
package/bin/beforeCli.mjs CHANGED
@@ -14,15 +14,21 @@ import path from 'path';
14
14
  import updateNotifier from 'update-notifier';
15
15
  import boxen from 'boxen';
16
16
  import {createRequire} from 'module';
17
+ import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
17
18
 
18
19
  const packageJson = createRequire(import.meta.url)('../package.json');
19
- const sitePkg = createRequire(path.join(process.cwd(), 'package.json'))(
20
- './package.json',
21
- );
20
+ /** @type {Record<string, any>} */
21
+ let sitePkg;
22
+ try {
23
+ sitePkg = createRequire(path.resolve('package.json'))('./package.json');
24
+ } catch {
25
+ logger.warn`path=${'package.json'} file not found at CWD: path=${process.cwd()}.`;
26
+ logger.info`This is non-critical, but could lead to undesired behavior downstream. Docusaurus assumes that path=${'package.json'} exists at CWD, because it's where the package manager looks up the script at. A common reason is because you have changed directory in the script. Instead of writing code=${'"start": "cd website && docusaurus start"'}, consider using the code=${'[siteDir]'} argument: code=${'"start": "docusaurus start website"'}.`;
27
+ sitePkg = {};
28
+ }
22
29
 
23
30
  const {
24
31
  name,
25
- version,
26
32
  engines: {node: requiredVersion},
27
33
  } = packageJson;
28
34
 
@@ -40,12 +46,11 @@ export default async function beforeCli() {
40
46
  const notifier = updateNotifier({
41
47
  pkg: {
42
48
  name,
43
- version,
49
+ version: DOCUSAURUS_VERSION,
44
50
  },
45
51
  // Check is in background so it's fine to use a small value like 1h
46
52
  // Use 0 for debugging
47
53
  updateCheckInterval: 1000 * 60 * 60,
48
- // updateCheckInterval: 0
49
54
  });
50
55
 
51
56
  // Hacky way to ensure we check for updates on first run
@@ -124,7 +129,7 @@ export default async function beforeCli() {
124
129
  console.log(docusaurusUpdateMessage);
125
130
  }
126
131
 
127
- // notify user if node version needs to be updated
132
+ // Notify user if node version needs to be updated
128
133
  if (!semver.satisfies(process.version, requiredVersion)) {
129
134
  logger.error('Minimum Node.js version not met :(');
130
135
  logger.info`You are using Node.js number=${process.version}, Requirement: Node.js number=${requiredVersion}.`;
@@ -11,7 +11,7 @@
11
11
  import logger from '@docusaurus/logger';
12
12
  import fs from 'fs-extra';
13
13
  import cli from 'commander';
14
- import {createRequire} from 'module';
14
+ import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
15
15
  import {
16
16
  build,
17
17
  swizzle,
@@ -29,9 +29,7 @@ await beforeCli();
29
29
 
30
30
  const resolveDir = (dir = '.') => fs.realpath(dir);
31
31
 
32
- cli
33
- .version(createRequire(import.meta.url)('../package.json').version)
34
- .usage('<command> [options]');
32
+ cli.version(DOCUSAURUS_VERSION).usage('<command> [options]');
35
33
 
36
34
  cli
37
35
  .command('build [siteDir]')
@@ -56,14 +54,8 @@ cli
56
54
  '--no-minify',
57
55
  'build website without minimizing JS bundles (default: false)',
58
56
  )
59
- .action(async (siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => {
60
- build(await resolveDir(siteDir), {
61
- bundleAnalyzer,
62
- outDir,
63
- config,
64
- locale,
65
- minify,
66
- });
57
+ .action(async (siteDir, options) => {
58
+ build(await resolveDir(siteDir), options);
67
59
  });
68
60
 
69
61
  cli
@@ -88,9 +80,9 @@ cli
88
80
  'copy TypeScript theme files when possible (default: false)',
89
81
  )
90
82
  .option('--danger', 'enable swizzle for unsafe component of themes')
91
- .action(async (themeName, componentName, siteDir, options) => {
92
- swizzle(await resolveDir(siteDir), themeName, componentName, options);
93
- });
83
+ .action(async (themeName, componentName, siteDir, options) =>
84
+ swizzle(await resolveDir(siteDir), themeName, componentName, options),
85
+ );
94
86
 
95
87
  cli
96
88
  .command('deploy [siteDir]')
@@ -111,13 +103,9 @@ cli
111
103
  '--skip-build',
112
104
  'skip building website before deploy it (default: false)',
113
105
  )
114
- .action(async (siteDir, {outDir, skipBuild, config}) => {
115
- deploy(await resolveDir(siteDir), {
116
- outDir,
117
- config,
118
- skipBuild,
119
- });
120
- });
106
+ .action(async (siteDir, options) =>
107
+ deploy(await resolveDir(siteDir), options),
108
+ );
121
109
 
122
110
  cli
123
111
  .command('start [siteDir]')
@@ -138,18 +126,8 @@ cli
138
126
  '--poll [interval]',
139
127
  'use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds',
140
128
  )
141
- .action(
142
- async (siteDir, {port, host, locale, config, hotOnly, open, poll}) => {
143
- start(await resolveDir(siteDir), {
144
- port,
145
- host,
146
- locale,
147
- config,
148
- hotOnly,
149
- open,
150
- poll,
151
- });
152
- },
129
+ .action(async (siteDir, options) =>
130
+ start(await resolveDir(siteDir), options),
153
131
  );
154
132
 
155
133
  cli
@@ -166,44 +144,25 @@ cli
166
144
  .option('-p, --port <port>', 'use specified port (default: 3000)')
167
145
  .option('--build', 'build website before serving (default: false)')
168
146
  .option('-h, --host <host>', 'use specified host (default: localhost)')
169
- .action(
170
- async (
171
- siteDir,
172
- {
173
- dir = 'build',
174
- port = 3000,
175
- host = 'localhost',
176
- build: buildSite = false,
177
- config,
178
- },
179
- ) => {
180
- serve(await resolveDir(siteDir), {
181
- dir,
182
- port,
183
- build: buildSite,
184
- config,
185
- host,
186
- });
187
- },
147
+ .action(async (siteDir, options) =>
148
+ serve(await resolveDir(siteDir), options),
188
149
  );
189
150
 
190
151
  cli
191
152
  .command('clear [siteDir]')
192
153
  .description('Remove build artifacts.')
193
- .action(async (siteDir) => {
194
- clear(await resolveDir(siteDir));
195
- });
154
+ .action(async (siteDir) => clear(await resolveDir(siteDir)));
196
155
 
197
156
  cli
198
157
  .command('write-translations [siteDir]')
199
158
  .description('Extract required translations of your site.')
200
159
  .option(
201
160
  '-l, --locale <locale>',
202
- 'the locale folder to write the translations\n"--locale fr" will write translations in ./i18n/fr folder)',
161
+ 'the locale folder to write the translations.\n"--locale fr" will write translations in the ./i18n/fr folder.',
203
162
  )
204
163
  .option(
205
164
  '--override',
206
- 'by default, we only append missing translation messages to existing translation files. This option allows to override existing translation messages. Make sure to commit or backup your existing translations, as they may be overridden',
165
+ 'By default, we only append missing translation messages to existing translation files. This option allows to override existing translation messages. Make sure to commit or backup your existing translations, as they may be overridden. (default: false)',
207
166
  )
208
167
  .option(
209
168
  '--config <config>',
@@ -211,20 +170,10 @@ cli
211
170
  )
212
171
  .option(
213
172
  '--messagePrefix <messagePrefix>',
214
- 'allows to init new written messages with a given prefix. This might help you to highlight untranslated message to make them stand out in the UI',
173
+ 'Allows to init new written messages with a given prefix. This might help you to highlight untranslated message by making them stand out in the UI (default: "")',
215
174
  )
216
- .action(
217
- async (
218
- siteDir,
219
- {locale = undefined, override = false, messagePrefix = '', config},
220
- ) => {
221
- writeTranslations(await resolveDir(siteDir), {
222
- locale,
223
- override,
224
- config,
225
- messagePrefix,
226
- });
227
- },
175
+ .action(async (siteDir, options) =>
176
+ writeTranslations(await resolveDir(siteDir), options),
228
177
  );
229
178
 
230
179
  cli
@@ -274,6 +223,6 @@ if (!process.argv.slice(2).length) {
274
223
  cli.parse(process.argv);
275
224
 
276
225
  process.on('unhandledRejection', (err) => {
277
- logger.error(err);
226
+ logger.error(err instanceof Error ? err.stack : err);
278
227
  process.exit(1);
279
228
  });
@@ -11,12 +11,11 @@ module.exports = {
11
11
  'error',
12
12
  {
13
13
  patterns: [
14
- // prevent importing lodash in client bundle
15
- // prefer shipping vanilla JS
14
+ // Prevent importing lodash in client bundle for bundle size
16
15
  'lodash',
17
16
  'lodash.**',
18
17
  'lodash/**',
19
- // prevent importing server code in client bundle
18
+ // Prevent importing server code in client bundle
20
19
  '**/../babel/**',
21
20
  '**/../server/**',
22
21
  '**/../commands/**',
@@ -5,5 +5,5 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  /// <reference types="react" />
8
- import './client-lifecycles-dispatcher';
8
+ import '@generated/client-modules';
9
9
  export default function App(): JSX.Element;
package/lib/client/App.js CHANGED
@@ -5,20 +5,24 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import React from 'react';
8
+ import '@generated/client-modules';
8
9
  import routes from '@generated/routes';
9
- import renderRoutes from './exports/renderRoutes';
10
+ import { useLocation } from '@docusaurus/router';
11
+ import normalizeLocation from './normalizeLocation';
12
+ import renderRoutes from '@docusaurus/renderRoutes';
10
13
  import { BrowserContextProvider } from './browserContext';
11
14
  import { DocusaurusContextProvider } from './docusaurusContext';
12
15
  import PendingNavigation from './PendingNavigation';
13
- import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
16
+ import BaseUrlIssueBanner from './BaseUrlIssueBanner';
14
17
  import SiteMetadataDefaults from './SiteMetadataDefaults';
15
18
  import Root from '@theme/Root';
16
19
  import SiteMetadata from '@theme/SiteMetadata';
17
- import './client-lifecycles-dispatcher';
18
20
  // TODO, quick fix for CSS insertion order
19
21
  import ErrorBoundary from '@docusaurus/ErrorBoundary';
20
22
  import Error from '@theme/Error';
21
23
  export default function App() {
24
+ const routeElement = renderRoutes(routes);
25
+ const location = useLocation();
22
26
  return (<ErrorBoundary fallback={Error}>
23
27
  <DocusaurusContextProvider>
24
28
  <BrowserContextProvider>
@@ -26,8 +30,8 @@ export default function App() {
26
30
  <SiteMetadataDefaults />
27
31
  <SiteMetadata />
28
32
  <BaseUrlIssueBanner />
29
- <PendingNavigation routes={routes} delay={1000}>
30
- {renderRoutes(routes)}
33
+ <PendingNavigation location={normalizeLocation(location)}>
34
+ {routeElement}
31
35
  </PendingNavigation>
32
36
  </Root>
33
37
  </BrowserContextProvider>
@@ -6,17 +6,22 @@
6
6
  */
7
7
  /// <reference types="react" />
8
8
  import './styles.module.css';
9
+ declare const InsertBannerWindowAttribute = "__DOCUSAURUS_INSERT_BASEURL_BANNER";
9
10
  declare global {
10
11
  interface Window {
11
- __DOCUSAURUS_INSERT_BASEURL_BANNER: boolean;
12
+ [InsertBannerWindowAttribute]: boolean;
12
13
  }
13
14
  }
14
15
  /**
15
16
  * We want to help the users with a bad baseUrl configuration (very common
16
- * error) Help message is inlined, and hidden if JS or CSS is able to load
17
+ * error). Help message is inlined, and hidden if JS or CSS is able to load.
18
+ *
19
+ * This component only inserts the base URL banner for the homepage, to avoid
20
+ * polluting every statically rendered page.
21
+ *
17
22
  * Note: it might create false positives (ie network failures): not a big deal
18
- * Note: we only inline this for the homepage to avoid polluting all the site's
19
- * pages
23
+ *
20
24
  * @see https://github.com/facebook/docusaurus/pull/3621
21
25
  */
22
- export default function BaseUrlIssueBanner(): JSX.Element | null;
26
+ export default function MaybeBaseUrlIssueBanner(): JSX.Element | null;
27
+ export {};
@@ -30,7 +30,7 @@ function createInlineHtmlBanner(baseUrl) {
30
30
  </div>
31
31
  `;
32
32
  }
33
- // fn needs to work for older browsers!
33
+ // Needs to work for older browsers!
34
34
  function createInlineScript(baseUrl) {
35
35
  return `
36
36
  window['${InsertBannerWindowAttribute}'] = true;
@@ -60,7 +60,7 @@ function insertBanner() {
60
60
  }
61
61
  `;
62
62
  }
63
- function BaseUrlIssueBannerEnabled() {
63
+ function BaseUrlIssueBanner() {
64
64
  const { siteConfig: { baseUrl }, } = useDocusaurusContext();
65
65
  // useLayoutEffect fires before DOMContentLoaded.
66
66
  // It gives the opportunity to avoid inserting the banner in the first place
@@ -68,7 +68,10 @@ function BaseUrlIssueBannerEnabled() {
68
68
  window[InsertBannerWindowAttribute] = false;
69
69
  }, []);
70
70
  return (<>
71
- {!ExecutionEnvironment.canUseDOM && (<Head>
71
+ {!ExecutionEnvironment.canUseDOM && (
72
+ // Safe to use `ExecutionEnvironment`, because `Head` is purely
73
+ // side-effect and doesn't affect hydration
74
+ <Head>
72
75
  <script>{createInlineScript(baseUrl)}</script>
73
76
  </Head>)}
74
77
  <div id={BannerContainerId}/>
@@ -76,17 +79,19 @@ function BaseUrlIssueBannerEnabled() {
76
79
  }
77
80
  /**
78
81
  * We want to help the users with a bad baseUrl configuration (very common
79
- * error) Help message is inlined, and hidden if JS or CSS is able to load
82
+ * error). Help message is inlined, and hidden if JS or CSS is able to load.
83
+ *
84
+ * This component only inserts the base URL banner for the homepage, to avoid
85
+ * polluting every statically rendered page.
86
+ *
80
87
  * Note: it might create false positives (ie network failures): not a big deal
81
- * Note: we only inline this for the homepage to avoid polluting all the site's
82
- * pages
88
+ *
83
89
  * @see https://github.com/facebook/docusaurus/pull/3621
84
90
  */
85
- export default function BaseUrlIssueBanner() {
91
+ export default function MaybeBaseUrlIssueBanner() {
86
92
  const { siteConfig: { baseUrl, baseUrlIssueBanner }, } = useDocusaurusContext();
87
93
  const { pathname } = useLocation();
88
- // returns true for the homepage during SRR
89
94
  const isHomePage = pathname === baseUrl;
90
95
  const enabled = baseUrlIssueBanner && isHomePage;
91
- return enabled ? <BaseUrlIssueBannerEnabled /> : null;
96
+ return enabled ? <BaseUrlIssueBanner /> : null;
92
97
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { type ReactElement } from 'react';
8
+ import type { ClientModule } from '@docusaurus/types';
9
+ import type { Location } from 'history';
10
+ export declare function dispatchLifecycleAction<K extends keyof ClientModule>(lifecycleAction: K, ...args: Parameters<NonNullable<ClientModule[K]>>): () => void;
11
+ declare function ClientLifecyclesDispatcher({ children, location, previousLocation, }: {
12
+ children: ReactElement;
13
+ location: Location;
14
+ previousLocation: Location | null;
15
+ }): JSX.Element;
16
+ export default ClientLifecyclesDispatcher;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { useLayoutEffect } from 'react';
8
+ import clientModules from '@generated/client-modules';
9
+ export function dispatchLifecycleAction(lifecycleAction, ...args) {
10
+ const callbacks = clientModules.map((clientModule) => {
11
+ const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ??
12
+ clientModule[lifecycleAction]);
13
+ return lifecycleFunction?.(...args);
14
+ });
15
+ return () => callbacks.forEach((cb) => cb?.());
16
+ }
17
+ function ClientLifecyclesDispatcher({ children, location, previousLocation, }) {
18
+ useLayoutEffect(() => {
19
+ if (previousLocation !== location) {
20
+ const { hash } = location;
21
+ if (!hash) {
22
+ window.scrollTo(0, 0);
23
+ }
24
+ else {
25
+ const id = decodeURIComponent(hash.substring(1));
26
+ const element = document.getElementById(id);
27
+ element?.scrollIntoView();
28
+ }
29
+ dispatchLifecycleAction('onRouteDidUpdate', { previousLocation, location });
30
+ }
31
+ }, [previousLocation, location]);
32
+ return children;
33
+ }
34
+ export default ClientLifecyclesDispatcher;
@@ -17,8 +17,7 @@ export const createStatefulLinksCollector = () => {
17
17
  };
18
18
  const Context = React.createContext({
19
19
  collectLink: () => {
20
- // noop by default for client
21
- // we only use the broken links checker server-side
20
+ // No-op for client. We only use the broken links checker server-side.
22
21
  },
23
22
  });
24
23
  export const useLinksCollector = () => useContext(Context);
@@ -4,29 +4,20 @@
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
- /// <reference types="node" />
8
7
  import React from 'react';
9
- import { type RouteComponentProps } from 'react-router-dom';
10
- import type { RouteConfig } from 'react-router-config';
11
8
  import type { Location } from 'history';
12
- import './nprogress.css';
13
- interface Props extends RouteComponentProps {
14
- readonly routes: RouteConfig[];
15
- readonly delay: number;
9
+ declare type Props = {
16
10
  readonly location: Location;
17
- }
18
- interface State {
11
+ readonly children: JSX.Element;
12
+ };
13
+ declare type State = {
19
14
  nextRouteHasLoaded: boolean;
20
- }
15
+ };
21
16
  declare class PendingNavigation extends React.Component<Props, State> {
22
- previousLocation: Location | null;
23
- progressBarTimeout: NodeJS.Timeout | null;
17
+ private previousLocation;
18
+ private routeUpdateCleanupCb;
24
19
  constructor(props: Props);
25
20
  shouldComponentUpdate(nextProps: Props, nextState: State): boolean;
26
- private clearProgressBarTimeout;
27
- private startProgressBar;
28
- private stopProgressBar;
29
21
  render(): JSX.Element;
30
22
  }
31
- declare const _default: React.ComponentClass<Pick<Props, "routes" | "delay">, any> & import("react-router").WithRouterStatics<typeof PendingNavigation>;
32
- export default _default;
23
+ export default PendingNavigation;
@@ -5,19 +5,21 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import React from 'react';
8
- import { Route, withRouter } from 'react-router-dom';
9
- import nprogress from 'nprogress';
10
- import clientLifecyclesDispatcher from './client-lifecycles-dispatcher';
8
+ import { Route } from 'react-router-dom';
9
+ import ClientLifecyclesDispatcher, { dispatchLifecycleAction, } from './ClientLifecyclesDispatcher';
10
+ import ExecutionEnvironment from './exports/ExecutionEnvironment';
11
11
  import preload from './preload';
12
- import normalizeLocation from './normalizeLocation';
13
- import './nprogress.css';
14
- nprogress.configure({ showSpinner: false });
15
12
  class PendingNavigation extends React.Component {
16
13
  constructor(props) {
17
14
  super(props);
18
15
  // previousLocation doesn't affect rendering, hence not stored in state.
19
16
  this.previousLocation = null;
20
- this.progressBarTimeout = null;
17
+ this.routeUpdateCleanupCb = ExecutionEnvironment.canUseDOM
18
+ ? dispatchLifecycleAction('onRouteUpdate', {
19
+ previousLocation: null,
20
+ location: this.props.location,
21
+ })
22
+ : () => { };
21
23
  this.state = {
22
24
  nextRouteHasLoaded: true,
23
25
  };
@@ -25,72 +27,39 @@ class PendingNavigation extends React.Component {
25
27
  // Intercept location update and still show current route until next route
26
28
  // is done loading.
27
29
  shouldComponentUpdate(nextProps, nextState) {
28
- const routeDidChange = nextProps.location !== this.props.location;
29
- const { routes, delay } = this.props;
30
- // If `routeDidChange` is true, means the router is trying to navigate to a
31
- // new route. We will preload the new route.
32
- if (routeDidChange) {
33
- const nextLocation = normalizeLocation(nextProps.location);
34
- this.startProgressBar(delay);
35
- // Save the location first.
36
- this.previousLocation = normalizeLocation(this.props.location);
37
- this.setState({
38
- nextRouteHasLoaded: false,
39
- });
40
- // Load data while the old screen remains.
41
- preload(routes, nextLocation.pathname)
42
- .then(() => {
43
- clientLifecyclesDispatcher.onRouteUpdate({
44
- previousLocation: this.previousLocation,
45
- location: nextLocation,
46
- });
47
- // Route has loaded, we can reset previousLocation.
48
- this.previousLocation = null;
49
- this.setState({ nextRouteHasLoaded: true }, this.stopProgressBar);
50
- const { hash } = nextLocation;
51
- if (!hash) {
52
- window.scrollTo(0, 0);
53
- }
54
- else {
55
- const id = decodeURIComponent(hash.substring(1));
56
- const element = document.getElementById(id);
57
- if (element) {
58
- element.scrollIntoView();
59
- }
60
- }
61
- })
62
- .catch((e) => console.warn(e));
63
- return false;
30
+ if (nextProps.location === this.props.location) {
31
+ // `nextRouteHasLoaded` is false means there's a pending route transition.
32
+ // Don't update until it's done.
33
+ return nextState.nextRouteHasLoaded;
64
34
  }
65
- // There's a pending route transition. Don't update until it's done.
66
- if (!nextState.nextRouteHasLoaded) {
67
- return false;
68
- }
69
- // Route has loaded, we can update now.
70
- return true;
71
- }
72
- clearProgressBarTimeout() {
73
- if (this.progressBarTimeout) {
74
- clearTimeout(this.progressBarTimeout);
75
- this.progressBarTimeout = null;
76
- }
77
- }
78
- startProgressBar(delay) {
79
- this.clearProgressBarTimeout();
80
- this.progressBarTimeout = setTimeout(() => {
81
- clientLifecyclesDispatcher.onRouteUpdateDelayed({
82
- location: normalizeLocation(this.props.location),
83
- });
84
- nprogress.start();
85
- }, delay);
86
- }
87
- stopProgressBar() {
88
- this.clearProgressBarTimeout();
89
- nprogress.done();
35
+ // props.location being different means the router is trying to navigate to
36
+ // a new route. We will preload the new route.
37
+ const nextLocation = nextProps.location;
38
+ // Save the location first.
39
+ this.previousLocation = this.props.location;
40
+ this.setState({ nextRouteHasLoaded: false });
41
+ this.routeUpdateCleanupCb = dispatchLifecycleAction('onRouteUpdate', {
42
+ previousLocation: this.previousLocation,
43
+ location: nextLocation,
44
+ });
45
+ // Load data while the old screen remains. Force preload instead of using
46
+ // `window.docusaurus`, because we want to avoid loading screen even when
47
+ // user is on saveData
48
+ preload(nextLocation.pathname)
49
+ .then(() => {
50
+ this.routeUpdateCleanupCb?.();
51
+ this.setState({ nextRouteHasLoaded: true });
52
+ })
53
+ .catch((e) => console.warn(e));
54
+ return false;
90
55
  }
91
56
  render() {
92
57
  const { children, location } = this.props;
93
- return (<Route location={normalizeLocation(location)} render={() => children}/>);
58
+ // Use a controlled <Route> to trick all descendants into rendering the old
59
+ // location.
60
+ return (<ClientLifecyclesDispatcher previousLocation={this.previousLocation} location={location}>
61
+ <Route location={location} render={() => children}/>
62
+ </ClientLifecyclesDispatcher>);
94
63
  }
95
64
  }
96
- export default withRouter(PendingNavigation);
65
+ export default PendingNavigation;
@@ -8,7 +8,6 @@ import React from 'react';
8
8
  import ReactDOM from 'react-dom';
9
9
  import { BrowserRouter } from 'react-router-dom';
10
10
  import { HelmetProvider } from 'react-helmet-async';
11
- import routes from '@generated/routes';
12
11
  import ExecutionEnvironment from './exports/ExecutionEnvironment';
13
12
  import App from './App';
14
13
  import preload from './preload';
@@ -22,7 +21,7 @@ if (ExecutionEnvironment.canUseDOM) {
22
21
  // For development, there is no existing markup so we had to render it.
23
22
  // We also preload async component to avoid first-load loading screen.
24
23
  const renderMethod = process.env.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render;
25
- preload(routes, window.location.pathname).then(() => {
24
+ preload(window.location.pathname).then(() => {
26
25
  renderMethod(<HelmetProvider>
27
26
  <BrowserRouter>
28
27
  <App />
@@ -15,8 +15,8 @@ declare global {
15
15
  };
16
16
  }
17
17
  }
18
- declare const docusaurus: {
19
- prefetch: (routePath: string) => boolean;
20
- preload: (routePath: string) => boolean;
21
- };
22
- export default docusaurus;
18
+ declare const _default: Readonly<{
19
+ prefetch(routePath: string): false | Promise<void[]>;
20
+ preload(routePath: string): false | Promise<void[]>;
21
+ }>;
22
+ export default _default;