@docusaurus/core 3.7.0 → 3.8.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 (47) hide show
  1. package/bin/beforeCli.mjs +3 -3
  2. package/lib/client/App.js +7 -4
  3. package/lib/client/serverEntry.js +10 -4
  4. package/lib/client/serverHelmetUtils.d.ts +11 -0
  5. package/lib/client/serverHelmetUtils.js +39 -0
  6. package/lib/client/theme-fallback/ThemeProvider/index.d.ts +9 -0
  7. package/lib/client/theme-fallback/ThemeProvider/index.js +17 -0
  8. package/lib/commands/build/buildLocale.js +33 -10
  9. package/lib/commands/clear.js +4 -3
  10. package/lib/commands/deploy.js +70 -28
  11. package/lib/commands/serve.js +2 -2
  12. package/lib/commands/start/start.js +7 -4
  13. package/lib/commands/start/utils.js +17 -3
  14. package/lib/commands/start/webpack.js +1 -1
  15. package/lib/commands/utils/clearPath.d.ts +10 -0
  16. package/lib/commands/utils/clearPath.js +21 -0
  17. package/lib/commands/utils/legacy/evalSourceMapMiddleware.d.ts +2 -0
  18. package/lib/commands/utils/legacy/evalSourceMapMiddleware.js +57 -0
  19. package/lib/commands/utils/openBrowser/openBrowser.d.ts +10 -0
  20. package/lib/commands/utils/openBrowser/openBrowser.js +124 -0
  21. package/lib/commands/utils/openBrowser/openChrome.applescript +94 -0
  22. package/lib/server/configValidation.d.ts +3 -1
  23. package/lib/server/configValidation.js +45 -4
  24. package/lib/ssg/ssgEnv.d.ts +10 -0
  25. package/lib/ssg/ssgEnv.js +38 -0
  26. package/lib/ssg/ssgExecutor.js +121 -8
  27. package/lib/ssg/ssgGlobalResult.d.ts +12 -0
  28. package/lib/ssg/ssgGlobalResult.js +74 -0
  29. package/lib/ssg/ssgParams.d.ts +1 -0
  30. package/lib/ssg/ssgParams.js +1 -0
  31. package/lib/ssg/ssgRenderer.d.ts +29 -0
  32. package/lib/ssg/{ssg.js → ssgRenderer.js} +55 -90
  33. package/lib/ssg/ssgTemplate.js +6 -7
  34. package/lib/ssg/ssgUtils.d.ts +0 -1
  35. package/lib/ssg/ssgUtils.js +0 -9
  36. package/lib/ssg/ssgWorkerInline.d.ts +12 -0
  37. package/lib/ssg/ssgWorkerInline.js +17 -0
  38. package/lib/ssg/ssgWorkerThread.d.ts +13 -0
  39. package/lib/ssg/ssgWorkerThread.js +36 -0
  40. package/lib/webpack/base.js +76 -28
  41. package/lib/webpack/client.js +0 -3
  42. package/lib/webpack/plugins/BundlerCPUProfilerPlugin.d.ts +12 -0
  43. package/lib/webpack/plugins/BundlerCPUProfilerPlugin.js +41 -0
  44. package/package.json +14 -14
  45. package/lib/ssg/ssg.d.ts +0 -21
  46. package/lib/webpack/plugins/CleanWebpackPlugin.d.ts +0 -59
  47. package/lib/webpack/plugins/CleanWebpackPlugin.js +0 -177
@@ -0,0 +1,29 @@
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 { SSGParams } from './ssgParams';
8
+ import type { PageCollectedData } from '../common';
9
+ export type SSGSuccess = {
10
+ success: true;
11
+ pathname: string;
12
+ result: {
13
+ collectedData: PageCollectedData;
14
+ warnings: string[];
15
+ };
16
+ };
17
+ export type SSGError = {
18
+ success: false;
19
+ pathname: string;
20
+ error: Error;
21
+ };
22
+ export type SSGResult = SSGSuccess | SSGError;
23
+ export type SSGRenderer = {
24
+ shutdown: () => Promise<void>;
25
+ renderPathnames: (pathnames: string[]) => Promise<SSGResult[]>;
26
+ };
27
+ export declare function loadSSGRenderer({ params, }: {
28
+ params: SSGParams;
29
+ }): Promise<SSGRenderer>;
@@ -6,13 +6,10 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.loadAppRenderer = loadAppRenderer;
10
- exports.printSSGWarnings = printSSGWarnings;
11
- exports.generateStaticFiles = generateStaticFiles;
9
+ exports.loadSSGRenderer = loadSSGRenderer;
12
10
  const tslib_1 = require("tslib");
