@docusaurus/core 4.0.0-canary-6841 → 4.0.0-canary-6843

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.
@@ -53,7 +53,6 @@ exports.DEFAULT_FASTER_CONFIG = {
53
53
  mdxCrossCompilerCache: false,
54
54
  rspackBundler: false,
55
55
  rspackPersistentCache: false,
56
- ssgWorkerThreads: false,
57
56
  gitEagerVcs: false,
58
57
  };
59
58
  // When using the "faster: true" shortcut
@@ -65,7 +64,6 @@ exports.DEFAULT_FASTER_CONFIG_TRUE = {
65
64
  mdxCrossCompilerCache: true,
66
65
  rspackBundler: true,
67
66
  rspackPersistentCache: true,
68
- ssgWorkerThreads: true,
69
67
  gitEagerVcs: true,
70
68
  };
71
69
  exports.DEFAULT_FUTURE_V4_CONFIG = {
@@ -217,7 +215,6 @@ const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives()
217
215
  mdxCrossCompilerCache: utils_validation_1.Joi.boolean(),
218
216
  rspackBundler: utils_validation_1.Joi.boolean(),
219
217
  rspackPersistentCache: utils_validation_1.Joi.boolean(),
220
- ssgWorkerThreads: utils_validation_1.Joi.boolean(),
221
218
  gitEagerVcs: utils_validation_1.Joi.boolean(),
222
219
  }), utils_validation_1.Joi.boolean()
223
220
  .required()
@@ -18,23 +18,6 @@ const ssgTemplate_1 = require("./ssgTemplate");
18
18
  const ssgEnv_1 = require("./ssgEnv");
19
19
  const ssgUtils_1 = require("./ssgUtils");
20
20
  const ssgGlobalResult_1 = require("./ssgGlobalResult");
21
- const ssgWorkerInline_1 = require("./ssgWorkerInline");
22
- const createSimpleSSGExecutor = async ({ params, pathnames, }) => {
23
- return {
24
- run: () => {
25
- return logger_1.PerfLogger.async('SSG (current thread)', async () => {
26
- const ssgResults = await (0, ssgWorkerInline_1.executeSSGInlineTask)({
27
- pathnames,
28
- params,
29
- });
30
- return (0, ssgGlobalResult_1.createGlobalSSGResult)(ssgResults);
31
- });
32
- },
33
- destroy: async () => {
34
- // nothing to do
35
- },
36
- };
37
- };
38
21
  // Sensible default that gives decent performances
39
22
  // It's hard to have a perfect formula that works for all hosts
40
23
  // Each thread has some creation overhead
@@ -75,15 +58,10 @@ function getWorkerColorEnv() {
75
58
  }
76
59
  return {};
77
60
  }
