@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.
- package/bin/beforeCli.mjs +3 -3
- package/lib/client/App.js +7 -4
- package/lib/client/serverEntry.js +10 -4
- package/lib/client/serverHelmetUtils.d.ts +11 -0
- package/lib/client/serverHelmetUtils.js +39 -0
- package/lib/client/theme-fallback/ThemeProvider/index.d.ts +9 -0
- package/lib/client/theme-fallback/ThemeProvider/index.js +17 -0
- package/lib/commands/build/buildLocale.js +33 -10
- package/lib/commands/clear.js +4 -3
- package/lib/commands/deploy.js +70 -28
- package/lib/commands/serve.js +2 -2
- package/lib/commands/start/start.js +7 -4
- package/lib/commands/start/utils.js +17 -3
- package/lib/commands/start/webpack.js +1 -1
- package/lib/commands/utils/clearPath.d.ts +10 -0
- package/lib/commands/utils/clearPath.js +21 -0
- package/lib/commands/utils/legacy/evalSourceMapMiddleware.d.ts +2 -0
- package/lib/commands/utils/legacy/evalSourceMapMiddleware.js +57 -0
- package/lib/commands/utils/openBrowser/openBrowser.d.ts +10 -0
- package/lib/commands/utils/openBrowser/openBrowser.js +124 -0
- package/lib/commands/utils/openBrowser/openChrome.applescript +94 -0
- package/lib/server/configValidation.d.ts +3 -1
- package/lib/server/configValidation.js +45 -4
- package/lib/ssg/ssgEnv.d.ts +10 -0
- package/lib/ssg/ssgEnv.js +38 -0
- package/lib/ssg/ssgExecutor.js +121 -8
- package/lib/ssg/ssgGlobalResult.d.ts +12 -0
- package/lib/ssg/ssgGlobalResult.js +74 -0
- package/lib/ssg/ssgParams.d.ts +1 -0
- package/lib/ssg/ssgParams.js +1 -0
- package/lib/ssg/ssgRenderer.d.ts +29 -0
- package/lib/ssg/{ssg.js → ssgRenderer.js} +55 -90
- package/lib/ssg/ssgTemplate.js +6 -7
- package/lib/ssg/ssgUtils.d.ts +0 -1
- package/lib/ssg/ssgUtils.js +0 -9
- package/lib/ssg/ssgWorkerInline.d.ts +12 -0
- package/lib/ssg/ssgWorkerInline.js +17 -0
- package/lib/ssg/ssgWorkerThread.d.ts +13 -0
- package/lib/ssg/ssgWorkerThread.js +36 -0
- package/lib/webpack/base.js +76 -28
- package/lib/webpack/client.js +0 -3
- package/lib/webpack/plugins/BundlerCPUProfilerPlugin.d.ts +12 -0
- package/lib/webpack/plugins/BundlerCPUProfilerPlugin.js +41 -0
- package/package.json +14 -14
- package/lib/ssg/ssg.d.ts +0 -21
- package/lib/webpack/plugins/CleanWebpackPlugin.d.ts +0 -59
- package/lib/webpack/plugins/CleanWebpackPlugin.js +0 -177
|
@@ -0,0 +1,124 @@
|
|
|
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 = openBrowser;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
// This code was initially in CRA/react-dev-utils (deprecated in 2025)
|
|
12
|
+
// We copied and refactored it
|
|
13
|
+
// See https://github.com/facebook/docusaurus/pull/10956
|
|
14
|
+
// See https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/openBrowser.js
|
|
15
|
+
/* eslint-disable */
|
|
16
|
+
const child_process_1 = require("child_process");
|
|
17
|
+
const util_1 = require("util");
|
|
18
|
+
const open_1 = tslib_1.__importDefault(require("open"));
|
|
19
|
+
const logger_1 = require("@docusaurus/logger");
|
|
20
|
+
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
21
|
+
// Not sure if we need this, but let's keep a secret escape hatch
|
|
22
|
+
// CRA/react-dev-utils supported BROWSER/BROWSER_ARGS
|
|
23
|
+
const BrowserEnv = process.env.DOCUSAURUS_BROWSER;
|
|
24
|
+
const BrowserEnvArgs = process.env.DOCUSAURUS_BROWSER_ARGS
|
|
25
|
+
? process.env.DOCUSAURUS_BROWSER_ARGS.split(' ')
|
|
26
|
+
: [];
|
|
27
|
+
// If we're on OS X, the user hasn't specifically
|
|
28
|
+
// requested a different browser, we can try opening
|
|
29
|
+
// Chrome with AppleScript. This lets us reuse an
|
|
30
|
+
// existing tab when possible instead of creating a new one.
|
|
31
|
+
// Copied from https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/openBrowser.js
|
|
32
|
+
async function tryOpenWithAppleScript({ url, browser, }) {
|
|
33
|
+
const shouldTryOpenChromiumWithAppleScript = process.platform === 'darwin' &&
|
|
34
|
+
(typeof browser !== 'string' || browser === 'google chrome');
|
|
35
|
+
if (!shouldTryOpenChromiumWithAppleScript) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (shouldTryOpenChromiumWithAppleScript) {
|
|
39
|
+
async function getBrowsersToTry() {
|
|
40
|
+
// Will use the first open browser found from list
|
|
41
|
+
const supportedChromiumBrowsers = [
|
|
42
|
+
'Google Chrome Canary',
|
|
43
|
+
'Google Chrome Dev',
|
|
44
|
+
'Google Chrome Beta',
|
|
45
|
+
'Google Chrome',
|
|
46
|
+
'Microsoft Edge',
|
|
47
|
+
'Brave Browser',
|
|
48
|
+
'Vivaldi',
|
|
49
|
+
'Chromium',
|
|
50
|
+
];
|
|
51
|
+
// Among all the supported browsers, retrieves to stdout the active ones
|
|
52
|
+
const command = `ps cax -o command | grep -E "^(${supportedChromiumBrowsers.join('|')})$"`;
|
|
53
|
+
const result = await execPromise(command);
|
|
54
|
+
const activeBrowsers = result.stdout.toString().trim().split('\n');
|
|
55
|
+
// This preserves the initial browser order
|
|
56
|
+
// We open Google Chrome Canary in priority over Google Chrome
|
|
57
|
+
return supportedChromiumBrowsers.filter((b) => activeBrowsers.includes(b));
|
|
58
|
+
}
|
|
59
|
+
async function tryBrowser(browserName) {
|
|
60
|
+
try {
|
|
61
|
+
// This command runs the openChrome.applescript (copied from CRA)
|
|
62
|
+
const command = `osascript openChrome.applescript "${encodeURI(url)}" "${browserName}"`;
|
|
63
|
+
await execPromise(command, {
|
|
64
|
+
cwd: __dirname,
|
|
65
|
+
});
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(`Failed to open browser ${browserName} with AppleScript`, err);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const browsers = await logger_1.PerfLogger.async('getBrowsersToTry', () => getBrowsersToTry());
|
|
74
|
+
for (let browser of browsers) {
|
|
75
|
+
const success = await logger_1.PerfLogger.async(browser, () => tryBrowser(browser));
|
|
76
|
+
if (success) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function toOpenApp(params) {
|
|
84
|
+
if (!params.browser) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
// Handles "cross-platform" shortcuts like "chrome", "firefox", "edge"
|
|
88
|
+
if (open_1.default.apps[params.browser]) {
|
|
89
|
+
return {
|
|
90
|
+
name: open_1.default.apps[params.browser],
|
|
91
|
+
arguments: params.browserArgs,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Fallback to platform-specific app name
|
|
95
|
+
return {
|
|
96
|
+
name: params.browser,
|
|
97
|
+
arguments: params.browserArgs,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function startBrowserProcess(params) {
|
|
101
|
+
if (await logger_1.PerfLogger.async('tryOpenWithAppleScript', () => tryOpenWithAppleScript(params))) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await (0, open_1.default)(params.url, {
|
|
106
|
+
app: toOpenApp(params),
|
|
107
|
+
wait: false,
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Returns true if it opened a browser
|
|
117
|
+
*/
|
|
118
|
+
async function openBrowser(url) {
|
|
119
|
+
return startBrowserProcess({
|
|
120
|
+
url,
|
|
121
|
+
browser: BrowserEnv,
|
|
122
|
+
browserArgs: BrowserEnvArgs,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
(*
|
|
2
|
+
Copyright (c) 2015-present, Facebook, Inc.
|
|
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
|
+
|
|
8
|
+
property targetTab: null
|
|
9
|
+
property targetTabIndex: -1
|
|
10
|
+
property targetWindow: null
|
|
11
|
+
property theProgram: "Google Chrome"
|
|
12
|
+
|
|
13
|
+
on run argv
|
|
14
|
+
set theURL to item 1 of argv
|
|
15
|
+
|
|
16
|
+
-- Allow requested program to be optional,
|
|
17
|
+
-- default to Google Chrome
|
|
18
|
+
if (count of argv) > 1 then
|
|
19
|
+
set theProgram to item 2 of argv
|
|
20
|
+
end if
|
|
21
|
+
|
|
22
|
+
using terms from application "Google Chrome"
|
|
23
|
+
tell application theProgram
|
|
24
|
+
|
|
25
|
+
if (count every window) = 0 then
|
|
26
|
+
make new window
|
|
27
|
+
end if
|
|
28
|
+
|
|
29
|
+
-- 1: Looking for tab running debugger
|
|
30
|
+
-- then, Reload debugging tab if found
|
|
31
|
+
-- then return
|
|
32
|
+
set found to my lookupTabWithUrl(theURL)
|
|
33
|
+
if found then
|
|
34
|
+
set targetWindow's active tab index to targetTabIndex
|
|
35
|
+
tell targetTab to reload
|
|
36
|
+
tell targetWindow to activate
|
|
37
|
+
set index of targetWindow to 1
|
|
38
|
+
return
|
|
39
|
+
end if
|
|
40
|
+
|
|
41
|
+
-- 2: Looking for Empty tab
|
|
42
|
+
-- In case debugging tab was not found
|
|
43
|
+
-- We try to find an empty tab instead
|
|
44
|
+
set found to my lookupTabWithUrl("chrome://newtab/")
|
|
45
|
+
if found then
|
|
46
|
+
set targetWindow's active tab index to targetTabIndex
|
|
47
|
+
set URL of targetTab to theURL
|
|
48
|
+
tell targetWindow to activate
|
|
49
|
+
return
|
|
50
|
+
end if
|
|
51
|
+
|
|
52
|
+
-- 3: Create new tab
|
|
53
|
+
-- both debugging and empty tab were not found
|
|
54
|
+
-- make a new tab with url
|
|
55
|
+
tell window 1
|
|
56
|
+
activate
|
|
57
|
+
make new tab with properties {URL:theURL}
|
|
58
|
+
end tell
|
|
59
|
+
end tell
|
|
60
|
+
end using terms from
|
|
61
|
+
end run
|
|
62
|
+
|
|
63
|
+
-- Function:
|
|
64
|
+
-- Lookup tab with given url
|
|
65
|
+
-- if found, store tab, index, and window in properties
|
|
66
|
+
-- (properties were declared on top of file)
|
|
67
|
+
on lookupTabWithUrl(lookupUrl)
|
|
68
|
+
using terms from application "Google Chrome"
|
|
69
|
+
tell application theProgram
|
|
70
|
+
-- Find a tab with the given url
|
|
71
|
+
set found to false
|
|
72
|
+
set theTabIndex to -1
|
|
73
|
+
repeat with theWindow in every window
|
|
74
|
+
set theTabIndex to 0
|
|
75
|
+
repeat with theTab in every tab of theWindow
|
|
76
|
+
set theTabIndex to theTabIndex + 1
|
|
77
|
+
if (theTab's URL as string) contains lookupUrl then
|
|
78
|
+
-- assign tab, tab index, and window to properties
|
|
79
|
+
set targetTab to theTab
|
|
80
|
+
set targetTabIndex to theTabIndex
|
|
81
|
+
set targetWindow to theWindow
|
|
82
|
+
set found to true
|
|
83
|
+
exit repeat
|
|
84
|
+
end if
|
|
85
|
+
end repeat
|
|
86
|
+
|
|
87
|
+
if found then
|
|
88
|
+
exit repeat
|
|
89
|
+
end if
|
|
90
|
+
end repeat
|
|
91
|
+
end tell
|
|
92
|
+
end using terms from
|
|
93
|
+
return found
|
|
94
|
+
end lookupTabWithUrl
|
|
@@ -5,12 +5,14 @@
|
|
|
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 { FasterConfig, FutureConfig, StorageConfig } from '@docusaurus/types/src/config';
|
|
8
|
+
import type { FasterConfig, FutureConfig, FutureV4Config, StorageConfig } from '@docusaurus/types/src/config';
|
|
9
9
|
import type { DocusaurusConfig, I18nConfig, MarkdownConfig } from '@docusaurus/types';
|
|
10
10
|
export declare const DEFAULT_I18N_CONFIG: I18nConfig;
|
|
11
11
|
export declare const DEFAULT_STORAGE_CONFIG: StorageConfig;
|
|
12
12
|
export declare const DEFAULT_FASTER_CONFIG: FasterConfig;
|
|
13
13
|
export declare const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig;
|
|
14
|
+
export declare const DEFAULT_FUTURE_V4_CONFIG: FutureV4Config;
|
|
15
|
+
export declare const DEFAULT_FUTURE_V4_CONFIG_TRUE: FutureV4Config;
|
|
14
16
|
export declare const DEFAULT_FUTURE_CONFIG: FutureConfig;
|
|
15
17
|
export declare const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig;
|
|
16
18
|
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'>;
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_FUTURE_CONFIG = exports.DEFAULT_FASTER_CONFIG_TRUE = exports.DEFAULT_FASTER_CONFIG = exports.DEFAULT_STORAGE_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0;
|
|
9
|
+
exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_FUTURE_CONFIG = exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = exports.DEFAULT_FUTURE_V4_CONFIG = exports.DEFAULT_FASTER_CONFIG_TRUE = exports.DEFAULT_FASTER_CONFIG = exports.DEFAULT_STORAGE_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0;
|
|
10
10
|
exports.validateConfig = validateConfig;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
11
12
|
const utils_1 = require("@docusaurus/utils");
|
|
12
13
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
13
14
|
const utils_common_1 = require("@docusaurus/utils-common");
|
|
15
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
14
16
|
const DEFAULT_I18N_LOCALE = 'en';
|
|
15
17
|
exports.DEFAULT_I18N_CONFIG = {
|
|
16
18
|
defaultLocale: DEFAULT_I18N_LOCALE,
|
|
@@ -29,6 +31,8 @@ exports.DEFAULT_FASTER_CONFIG = {
|
|
|
29
31
|
lightningCssMinimizer: false,
|
|
30
32
|
mdxCrossCompilerCache: false,
|
|
31
33
|
rspackBundler: false,
|
|
34
|
+
rspackPersistentCache: false,
|
|
35
|
+
ssgWorkerThreads: false,
|
|
32
36
|
};
|
|
33
37
|
// When using the "faster: true" shortcut
|
|
34
38
|
exports.DEFAULT_FASTER_CONFIG_TRUE = {
|
|
@@ -38,8 +42,20 @@ exports.DEFAULT_FASTER_CONFIG_TRUE = {
|
|
|
38
42
|
lightningCssMinimizer: true,
|
|
39
43
|
mdxCrossCompilerCache: true,
|
|
40
44
|
rspackBundler: true,
|
|
45
|
+
rspackPersistentCache: true,
|
|
46
|
+
ssgWorkerThreads: true,
|
|
47
|
+
};
|
|
48
|
+
exports.DEFAULT_FUTURE_V4_CONFIG = {
|
|
49
|
+
removeLegacyPostBuildHeadAttribute: false,
|
|
50
|
+
useCssCascadeLayers: false,
|
|
51
|
+
};
|
|
52
|
+
// When using the "v4: true" shortcut
|
|
53
|
+
exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = {
|
|
54
|
+
removeLegacyPostBuildHeadAttribute: true,
|
|
55
|
+
useCssCascadeLayers: true,
|
|
41
56
|
};
|
|
42
57
|
exports.DEFAULT_FUTURE_CONFIG = {
|
|
58
|
+
v4: exports.DEFAULT_FUTURE_V4_CONFIG,
|
|
43
59
|
experimental_faster: exports.DEFAULT_FASTER_CONFIG,
|
|
44
60
|
experimental_storage: exports.DEFAULT_STORAGE_CONFIG,
|
|
45
61
|
experimental_router: 'browser',
|
|
@@ -155,11 +171,22 @@ const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives()
|
|
|
155
171
|
lightningCssMinimizer: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.lightningCssMinimizer),
|
|
156
172
|
mdxCrossCompilerCache: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache),
|
|
157
173
|
rspackBundler: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.rspackBundler),
|
|
174
|
+
rspackPersistentCache: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.rspackPersistentCache),
|
|
175
|
+
ssgWorkerThreads: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.ssgWorkerThreads),
|
|
158
176
|
}), utils_validation_1.Joi.boolean()
|
|
159
177
|
.required()
|
|
160
178
|
.custom((bool) => bool ? exports.DEFAULT_FASTER_CONFIG_TRUE : exports.DEFAULT_FASTER_CONFIG))
|
|
161
179
|
.optional()
|
|
162
180
|
.default(exports.DEFAULT_FASTER_CONFIG);
|
|
181
|
+
const FUTURE_V4_SCHEMA = utils_validation_1.Joi.alternatives()
|
|
182
|
+
.try(utils_validation_1.Joi.object({
|
|
183
|
+
removeLegacyPostBuildHeadAttribute: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute),
|
|
184
|
+
useCssCascadeLayers: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.useCssCascadeLayers),
|
|
185
|
+
}), utils_validation_1.Joi.boolean()
|
|
186
|
+
.required()
|
|
187
|
+
.custom((bool) => bool ? exports.DEFAULT_FUTURE_V4_CONFIG_TRUE : exports.DEFAULT_FUTURE_V4_CONFIG))
|
|
188
|
+
.optional()
|
|
189
|
+
.default(exports.DEFAULT_FUTURE_V4_CONFIG);
|
|
163
190
|
const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
164
191
|
type: utils_validation_1.Joi.string()
|
|
165
192
|
.equal('localStorage', 'sessionStorage')
|
|
@@ -171,6 +198,7 @@ const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
|
171
198
|
.optional()
|
|
172
199
|
.default(exports.DEFAULT_STORAGE_CONFIG);
|
|
173
200
|
const FUTURE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
201
|
+
v4: FUTURE_V4_SCHEMA,
|
|
174
202
|
experimental_faster: FASTER_CONFIG_SCHEMA,
|
|
175
203
|
experimental_storage: STORAGE_CONFIG_SCHEMA,
|
|
176
204
|
experimental_router: utils_validation_1.Joi.string()
|
|
@@ -308,6 +336,20 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
|
|
|
308
336
|
}).messages({
|
|
309
337
|
'docusaurus.configValidationWarning': 'Docusaurus config validation warning. Field {#label}: {#warningMessage}',
|
|
310
338
|
});
|
|
339
|
+
// Expressing this kind of logic in Joi is a pain
|
|
340
|
+
// We also want to decouple logic from Joi: easier to remove it later!
|
|
341
|
+
function ensureDocusaurusConfigConsistency(config) {
|
|
342
|
+
if (config.future.experimental_faster.ssgWorkerThreads &&
|
|
343
|
+
!config.future.v4.removeLegacyPostBuildHeadAttribute) {
|
|
344
|
+
throw new Error(`Docusaurus config ${logger_1.default.code('future.experimental_faster.ssgWorkerThreads')} requires the future flag ${logger_1.default.code('future.v4.removeLegacyPostBuildHeadAttribute')} to be turned on.
|
|
345
|
+
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: ${logger_1.default.code('{future: {v4: true}}')}
|
|
346
|
+
All the v4 future flags are documented here: https://docusaurus.io/docs/api/docusaurus-config#future`);
|
|
347
|
+
}
|
|
348
|
+
if (config.future.experimental_faster.rspackPersistentCache &&
|
|
349
|
+
!config.future.experimental_faster.rspackBundler) {
|
|
350
|
+
throw new Error(`Docusaurus config flag ${logger_1.default.code('future.experimental_faster.rspackPersistentCache')} requires the flag ${logger_1.default.code('future.experimental_faster.rspackBundler')} to be turned on.`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
311
353
|
// TODO move to @docusaurus/utils-validation
|
|
312
354
|
function validateConfig(config, siteConfigPath) {
|
|
313
355
|
const { error, warning, value } = exports.ConfigSchema.validate(config, {
|
|
@@ -329,7 +371,6 @@ function validateConfig(config, siteConfigPath) {
|
|
|
329
371
|
: formattedError;
|
|
330
372
|
throw new Error(formattedError);
|
|
331
373
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
374
|
+
ensureDocusaurusConfigConsistency(value);
|
|
375
|
+
return value;
|
|
335
376
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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 declare const SSGConcurrency: number;
|
|
8
|
+
export declare const SSGWorkerThreadCount: number | undefined;
|
|
9
|
+
export declare const SSGWorkerThreadTaskSize: number;
|
|
10
|
+
export declare const SSGWorkerThreadRecyclerMaxMemory: number | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
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.SSGWorkerThreadRecyclerMaxMemory = exports.SSGWorkerThreadTaskSize = exports.SSGWorkerThreadCount = exports.SSGConcurrency = void 0;
|
|
10
|
+
// Secret way to set SSR plugin async concurrency option
|
|
11
|
+
// Waiting for feedback before documenting this officially?
|
|
12
|
+
// TODO Docusaurus v4, rename SSR => SSG
|
|
13
|
+
exports.SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
|
|
14
|
+
? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
|
|
15
|
+
: // Not easy to define a reasonable option default
|
|
16
|
+
// Will still be better than Infinity
|
|
17
|
+
// See also https://github.com/sindresorhus/p-map/issues/24
|
|
18
|
+
32;
|
|
19
|
+
// Secret way to set SSR plugin async concurrency option
|
|
20
|
+
// Waiting for feedback before documenting this officially?
|
|
21
|
+
exports.SSGWorkerThreadCount = process.env
|
|
22
|
+
.DOCUSAURUS_SSG_WORKER_THREAD_COUNT
|
|
23
|
+
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_COUNT, 10)
|
|
24
|
+
: undefined;
|
|
25
|
+
// Number of pathnames to SSG per worker task
|
|
26
|
+
exports.SSGWorkerThreadTaskSize = process.env
|
|
27
|
+
.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE
|
|
28
|
+
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE, 10)
|
|
29
|
+
: 10; // TODO need fine-tuning
|
|
30
|
+
// Controls worker thread recycling behavior (maxMemoryLimitBeforeRecycle)
|
|
31
|
+
// See https://github.com/facebook/docusaurus/pull/11166
|
|
32
|
+
// See https://github.com/facebook/docusaurus/issues/11161
|
|
33
|
+
exports.SSGWorkerThreadRecyclerMaxMemory = process.env
|
|
34
|
+
.DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY
|
|
35
|
+
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_RECYCLER_MAX_MEMORY, 10)
|
|
36
|
+
: // 1 GB is a quite reasonable max value
|
|
37
|
+
// It should work well even for large sites
|
|
38
|
+
1000000000;
|
package/lib/ssg/ssgExecutor.js
CHANGED
|
@@ -7,18 +7,128 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.executeSSG = executeSSG;
|
|
10
|
-
const
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const path = tslib_1.__importStar(require("path"));
|
|
12
|
+
const node_url_1 = require("node:url");
|
|
13
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
14
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
15
|
+
const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
|
|
11
16
|
const ssgParams_1 = require("./ssgParams");
|
|
12
|
-
const ssg_1 = require("./ssg");
|
|
13
17
|
const ssgTemplate_1 = require("./ssgTemplate");
|
|
18
|
+
const ssgEnv_1 = require("./ssgEnv");
|
|
14
19
|
const ssgUtils_1 = require("./ssgUtils");
|
|
15
|
-
|
|
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
|
+
// Sensible default that gives decent performances
|
|
39
|
+
// It's hard to have a perfect formula that works for all hosts
|
|
40
|
+
// Each thread has some creation overhead
|
|
41
|
+
// Having 1 thread per cpu doesn't necessarily improve perf on small sites
|
|
42
|
+
// We want to ensure that we don't create a worker thread for less than x paths
|
|
43
|
+
function inferNumberOfThreads({ pageCount, cpuCount, minPagesPerCpu, }) {
|
|
44
|
+
// Calculate "ideal" amount of threads based on the number of pages to render
|
|
45
|
+
const threadsByWorkload = Math.ceil(pageCount / minPagesPerCpu);
|
|
46
|
+
// Use the smallest of threadsByWorkload or cpuCount, ensuring min=1 thread
|
|
47
|
+
return Math.max(1, Math.min(threadsByWorkload, cpuCount));
|
|
48
|
+
}
|
|
49
|
+
function getNumberOfThreads(pathnames) {
|
|
50
|
+
if (typeof ssgEnv_1.SSGWorkerThreadCount !== 'undefined') {
|
|
51
|
+
return ssgEnv_1.SSGWorkerThreadCount;
|
|
52
|
+
}
|
|
53
|
+
// See also https://github.com/tinylibs/tinypool/pull/108
|
|
54
|
+
const cpuCount =
|
|
55
|
+
// TODO Docusaurus v4: bump node, availableParallelism() now always exists
|
|
56
|
+
typeof os_1.default.availableParallelism === 'function'
|
|
57
|
+
? os_1.default.availableParallelism()
|
|
58
|
+
: os_1.default.cpus().length;
|
|
59
|
+
return inferNumberOfThreads({
|
|
60
|
+
pageCount: pathnames.length,
|
|
61
|
+
cpuCount,
|
|
62
|
+
// These are "magic value" that we should refine based on user feedback
|
|
63
|
+
// Local tests show that it's not worth spawning new workers for few pages
|
|
64
|
+
minPagesPerCpu: 100,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const createPooledSSGExecutor = async ({ params, pathnames, }) => {
|
|
68
|
+
const numberOfThreads = getNumberOfThreads(pathnames);
|
|
69
|
+
// When the inferred or provided number of threads is just 1
|
|
70
|
+
// It's not worth it to use a thread pool
|
|
71
|
+
// This also allows users to disable the thread pool with the env variable
|
|
72
|
+
// DOCUSAURUS_SSG_WORKER_THREADS=1
|
|
73
|
+
if (numberOfThreads === 1) {
|
|
74
|
+
return createSimpleSSGExecutor({ params, pathnames });
|
|
75
|
+
}
|
|
76
|
+
const pool = await logger_1.PerfLogger.async(`Create SSG thread pool - ${logger_1.default.cyan(numberOfThreads)} threads`, async () => {
|
|
77
|
+
const Tinypool = await import('tinypool').then((m) => m.default);
|
|
78
|
+
const workerURL = (0, node_url_1.pathToFileURL)(path.resolve(__dirname, 'ssgWorkerThread.js'));
|
|
79
|
+
return new Tinypool({
|
|
80
|
+
filename: workerURL.pathname,
|
|
81
|
+
minThreads: numberOfThreads,
|
|
82
|
+
maxThreads: numberOfThreads,
|
|
83
|
+
concurrentTasksPerWorker: 1,
|
|
84
|
+
runtime: 'worker_threads',
|
|
85
|
+
isolateWorkers: false,
|
|
86
|
+
workerData: { params },
|
|
87
|
+
// WORKER MEMORY MANAGEMENT
|
|
88
|
+
// Allows containing SSG memory leaks with a thread recycling workaround
|
|
89
|
+
// See https://github.com/facebook/docusaurus/pull/11166
|
|
90
|
+
// See https://github.com/facebook/docusaurus/issues/11161
|
|
91
|
+
maxMemoryLimitBeforeRecycle: ssgEnv_1.SSGWorkerThreadRecyclerMaxMemory,
|
|
92
|
+
resourceLimits: {
|
|
93
|
+
// For some reason I can't figure out how to limit memory on a worker
|
|
94
|
+
// See https://x.com/sebastienlorber/status/1920781195618513143
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const pathnamesChunks = lodash_1.default.chunk(pathnames, ssgEnv_1.SSGWorkerThreadTaskSize);
|
|
99
|
+
// Tiny wrapper for type-safety
|
|
100
|
+
const submitTask = async (task) => {
|
|
101
|
+
const result = await pool.run(task);
|
|
102
|
+
// Note, we don't use PerfLogger.async() because all tasks are submitted
|
|
103
|
+
// immediately at once and queued, while results are received progressively
|
|
104
|
+
logger_1.PerfLogger.log(`Result for task ${logger_1.default.name(task.id)}`);
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
run: async () => {
|
|
109
|
+
const results = await logger_1.PerfLogger.async(`Thread pool`, async () => {
|
|
110
|
+
return Promise.all(pathnamesChunks.map((taskPathnames, taskIndex) => {
|
|
111
|
+
return submitTask({
|
|
112
|
+
id: taskIndex + 1,
|
|
113
|
+
pathnames: taskPathnames,
|
|
114
|
+
});
|
|
115
|
+
}));
|
|
116
|
+
});
|
|
117
|
+
const allResults = results.flat();
|
|
118
|
+
return (0, ssgGlobalResult_1.createGlobalSSGResult)(allResults);
|
|
119
|
+
},
|
|
120
|
+
destroy: async () => {
|
|
121
|
+
await pool.destroy();
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
};
|
|
16
125
|
async function executeSSG({ props, serverBundlePath, clientManifestPath, router, }) {
|
|
17
126
|
const params = await (0, ssgParams_1.createSSGParams)({
|
|
18
127
|
serverBundlePath,
|
|
19
128
|
clientManifestPath,
|
|
20
129
|
props,
|
|
21
130
|
});
|
|
131
|
+
// TODO doesn't look like the appropriate place for hash router entry
|
|
22
132
|
if (router === 'hash') {
|
|
23
133
|
logger_1.PerfLogger.start('Generate Hash Router entry point');
|
|
24
134
|
const content = await (0, ssgTemplate_1.renderHashRouterTemplate)({ params });
|
|
@@ -26,9 +136,12 @@ async function executeSSG({ props, serverBundlePath, clientManifestPath, router,
|
|
|
26
136
|
logger_1.PerfLogger.end('Generate Hash Router entry point');
|
|
27
137
|
return { collectedData: {} };
|
|
28
138
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
139
|
+
const createExecutor = props.siteConfig.future.experimental_faster
|
|
140
|
+
.ssgWorkerThreads
|
|
141
|
+
? createPooledSSGExecutor
|
|
142
|
+
: createSimpleSSGExecutor;
|
|
143
|
+
const executor = await createExecutor({ params, pathnames: props.routesPaths });
|
|
144
|
+
const result = await executor.run();
|
|
145
|
+
await executor.destroy();
|
|
146
|
+
return result;
|
|
34
147
|
}
|
|
@@ -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 { SiteCollectedData } from '../common';
|
|
9
|
+
export type SSGGlobalResult = {
|
|
10
|
+
collectedData: SiteCollectedData;
|
|
11
|
+
};
|
|
12
|
+
export declare function createGlobalSSGResult(ssgResults: SSGResult[]): Promise<SSGGlobalResult>;
|
|
@@ -0,0 +1,74 @@
|
|
|
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.createGlobalSSGResult = createGlobalSSGResult;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
12
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
13
|
+
function printSSGWarnings(results) {
|
|
14
|
+
// Escape hatch because SWC is quite aggressive to report errors
|
|
15
|
+
// See https://github.com/facebook/docusaurus/pull/10554
|
|
16
|
+
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
|
|
17
|
+
if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const ignoredWarnings = [
|
|
21
|
+
// TODO Docusaurus v4: remove with React 19 upgrade
|
|
22
|
+
// React 18 emit NULL chars, and minifier detects it
|
|
23
|
+
// see https://github.com/facebook/docusaurus/issues/9985
|
|
24
|
+
'Unexpected null character',
|
|
25
|
+
];
|
|
26
|
+
const keepWarning = (warning) => {
|
|
27
|
+
return !ignoredWarnings.some((iw) => warning.includes(iw));
|
|
28
|
+
};
|
|
29
|
+
const resultsWithWarnings = results
|
|
30
|
+
.map((success) => {
|
|
31
|
+
return {
|
|
32
|
+
...success,
|
|
33
|
+
warnings: success.result.warnings.filter(keepWarning),
|
|
34
|
+
};
|
|
35
|
+
})
|
|
36
|
+
.filter((result) => result.warnings.length > 0);
|
|
37
|
+
if (resultsWithWarnings.length) {
|
|
38
|
+
const message = `Docusaurus static site generation process emitted warnings for ${resultsWithWarnings.length} path${resultsWithWarnings.length ? 's' : ''}
|
|
39
|
+
This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
|
|
40
|
+
Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
|
|
41
|
+
|
|
42
|
+
- ${resultsWithWarnings
|
|
43
|
+
.map((result) => `${logger_1.default.path(result.pathname)}:
|
|
44
|
+
- ${result.warnings.join('\n - ')}
|
|
45
|
+
`)
|
|
46
|
+
.join('\n- ')}`;
|
|
47
|
+
logger_1.default.warn(message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function throwSSGError(ssgErrors) {
|
|
51
|
+
const message = `Docusaurus static site generation failed for ${ssgErrors.length} path${ssgErrors.length ? 's' : ''}:\n- ${ssgErrors
|
|
52
|
+
.map((ssgError) => logger_1.default.path(ssgError.pathname))
|
|
53
|
+
.join('\n- ')}`;
|
|
54
|
+
// Note logging this error properly require using inspect(error,{depth})
|
|
55
|
+
// See https://github.com/nodejs/node/issues/51637
|
|
56
|
+
throw new Error(message, {
|
|
57
|
+
cause: new AggregateError(ssgErrors.map((ssgError) => ssgError.error)),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function createGlobalSSGResult(ssgResults) {
|
|
61
|
+
const [ssgSuccesses, ssgErrors] = lodash_1.default.partition(ssgResults, (result) => result.success);
|
|
62
|
+
// For now, only success results emit warnings
|
|
63
|
+
// For errors, we throw without warnings
|
|
64
|
+
printSSGWarnings(ssgSuccesses);
|
|
65
|
+
if (ssgErrors.length > 0) {
|
|
66
|
+
throwSSGError(ssgErrors);
|
|
67
|
+
}
|
|
68
|
+
// If we only have SSG successes, we can consolidate those in a single result
|
|
69
|
+
const collectedData = lodash_1.default.chain(ssgSuccesses)
|
|
70
|
+
.keyBy((success) => success.pathname)
|
|
71
|
+
.mapValues((ssgSuccess) => ssgSuccess.result.collectedData)
|
|
72
|
+
.value();
|
|
73
|
+
return { collectedData };
|
|
74
|
+
}
|
package/lib/ssg/ssgParams.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type SSGParams = {
|
|
|
20
20
|
htmlMinifierType: HtmlMinifierType;
|
|
21
21
|
serverBundlePath: string;
|
|
22
22
|
ssgTemplateContent: string;
|
|
23
|
+
v4RemoveLegacyPostBuildHeadAttribute: boolean;
|
|
23
24
|
};
|
|
24
25
|
export declare function createSSGParams({ props, serverBundlePath, clientManifestPath, }: {
|
|
25
26
|
props: Props;
|
package/lib/ssg/ssgParams.js
CHANGED
|
@@ -30,6 +30,7 @@ async function createSSGParams({ props, serverBundlePath, clientManifestPath, })
|
|
|
30
30
|
.swcHtmlMinimizer
|
|
31
31
|
? 'swc'
|
|
32
32
|
: 'terser',
|
|
33
|
+
v4RemoveLegacyPostBuildHeadAttribute: props.siteConfig.future.v4.removeLegacyPostBuildHeadAttribute,
|
|
33
34
|
};
|
|
34
35
|
// Useless but ensures that SSG params remain serializable
|
|
35
36
|
return structuredClone(params);
|