13
11
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
14
12
  const path_1 = tslib_1.__importDefault(require("path"));
15
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
16
13
  // TODO eval is archived / unmaintained: https://github.com/pierrec/node-eval
17
14
  // We should internalize/modernize it
18
15
  const eval_1 = tslib_1.__importDefault(require("eval"));
@@ -20,11 +17,11 @@ const p_map_1 = tslib_1.__importDefault(require("p-map"));
20
17
  const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
21
18
  const bundler_1 = require("@docusaurus/bundler");
22
19
  const ssgTemplate_1 = require("./ssgTemplate");
20
+ const ssgEnv_1 = require("./ssgEnv");
23
21
  const ssgUtils_1 = require("./ssgUtils");
24
22
  const ssgNodeRequire_1 = require("./ssgNodeRequire");
25
23
  async function loadAppRenderer({ serverBundlePath, }) {
26
24
  const source = await logger_1.PerfLogger.async(`Load server bundle`, () => fs_extra_1.default.readFile(serverBundlePath));
27
- logger_1.PerfLogger.log(`Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`);
28
25
  const filename = path_1.default.basename(serverBundlePath);
29
26
  const ssgRequire = (0, ssgNodeRequire_1.createSSGRequire)(serverBundlePath);
30
27
  const globals = {
@@ -42,7 +39,7 @@ async function loadAppRenderer({ serverBundlePath, }) {
42
39
  /* scope: */ globals,
43
40
  /* includeGlobals: */ true));
44
41
  if (!serverEntry?.default || typeof serverEntry.default !== 'function') {
45
- throw new Error(`Server bundle export from "${filename}" must be a function that renders the Docusaurus React app.`);
42
+ throw new Error(`Docusaurus Bug: server bundle export from "${filename}" must be a function that renders the Docusaurus React app, not ${typeof serverEntry?.default}`);
46
43
  }
47
44
  async function shutdown() {
48
45
  ssgRequire.cleanup();
@@ -52,44 +49,8 @@ async function loadAppRenderer({ serverBundlePath, }) {
52
49
  shutdown,
53
50
  };
54
51
  }
55
- function printSSGWarnings(results) {
56
- // Escape hatch because SWC is quite aggressive to report errors
57
- // See https://github.com/facebook/docusaurus/pull/10554
58
- // See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
59
- if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
60
- return;
61
- }
62
- const ignoredWarnings = [
63
- // TODO React/Docusaurus emit NULL chars, and minifier detects it
64
- // see https://github.com/facebook/docusaurus/issues/9985
65
- 'Unexpected null character',
66
- ];
67
- const keepWarning = (warning) => {
68
- return !ignoredWarnings.some((iw) => warning.includes(iw));
69
- };
70
- const resultsWithWarnings = results
71
- .map((result) => {
72
- return {
73
- ...result,
74
- warnings: result.warnings.filter(keepWarning),
75
- };
76
- })
77
- .filter((result) => result.warnings.length > 0);
78
- if (resultsWithWarnings.length) {
79
- const message = `Docusaurus static site generation process emitted warnings for ${resultsWithWarnings.length} path${resultsWithWarnings.length ? 's' : ''}
80
- This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
81
- Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
82
-
83
- - ${resultsWithWarnings
84
- .map((result) => `${logger_1.default.path(result.pathname)}:
85
- - ${result.warnings.join('\n - ')}
86
- `)
87
- .join('\n- ')}`;
88
- logger_1.default.warn(message);
89
- }
90
- }
91
- async function generateStaticFiles({ pathnames, params, }) {
92
- const [renderer, htmlMinifier, ssgTemplate] = await Promise.all([
52
+ async function loadSSGRenderer({ params, }) {
53
+ const [appRenderer, htmlMinifier, ssgTemplate] = await Promise.all([
93
54
  logger_1.PerfLogger.async('Load App renderer', () => loadAppRenderer({
94
55
  serverBundlePath: params.serverBundlePath,
95
56
  })),
@@ -98,54 +59,49 @@ async function generateStaticFiles({ pathnames, params, }) {
98
59
  })),
99
60
  logger_1.PerfLogger.async('Compile SSG template', () => (0, ssgTemplate_1.compileSSGTemplate)(params.ssgTemplateContent)),
100
61
  ]);
101
- // Note that we catch all async errors on purpose
102
- // Docusaurus presents all the SSG errors to the user, not just the first one
103
- const results = await (0, p_map_1.default)(pathnames, async (pathname) => generateStaticFile({
104
- pathname,
105
- renderer,
106
- params,
107
- htmlMinifier,
108
- ssgTemplate,
109
- }).then((result) => ({
110
- pathname,
111
- result,
112
- error: null,
113
- warnings: result.warnings,
114
- }), (error) => ({
115
- pathname,
116
- result: null,
117
- error: error,
118
- warnings: [],
119
- })), { concurrency: ssgUtils_1.SSGConcurrency });
120
- await renderer.shutdown();
121
- printSSGWarnings(results);
122
- const [allSSGErrors, allSSGSuccesses] = lodash_1.default.partition(results, (result) => !!result.error);
123
- if (allSSGErrors.length > 0) {
124
- const message = `Docusaurus static site generation failed for ${allSSGErrors.length} path${allSSGErrors.length ? 's' : ''}:\n- ${allSSGErrors
125
- .map((ssgError) => logger_1.default.path(ssgError.pathname))
126
- .join('\n- ')}`;
127
- // Note logging this error properly require using inspect(error,{depth})
128
- // See https://github.com/nodejs/node/issues/51637
129
- throw new Error(message, {
130
- cause: new AggregateError(allSSGErrors.map((ssgError) => ssgError.error)),
131
- });
132
- }
133
- const collectedData = lodash_1.default.chain(allSSGSuccesses)
134
- .keyBy((success) => success.pathname)
135
- .mapValues((ssgSuccess) => ssgSuccess.result.collectedData)
136
- .value();
137
- return { collectedData };
62
+ return {
63
+ renderPathnames: (pathnames) => {
64
+ return (0, p_map_1.default)(pathnames, async (pathname) => generateStaticFile({
65
+ pathname,
66
+ appRenderer,
67
+ params,
68
+ htmlMinifier,
69
+ ssgTemplate,
70
+ }), { concurrency: ssgEnv_1.SSGConcurrency });
71
+ },
72
+ shutdown: async () => {
73
+ await appRenderer.shutdown();
74
+ },
75
+ };
138
76
  }
139
- async function generateStaticFile({ pathname, renderer, params, htmlMinifier, ssgTemplate, }) {
77
+ // We reduce the page collected data structure after the HTML file is written
78
+ // Some data (modules, metadata.internal) is only useful to create the HTML file
79
+ // It's not useful to aggregate that collected data in memory
80
+ // Keep this data structure as small as possible
81
+ // See https://github.com/facebook/docusaurus/pull/11162
82
+ function reduceCollectedData(pageCollectedData) {
83
+ // We re-create the object from scratch
84
+ // We absolutely want to avoid TS duck typing
85
+ return {
86
+ anchors: pageCollectedData.anchors,
87
+ metadata: {
88
+ public: pageCollectedData.metadata.public,
89
+ helmet: pageCollectedData.metadata.helmet,
90
+ },
91
+ links: pageCollectedData.links,
92
+ };
93
+ }
94
+ async function generateStaticFile({ pathname, appRenderer, params, htmlMinifier, ssgTemplate, }) {
140
95
  try {
141
96
  // This only renders the app HTML
142
- const result = await renderer.render({
97
+ const appRenderResult = await appRenderer.render({
143
98
  pathname,
99
+ v4RemoveLegacyPostBuildHeadAttribute: params.v4RemoveLegacyPostBuildHeadAttribute,
144
100
  });
145
101
  // This renders the full page HTML, including head tags...
146
102
  const fullPageHtml = (0, ssgTemplate_1.renderSSGTemplate)({
147
103
  params,
148
- result,
104
+ result: appRenderResult,
149
105
  ssgTemplate,
150
106
  });
151
107
  const minifierResult = await htmlMinifier.minify(fullPageHtml);
@@ -154,19 +110,28 @@ async function generateStaticFile({ pathname, renderer, params, htmlMinifier, ss
154
110
  content: minifierResult.code,
155
111
  params,
156
112
  });
113
+ const collectedData = reduceCollectedData(appRenderResult.collectedData);
157
114
  return {
158
- collectedData: result.collectedData,
159
- // As of today, only the html minifier can emit SSG warnings
160
- warnings: minifierResult.warnings,
115
+ success: true,
116
+ pathname,
117
+ result: {
118
+ collectedData,
119
+ // As of today, only the html minifier can emit SSG warnings
120
+ warnings: minifierResult.warnings,
121
+ },
161
122
  };
162
123
  }
163
124
  catch (errorUnknown) {
164
125
  const error = errorUnknown;
165
126
  const tips = getSSGErrorTips(error);
166
127
  const message = logger_1.default.interpolate `Can't render static file for pathname path=${pathname}${tips ? `\n\n${tips}` : ''}`;
167
- throw new Error(message, {
168
- cause: error,
169
- });
128
+ return {
129
+ success: false,
130
+ pathname,
131
+ error: new Error(message, {
132
+ cause: error,
133
+ }),
134
+ };
170
135
  }
171
136
  }
172
137
  function getSSGErrorTips(error) {
@@ -35,15 +35,14 @@ function getScriptsAndStylesheets({ modules, manifest, }) {
35
35
  }
36
36
  function renderSSGTemplate({ params, result, ssgTemplate, }) {
37
37
  const { baseUrl, headTags, preBodyTags, postBodyTags, manifest, noIndex, DOCUSAURUS_VERSION, } = params;
38
- const { html: appHtml, collectedData: { modules, helmet }, } = result;
38
+ const { html: appHtml, collectedData: { modules, metadata }, } = result;
39
39
  const { scripts, stylesheets } = getScriptsAndStylesheets({ manifest, modules });
40
- const htmlAttributes = helmet.htmlAttributes.toString();
41
- const bodyAttributes = helmet.bodyAttributes.toString();
40
+ const { htmlAttributes, bodyAttributes } = metadata.internal;
42
41
  const metaStrings = [
43
- helmet.title.toString(),
44
- helmet.meta.toString(),
45
- helmet.link.toString(),
46
- helmet.script.toString(),
42
+ metadata.internal.title,
43
+ metadata.internal.meta,
44
+ metadata.internal.link,
45
+ metadata.internal.script,
47
46
  ];
48
47
  const metaAttributes = metaStrings.filter(Boolean);
49
48
  const data = {
@@ -5,7 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { SSGParams } from './ssgParams';
8
- export declare const SSGConcurrency: number;
9
8
  export declare function generateHashRouterEntrypoint({ content, params, }: {
10
9
  content: string;
11
10
  params: SSGParams;
@@ -6,20 +6,11 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.SSGConcurrency = void 0;
10
9
  exports.generateHashRouterEntrypoint = generateHashRouterEntrypoint;
11
10
  exports.writeStaticFile = writeStaticFile;
12
11
  const tslib_1 = require("tslib");
13
12
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
14
13
  const path_1 = tslib_1.__importDefault(require("path"));
15
- // Secret way to set SSR plugin concurrency option
16
- // Waiting for feedback before documenting this officially?
17
- exports.SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
18
- ? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
19
- : // Not easy to define a reasonable option default
20
- // Will still be better than Infinity
21
- // See also https://github.com/sindresorhus/p-map/issues/24
22
- 32;
23
14
  function pathnameToFilename({ pathname, trailingSlash, }) {
24
15
  const outputFileName = pathname.replace(/^[/\\]/, ''); // Remove leading slashes for webpack-dev-server
25
16
  // Paths ending with .html are left untouched
@@ -0,0 +1,12 @@
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 SSGResult } from './ssgRenderer';
8
+ import type { SSGParams } from './ssgParams';
9
+ export declare function executeSSGInlineTask(arg: {
10
+ pathnames: string[];
11
+ params: SSGParams;
12
+ }): Promise<SSGResult[]>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.executeSSGInlineTask = executeSSGInlineTask;
10
+ const ssgRenderer_1 = require("./ssgRenderer");
11
+ // "inline" means in the current thread, not in a worker
12
+ async function executeSSGInlineTask(arg) {
13
+ const appRenderer = await (0, ssgRenderer_1.loadSSGRenderer)({ params: arg.params });
14
+ const ssgResults = appRenderer.renderPathnames(arg.pathnames);
15
+ await appRenderer.shutdown();
16
+ return ssgResults;
17
+ }
@@ -0,0 +1,13 @@
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 SSGResult } from './ssgRenderer.js';
8
+ export type SSGWorkerThreadTask = {
9
+ id: number;
10
+ pathnames: string[];
11
+ };
12
+ export default function executeSSGWorkerThreadTask(task: SSGWorkerThreadTask): Promise<SSGResult[]>;
13
+ export type ExecuteSSGWorkerThreadTask = typeof executeSSGWorkerThreadTask;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.default = executeSSGWorkerThreadTask;
10
+ const tslib_1 = require("tslib");
11
+ const node_worker_threads_1 = require("node:worker_threads");
12
+ const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
13
+ const ssgRenderer_js_1 = require("./ssgRenderer.js");
14
+ // eslint-disable-next-line no-underscore-dangle
15
+ const workerId = process?.__tinypool_state__?.workerId;
16
+ if (!workerId) {
17
+ throw new Error('SSG Worker Thread not executing in Tinypool context?');
18
+ }
19
+ const params = node_worker_threads_1.workerData?.[1]?.params;
20
+ if (!params) {
21
+ throw new Error(`SSG Worker Thread workerData params missing`);
22
+ }
23
+ const WorkerLogPrefix = `SSG Worker ${logger_1.default.name(workerId)}`;
24
+ // We only load once the SSG rendered (expensive), NOT once per worker task
25
+ // TODO check potential memory leak?
26
+ const appRendererPromise = logger_1.PerfLogger.async(`${WorkerLogPrefix} - Initialization`, () => (0, ssgRenderer_js_1.loadSSGRenderer)({
27
+ params,
28
+ }));
29
+ async function executeSSGWorkerThreadTask(task) {
30
+ const appRenderer = await appRendererPromise;
31
+ const ssgResults = await logger_1.PerfLogger.async(`${WorkerLogPrefix} - Task ${logger_1.default.name(task.id)} - Rendering ${logger_1.default.cyan(task.pathnames.length)} pathnames`, () => appRenderer.renderPathnames(task.pathnames));
32
+ // Afaik it's not needed to shutdown here,
33
+ // The thread pool destroys worker thread and releases worker thread memory
34
+ // await appRenderer.shutdown();
35
+ return ssgResults;
36
+ }
@@ -16,6 +16,7 @@ const babel_1 = require("@docusaurus/babel");
16
16
  const bundler_1 = require("@docusaurus/bundler");
17
17
  const utils_1 = require("@docusaurus/utils");
18
18
  const aliases_1 = require("./aliases");
19
+ const BundlerCPUProfilerPlugin_1 = require("./plugins/BundlerCPUProfilerPlugin");
19
20
  const CSS_REGEX = /\.css$/i;
20
21
  const CSS_MODULE_REGEX = /\.module\.css$/i;
21
22
  exports.clientDir = path_1.default.join(__dirname, '..', 'client');
@@ -58,43 +59,72 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
58
59
  const CSSExtractPlugin = await (0, bundler_1.getCSSExtractPlugin)({
59
60
  currentBundler: props.currentBundler,
60
61
  });
62
+ // Can we share the same cache across locales?
63
+ // Exploring that question at https://github.com/webpack/webpack/issues/13034
64
+ function getCacheName() {
65
+ return `${name}-${mode}-${props.i18n.currentLocale}`;
66
+ }
67
+ // When the version string changes, the cache is evicted
68
+ function getCacheVersion() {
69
+ // Because Webpack does not evict the cache on alias/swizzle changes,
70
+ // See https://github.com/webpack/webpack/issues/13627
71
+ const themeAliasesHash = (0, utils_1.md5Hash)(JSON.stringify(themeAliases));
72
+ return `${siteMetadata.docusaurusVersion}-${themeAliasesHash}`;
73
+ }
74
+ // When one of those modules/dependencies change (including transitive
75
+ // deps), cache is invalidated
76
+ function getCacheBuildDependencies() {
77
+ return [
78
+ __filename,
79
+ path_1.default.join(__dirname, isServer ? 'server.js' : 'client.js'),
80
+ // Docusaurus config changes can affect MDX/JSX compilation, so we'd
81
+ // rather evict the cache.
82
+ // See https://github.com/questdb/questdb.io/issues/493
83
+ siteConfigPath,
84
+ ];
85
+ }
61
86
  function getCache() {
87
+ // Use default: memory cache in dev, nothing in prod
88
+ // See https://rspack.dev/config/cache#cache
89
+ const disabledPersistentCacheValue = undefined;
90
+ if (process.env.DOCUSAURUS_NO_PERSISTENT_CACHE) {
91
+ return disabledPersistentCacheValue;
92
+ }
62
93
  if (props.currentBundler.name === 'rspack') {
63
- // TODO Rspack only supports memory cache (as of Sept 2024)
64
- // TODO re-enable file persistent cache one Rspack supports it
65
- // See also https://rspack.dev/config/cache#cache
66
- return undefined;
94
+ if (props.siteConfig.future.experimental_faster.rspackPersistentCache) {
95
+ // Use cache: true + experiments.cache.type: "persistent"
96
+ // See https://rspack.dev/config/experiments#persistent-cache
97
+ return true;
98
+ }
99
+ else {
100
+ return disabledPersistentCacheValue;
101
+ }
67
102
  }
68
103
  return {
69
104
  type: 'filesystem',
70
- // Can we share the same cache across locales?
71
- // Exploring that question at https://github.com/webpack/webpack/issues/13034
72
- // name: `${name}-${mode}`,
73
- name: `${name}-${mode}-${props.i18n.currentLocale}`,
74
- // When version string changes, cache is evicted
75
- version: [
76
- siteMetadata.docusaurusVersion,
77
- // Webpack does not evict the cache correctly on alias/swizzle change,
78
- // so we force eviction.
79
- // See https://github.com/webpack/webpack/issues/13627
80
- (0, utils_1.md5Hash)(JSON.stringify(themeAliases)),
81
- ].join('-'),
82
- // When one of those modules/dependencies change (including transitive
83
- // deps), cache is invalidated
105
+ name: getCacheName(),
106
+ version: getCacheVersion(),
84
107
  buildDependencies: {
85
- config: [
86
- __filename,
87
- path_1.default.join(__dirname, isServer ? 'server.js' : 'client.js'),
88
- // Docusaurus config changes can affect MDX/JSX compilation, so we'd
89
- // rather evict the cache.
90
- // See https://github.com/questdb/questdb.io/issues/493
91
- siteConfigPath,
92
- ],
108
+ config: getCacheBuildDependencies(),
93
109
  },
94
110
  };
95
111
  }
96
112
  function getExperiments() {
97
113
  if (props.currentBundler.name === 'rspack') {
114
+ const PersistentCacheAttributes = process.env
115
+ .DOCUSAURUS_NO_PERSISTENT_CACHE
116
+ ? {}
117
+ : {
118
+ cache: {
119
+ type: 'persistent',
120
+ // Rspack doesn't have "cache.name" like Webpack
121
+ // This is not ideal but work around is to merge name/version
122
+ // See https://github.com/web-infra-dev/rspack/pull/8920#issuecomment-2658938695
123
+ version: `${getCacheName()}-${getCacheVersion()}`,
124
+ buildDependencies: getCacheBuildDependencies(),
125
+ },
126
+ };
127
+ // TODO find a way to type this
98
128
  return {
99
129
  // This is mostly useful in dev
100
130
  // See https://rspack.dev/config/experiments#experimentsincremental
@@ -105,6 +135,10 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
105
135
  // See https://github.com/facebook/docusaurus/issues/10646
106
136
  // @ts-expect-error: Rspack-only, not available in Webpack typedefs
107
137
  incremental: !isProd && !process.env.DISABLE_RSPACK_INCREMENTAL,
138
+ // TODO re-enable later, there's an Rspack performance issue
139
+ // see https://github.com/facebook/docusaurus/pull/11178
140
+ parallelCodeSplitting: false,
141
+ ...PersistentCacheAttributes,
108
142
  };
109
143
  }
110
144
  return undefined;
@@ -162,7 +196,18 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
162
196
  modules: ['node_modules', path_1.default.join(siteDir, 'node_modules')],
163
197
  },
164
198
  optimization: {
165
- removeAvailableModules: false,
199
+ // The optimization.concatenateModules is expensive
200
+ // - On the server, it's not useful to run it at all
201
+ // - On the client, it leads to a ~3% JS assets total size decrease
202
+ // Let's keep it by default, but large sites may prefer faster builds
203
+ // See also https://github.com/facebook/docusaurus/pull/11176
204
+ concatenateModules: !isServer,
205
+ // The optimization.mergeDuplicateChunks is expensive
206
+ // - On the server, it's not useful to run it at all
207
+ // - On the client, we compared assets/js before/after and see 0 change
208
+ // `du -sk js-before js-after` => the JS assets have the exact same size
209
+ // See also https://github.com/facebook/docusaurus/pull/11176
210
+ mergeDuplicateChunks: false,
166
211
  // Only minimize client bundle in production because server bundle is only
167
212
  // used for static site generation
168
213
  minimize: minimizeEnabled,
@@ -201,6 +246,7 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
201
246
  module: {
202
247
  rules: [
203
248
  fileLoaderUtils.rules.images(),
249
+ fileLoaderUtils.rules.svgs(),
204
250
  fileLoaderUtils.rules.fonts(),
205
251
  fileLoaderUtils.rules.media(),
206
252
  fileLoaderUtils.rules.otherAssets(),
@@ -252,6 +298,8 @@ async function createBaseConfig({ props, isServer, minify, faster, configureWebp
252
298
  // for more reasoning
253
299
  ignoreOrder: true,
254
300
  }),
255
- ],
301
+ process.env.DOCUSAURUS_BUNDLER_CPU_PROFILE &&
302
+ new BundlerCPUProfilerPlugin_1.BundlerCPUProfilerPlugin(),
303
+ ].filter(Boolean),
256
304
  };
257
305
  }
@@ -17,7 +17,6 @@ const html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plug
17
17
  const bundler_1 = require("@docusaurus/bundler");
18
18
  const base_1 = require("./base");
19
19
  const ChunkAssetPlugin_1 = tslib_1.__importDefault(require("./plugins/ChunkAssetPlugin"));
20
- const CleanWebpackPlugin_1 = tslib_1.__importDefault(require("./plugins/CleanWebpackPlugin"));
21
20
  const ForceTerminatePlugin_1 = tslib_1.__importDefault(require("./plugins/ForceTerminatePlugin"));
22
21
  const StaticDirectoriesCopyPlugin_1 = require("./plugins/StaticDirectoriesCopyPlugin");
23
22
  async function createBaseClientConfig({ props, hydrate, minify, faster, configureWebpackUtils, }) {
@@ -108,8 +107,6 @@ async function createBuildClientConfig({ props, minify, faster, configureWebpack
108
107
  }), {
109
108
  plugins: [
110
109
  new ForceTerminatePlugin_1.default(),
111
- // Remove/clean build folders before building bundles.
112
- new CleanWebpackPlugin_1.default({ verbose: false }),
113
110
  // Visualize size of webpack output files with an interactive zoomable
114
111
  // tree map.
115
112
  bundleAnalyzer && new webpack_bundle_analyzer_1.BundleAnalyzerPlugin(),
@@ -0,0 +1,12 @@
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 { Compiler } from 'webpack';
8
+ export declare class BundlerCPUProfilerPlugin {
9
+ output: string;
10
+ constructor(output?: string);
11
+ apply(compiler: Compiler): void;
12
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.BundlerCPUProfilerPlugin = void 0;
10
+ const tslib_1 = require("tslib");
11
+ const node_inspector_1 = tslib_1.__importDefault(require("node:inspector"));
12
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
13
+ // Bundle CPU profiling plugin, contributed by the Rspack team
14
+ // Can be opened in https://www.speedscope.app/
15
+ // See also https://github.com/jerrykingxyz/docusaurus/pull/1
16
+ // See also https://github.com/facebook/docusaurus/pull/10985
17
+ class BundlerCPUProfilerPlugin {
18
+ constructor(output) {
19
+ this.output = output ?? './bundler-cpu-profile.json';
20
+ }
21
+ apply(compiler) {
22
+ const session = new node_inspector_1.default.Session();
23
+ session.connect();
24
+ session.post('Profiler.enable');
25
+ session.post('Profiler.start');
26
+ // In dev/watch mode, we restart the profiler before each compilation
27
+ compiler.hooks.watchRun.tapPromise(BundlerCPUProfilerPlugin.name, async () => {
28
+ session.post('Profiler.start');
29
+ });
30
+ compiler.hooks.done.tapPromise(BundlerCPUProfilerPlugin.name, async () => {
31
+ session.post('Profiler.stop', (error, param) => {
32
+ if (error) {
33
+ console.error('Failed to generate JS CPU profile:', error);
34
+ return;
35
+ }
36
+ fs_extra_1.default.writeFile(this.output, JSON.stringify(param.profile)).catch(console.error);
37
+ });
38
+ });
39
+ }
40
+ }
41
+ exports.BundlerCPUProfilerPlugin = BundlerCPUProfilerPlugin;