78
- const createPooledSSGExecutor = async ({ params, pathnames, }) => {
61
+ // SSG always runs in worker threads, even for small sites
62
+ // This isolates the server bundle execution from the main Docusaurus process
63
+ async function createSSGExecutor({ params, pathnames, }) {
79
64
  const numberOfThreads = getNumberOfThreads(pathnames);
80
- // When the inferred or provided number of threads is just 1
81
- // It's not worth it to use a thread pool
82
- // This also allows users to disable the thread pool with the env variable
83
- // DOCUSAURUS_SSG_WORKER_THREADS=1
84
- if (numberOfThreads === 1) {
85
- return createSimpleSSGExecutor({ params, pathnames });
86
- }
87
65
  const pool = await logger_1.PerfLogger.async(`Create SSG thread pool - ${logger_1.default.cyan(numberOfThreads)} threads`, async () => {
88
66
  const Tinypool = await import('tinypool').then((m) => m.default);
89
67
  const workerURL = (0, node_url_1.pathToFileURL)(path.resolve(__dirname, 'ssgWorkerThread.js'));
@@ -138,7 +116,7 @@ const createPooledSSGExecutor = async ({ params, pathnames, }) => {
138
116
  await pool.destroy();
139
117
  },
140
118
  };
141
- };
119
+ }
142
120
  async function executeSSG({ props, serverBundlePath, clientManifestPath, router, }) {
143
121
  const params = await (0, ssgParams_1.createSSGParams)({
144
122
  serverBundlePath,
@@ -153,10 +131,10 @@ async function executeSSG({ props, serverBundlePath, clientManifestPath, router,
153
131
  logger_1.PerfLogger.end('Generate Hash Router entry point');
154
132
  return { collectedData: {} };
155
133
  }
156
- const createExecutor = props.siteConfig.future.faster.ssgWorkerThreads
157
- ? createPooledSSGExecutor
158
- : createSimpleSSGExecutor;
159
- const executor = await createExecutor({ params, pathnames: props.routesPaths });
134
+ const executor = await createSSGExecutor({
135
+ params,
136
+ pathnames: props.routesPaths,
137
+ });
160
138
  const result = await executor.run();
161
139
  await executor.destroy();
162
140
  return result;
@@ -21,7 +21,6 @@ export type SSGError = {
21
21
  };
22
22
  export type SSGResult = SSGSuccess | SSGError;
23
23
  export type SSGRenderer = {
24
- shutdown: () => Promise<void>;
25
24
  renderPathnames: (pathnames: string[]) => Promise<SSGResult[]>;
26
25
  };
27
26
  export declare function loadSSGRenderer({ params, }: {
@@ -8,46 +8,33 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.loadSSGRenderer = loadSSGRenderer;
10
10
  const tslib_1 = require("tslib");
11
- const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
11
+ const module_1 = require("module");
12
12
  const path_1 = tslib_1.__importDefault(require("path"));
13
- // TODO eval is archived / unmaintained: https://github.com/pierrec/node-eval
14
- // We should internalize/modernize it
15
- const eval_1 = tslib_1.__importDefault(require("eval"));
13
+ const vm_1 = require("vm");
14
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
16
15
  const p_map_1 = tslib_1.__importDefault(require("p-map"));
17
16
  const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
18
17
  const bundler_1 = require("@docusaurus/bundler");
19
18
  const ssgTemplate_1 = require("./ssgTemplate");
20
19
  const ssgEnv_1 = require("./ssgEnv");
21
20
  const ssgUtils_1 = require("./ssgUtils");
22
- const ssgNodeRequire_1 = require("./ssgNodeRequire");
21
+ // Evaluates the server bundle as a CommonJS module, like Node.js would do
22
+ // We don't use an isolated vm context on purpose: SSG runs in worker threads
23
+ // that already isolate it from the main Docusaurus process
24
+ // Running in the current context is faster and avoids reading all the globals
25
+ function evaluateServerBundle({ source, serverBundlePath, }) {
26
+ const moduleFunction = (0, vm_1.compileFunction)(source, ['exports', 'require', 'module', '__filename', '__dirname'], { filename: serverBundlePath });
27
+ const module = { exports: {} };
28
+ moduleFunction.call(module.exports, module.exports, (0, module_1.createRequire)(serverBundlePath), module, serverBundlePath, path_1.default.dirname(serverBundlePath));
29
+ return module.exports;
30
+ }
23
31
  async function loadAppRenderer({ serverBundlePath, }) {
24
- const source = await logger_1.PerfLogger.async(`Load server bundle`, () => fs_extra_1.default.readFile(serverBundlePath));
25
- const filename = path_1.default.basename(serverBundlePath);
26
- const ssgRequire = (0, ssgNodeRequire_1.createSSGRequire)(serverBundlePath);
27
- const globals = {
28
- // When using "new URL('file.js', import.meta.url)", Webpack will emit
29
- // __filename, and this plugin will throw. not sure the __filename value
30
- // has any importance for this plugin, just using an empty string to
31
- // avoid the error. See https://github.com/facebook/docusaurus/issues/4922
32
- __filename: '',
33
- // This uses module.createRequire() instead of very old "require-like" lib
34
- // See also: https://github.com/pierrec/node-eval/issues/33
35
- require: ssgRequire.require,
36
- };
37
- const serverEntry = await logger_1.PerfLogger.async(`Evaluate server bundle`, () => (0, eval_1.default)(source,
38
- /* filename: */ filename,
39
- /* scope: */ globals,
40
- /* includeGlobals: */ true));
32
+ const source = await logger_1.PerfLogger.async(`Load server bundle`, () => fs_extra_1.default.readFile(serverBundlePath, 'utf8'));
33
+ const serverEntry = await logger_1.PerfLogger.async(`Evaluate server bundle`, () => evaluateServerBundle({ source, serverBundlePath }));
41
34
  if (!serverEntry?.default || typeof serverEntry.default !== 'function') {
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}`);
35
+ throw new Error(`Docusaurus Bug: server bundle export from "${serverBundlePath}" must be a function that renders the Docusaurus React app, not ${typeof serverEntry?.default}`);
43
36
  }
44
- async function shutdown() {
45
- ssgRequire.cleanup();
46
- }
47
- return {
48
- render: serverEntry.default,
49
- shutdown,
50
- };
37
+ return { render: serverEntry.default };
51
38
  }
52
39
  async function loadSSGRenderer({ params, }) {
53
40
  const [appRenderer, htmlMinifier, ssgTemplate] = await Promise.all([
@@ -69,9 +56,6 @@ async function loadSSGRenderer({ params, }) {
69
56
  ssgTemplate,
70
57
  }), { concurrency: ssgEnv_1.SSGConcurrency });
71
58
  },
72
- shutdown: async () => {
73
- await appRenderer.shutdown();
74
- },
75
59
  };
76
60
  }
77
61
  // We reduce the page collected data structure after the HTML file is written
@@ -20,6 +20,12 @@ if (!params) {
20
20
  throw new Error(`SSG Worker Thread workerData params missing`);
21
21
  }
22
22
  const WorkerLogPrefix = `SSG Worker ${logger_1.default.name(workerId)}`;
23
+ // The server bundle runs in the worker thread global context
24
+ // Web Storage should not be available during SSG, like it was before Node 25
25
+ // Reading it emits a warning on Node 25+ unless --localstorage-file is used
26
+ // Deleting it only affects this worker thread, not the main thread
27
+ Reflect.deleteProperty(globalThis, 'localStorage');
28
+ Reflect.deleteProperty(globalThis, 'sessionStorage');
23
29
  // We only load once the SSG rendered (expensive), NOT once per worker task
24
30
  // TODO check potential memory leak?
25
31
  const appRendererPromise = logger_1.PerfLogger.async(`${WorkerLogPrefix} - Initialization`, () => (0, ssgRenderer_js_1.loadSSGRenderer)({
@@ -28,8 +34,5 @@ const appRendererPromise = logger_1.PerfLogger.async(`${WorkerLogPrefix} - Initi
28
34
  async function executeSSGWorkerThreadTask(task) {
29
35
  const appRenderer = await appRendererPromise;
30
36
  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));
31
- // Afaik it's not needed to shutdown here,
32
- // The thread pool destroys worker thread and releases worker thread memory
33
- // await appRenderer.shutdown();
34
37
  return ssgResults;
35
38
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@docusaurus/core",
3
3
  "description": "Easy to Maintain Open Source Documentation Websites",
4
- "version": "4.0.0-canary-6841",
4
+ "version": "4.0.0-canary-6843",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -33,14 +33,14 @@
33
33
  "url": "https://github.com/facebook/docusaurus/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@docusaurus/babel": "4.0.0-canary-6841",
37
- "@docusaurus/bundler": "4.0.0-canary-6841",
38
- "@docusaurus/glob": "4.0.0-canary-6841",
39
- "@docusaurus/logger": "4.0.0-canary-6841",
40
- "@docusaurus/mdx-loader": "4.0.0-canary-6841",
41
- "@docusaurus/utils": "4.0.0-canary-6841",
42
- "@docusaurus/utils-common": "4.0.0-canary-6841",
43
- "@docusaurus/utils-validation": "4.0.0-canary-6841",
36
+ "@docusaurus/babel": "4.0.0-canary-6843",
37
+ "@docusaurus/bundler": "4.0.0-canary-6843",
38
+ "@docusaurus/glob": "4.0.0-canary-6843",
39
+ "@docusaurus/logger": "4.0.0-canary-6843",
40
+ "@docusaurus/mdx-loader": "4.0.0-canary-6843",
41
+ "@docusaurus/utils": "4.0.0-canary-6843",
42
+ "@docusaurus/utils-common": "4.0.0-canary-6843",
43
+ "@docusaurus/utils-validation": "4.0.0-canary-6843",
44
44
  "boxen": "^8.0.1",
45
45
  "cli-table3": "^0.6.5",
46
46
  "combine-promises": "^1.2.0",
@@ -49,7 +49,6 @@
49
49
  "detect-port": "^2.1.0",
50
50
  "escape-html": "^1.0.3",
51
51
  "eta": "^4.6.0",
52
- "eval": "^0.1.8",
53
52
  "execa": "^10.0.1",
54
53
  "fs-extra": "^11.4.0",
55
54
  "html-tags": "^5.1.0",
@@ -76,8 +75,8 @@
76
75
  "webpack-merge": "^6.0.1"
77
76
  },
78
77
  "devDependencies": {
79
- "@docusaurus/module-type-aliases": "4.0.0-canary-6841",
80
- "@docusaurus/types": "4.0.0-canary-6841",
78
+ "@docusaurus/module-type-aliases": "4.0.0-canary-6843",
79
+ "@docusaurus/types": "4.0.0-canary-6843",
81
80
  "@total-typescript/shoehorn": "^0.1.2",
82
81
  "@types/escape-html": "^1.0.4",
83
82
  "@types/history": "^4.7.11",
@@ -92,7 +91,7 @@
92
91
  "utility-types": "^3.11.0"
93
92
  },
94
93
  "peerDependencies": {
95
- "@docusaurus/faster": "4.0.0-canary-6841",
94
+ "@docusaurus/faster": "4.0.0-canary-6843",
96
95
  "@mdx-js/react": "^3.1.1",
97
96
  "react": "^19.3.0",
98
97
  "react-dom": "^19.3.0"
@@ -105,5 +104,5 @@
105
104
  "engines": {
106
105
  "node": ">=24.21"
107
106
  },
108
- "gitHead": "6c97ad62ff58f7eda88392a0e33fcd982d826567"
107
+ "gitHead": "a81a4292fc3069231904bcd6dad29ffea849e3de"
109
108
  }
@@ -1,11 +0,0 @@
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
- export type SSGNodeRequire = {
8
- require: NodeJS.Require;
9
- cleanup: () => void;
10
- };
11
- export declare function createSSGRequire(serverBundlePath: string): SSGNodeRequire;
@@ -1,40 +0,0 @@
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.createSSGRequire = createSSGRequire;
10
- const module_1 = require("module");
11
- // The eval/vm.Script used for running the server bundle need a require() impl
12
- // This impl has to be relative to the server bundler path
13
- // This enables the server bundle to resolve relative paths such as:
14
- // - require('./assets/js/some-chunk.123456.js')
15
- //
16
- // Unfortunately, Node.js vm.Script doesn't isolate memory / require.cache
17
- // This means that if we build multiple Docusaurus localized sites in a row
18
- // The Node.js require cache will keep growing and retain in memory the JS
19
- // assets of the former SSG builds
20
- // We have to clean up the node require cache manually to avoid leaking memory!
21
- // See also https://x.com/sebastienlorber/status/1848399310116831702
22
- function createSSGRequire(serverBundlePath) {
23
- const realRequire = (0, module_1.createRequire)(serverBundlePath);
24
- const allRequiredIds = [];
25
- const ssgRequireFunction = (id) => {
26
- const module = realRequire(id);
27
- allRequiredIds.push(id);
28
- return module;
29
- };
30
- const cleanup = () => {
31
- allRequiredIds.forEach((id) => {
32
- delete realRequire.cache[realRequire.resolve(id)];
33
- });
34
- };
35
- ssgRequireFunction.resolve = realRequire.resolve;
36
- ssgRequireFunction.cache = realRequire.cache;
37
- ssgRequireFunction.extensions = realRequire.extensions;
38
- ssgRequireFunction.main = realRequire.main;
39
- return { require: ssgRequireFunction, cleanup };
40
- }
@@ -1,12 +0,0 @@
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[]>;
@@ -1,17 +0,0 @@
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
- }