@docusaurus/core 3.7.0-canary-6201 → 3.7.0-canary-6205
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/lib/server/configValidation.js +16 -3
- package/lib/ssg/ssgEnv.d.ts +9 -0
- package/lib/ssg/ssgEnv.js +29 -0
- package/lib/ssg/ssgExecutor.js +102 -8
- package/lib/ssg/ssgGlobalResult.d.ts +12 -0
- package/lib/ssg/ssgGlobalResult.js +74 -0
- package/lib/ssg/ssgRenderer.d.ts +29 -0
- package/lib/ssg/{ssg.js → ssgRenderer.js} +35 -89
- 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/package.json +12 -11
- package/lib/ssg/ssg.d.ts +0 -21
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
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,7 @@ exports.DEFAULT_FASTER_CONFIG = {
|
|
|
29
31
|
lightningCssMinimizer: false,
|
|
30
32
|
mdxCrossCompilerCache: false,
|
|
31
33
|
rspackBundler: false,
|
|
34
|
+
ssgWorkerThreads: false,
|
|
32
35
|
};
|
|
33
36
|
// When using the "faster: true" shortcut
|
|
34
37
|
exports.DEFAULT_FASTER_CONFIG_TRUE = {
|
|
@@ -38,6 +41,7 @@ exports.DEFAULT_FASTER_CONFIG_TRUE = {
|
|
|
38
41
|
lightningCssMinimizer: true,
|
|
39
42
|
mdxCrossCompilerCache: true,
|
|
40
43
|
rspackBundler: true,
|
|
44
|
+
ssgWorkerThreads: true,
|
|
41
45
|
};
|
|
42
46
|
exports.DEFAULT_FUTURE_V4_CONFIG = {
|
|
43
47
|
removeLegacyPostBuildHeadAttribute: false,
|
|
@@ -163,6 +167,7 @@ const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives()
|
|
|
163
167
|
lightningCssMinimizer: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.lightningCssMinimizer),
|
|
164
168
|
mdxCrossCompilerCache: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache),
|
|
165
169
|
rspackBundler: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.rspackBundler),
|
|
170
|
+
ssgWorkerThreads: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.ssgWorkerThreads),
|
|
166
171
|
}), utils_validation_1.Joi.boolean()
|
|
167
172
|
.required()
|
|
168
173
|
.custom((bool) => bool ? exports.DEFAULT_FASTER_CONFIG_TRUE : exports.DEFAULT_FASTER_CONFIG))
|
|
@@ -325,6 +330,15 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
|
|
|
325
330
|
}).messages({
|
|
326
331
|
'docusaurus.configValidationWarning': 'Docusaurus config validation warning. Field {#label}: {#warningMessage}',
|
|
327
332
|
});
|
|
333
|
+
// Expressing this kind of logic in Joi is a pain
|
|
334
|
+
// We also want to decouple logic from Joi: easier to remove it later!
|
|
335
|
+
function ensureDocusaurusConfigConsistency(config) {
|
|
336
|
+
if (config.future.experimental_faster.ssgWorkerThreads &&
|
|
337
|
+
!config.future.v4.removeLegacyPostBuildHeadAttribute) {
|
|
338
|
+
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.
|
|
339
|
+
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: ${logger_1.default.code('{future: {v4: true}}')}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
328
342
|
// TODO move to @docusaurus/utils-validation
|
|
329
343
|
function validateConfig(config, siteConfigPath) {
|
|
330
344
|
const { error, warning, value } = exports.ConfigSchema.validate(config, {
|
|
@@ -346,7 +360,6 @@ function validateConfig(config, siteConfigPath) {
|
|
|
346
360
|
: formattedError;
|
|
347
361
|
throw new Error(formattedError);
|
|
348
362
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
363
|
+
ensureDocusaurusConfigConsistency(value);
|
|
364
|
+
return value;
|
|
352
365
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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;
|
|
@@ -0,0 +1,29 @@
|
|
|
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.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
|
package/lib/ssg/ssgExecutor.js
CHANGED
|
@@ -7,18 +7,109 @@
|
|
|
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('Generate static files (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
|
+
return inferNumberOfThreads({
|
|
54
|
+
pageCount: pathnames.length,
|
|
55
|
+
// TODO use "physical CPUs" instead of "logical CPUs" (like Tinypool does)
|
|
56
|
+
// See also https://github.com/tinylibs/tinypool/pull/108
|
|
57
|
+
cpuCount: os_1.default.cpus().length,
|
|
58
|
+
// These are "magic value" that we should refine based on user feedback
|
|
59
|
+
// Local tests show that it's not worth spawning new workers for few pages
|
|
60
|
+
minPagesPerCpu: 100,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const createPooledSSGExecutor = async ({ params, pathnames, }) => {
|
|
64
|
+
const numberOfThreads = getNumberOfThreads(pathnames);
|
|
65
|
+
// When the inferred or provided number of threads is just 1
|
|
66
|
+
// It's not worth it to use a thread pool
|
|
67
|
+
// This also allows users to disable the thread pool with the env variable
|
|
68
|
+
// DOCUSAURUS_SSG_WORKER_THREADS=1
|
|
69
|
+
if (numberOfThreads === 1) {
|
|
70
|
+
return createSimpleSSGExecutor({ params, pathnames });
|
|
71
|
+
}
|
|
72
|
+
const pool = await logger_1.PerfLogger.async(`Create SSG pool - ${logger_1.default.cyan(numberOfThreads)} threads`, async () => {
|
|
73
|
+
const Tinypool = await import('tinypool').then((m) => m.default);
|
|
74
|
+
const workerURL = (0, node_url_1.pathToFileURL)(path.resolve(__dirname, 'ssgWorkerThread.js'));
|
|
75
|
+
return new Tinypool({
|
|
76
|
+
filename: workerURL.pathname,
|
|
77
|
+
minThreads: numberOfThreads,
|
|
78
|
+
maxThreads: numberOfThreads,
|
|
79
|
+
concurrentTasksPerWorker: 1,
|
|
80
|
+
runtime: 'worker_threads',
|
|
81
|
+
isolateWorkers: false,
|
|
82
|
+
workerData: { params },
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
const pathnamesChunks = lodash_1.default.chunk(pathnames, ssgEnv_1.SSGWorkerThreadTaskSize);
|
|
86
|
+
// Tiny wrapper for type-safety
|
|
87
|
+
const submitTask = (task) => pool.run(task);
|
|
88
|
+
return {
|
|
89
|
+
run: async () => {
|
|
90
|
+
const results = await logger_1.PerfLogger.async(`Generate static files (${numberOfThreads} worker threads)`, async () => {
|
|
91
|
+
return Promise.all(pathnamesChunks.map((taskPathnames, taskIndex) => {
|
|
92
|
+
return submitTask({
|
|
93
|
+
id: taskIndex + 1,
|
|
94
|
+
pathnames: taskPathnames,
|
|
95
|
+
});
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
98
|
+
const allResults = results.flat();
|
|
99
|
+
return (0, ssgGlobalResult_1.createGlobalSSGResult)(allResults);
|
|
100
|
+
},
|
|
101
|
+
destroy: async () => {
|
|
102
|
+
await pool.destroy();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
16
106
|
async function executeSSG({ props, serverBundlePath, clientManifestPath, router, }) {
|
|
17
107
|
const params = await (0, ssgParams_1.createSSGParams)({
|
|
18
108
|
serverBundlePath,
|
|
19
109
|
clientManifestPath,
|
|
20
110
|
props,
|
|
21
111
|
});
|
|
112
|
+
// TODO doesn't look like the appropriate place for hash router entry
|
|
22
113
|
if (router === 'hash') {
|
|
23
114
|
logger_1.PerfLogger.start('Generate Hash Router entry point');
|
|
24
115
|
const content = await (0, ssgTemplate_1.renderHashRouterTemplate)({ params });
|
|
@@ -26,9 +117,12 @@ async function executeSSG({ props, serverBundlePath, clientManifestPath, router,
|
|
|
26
117
|
logger_1.PerfLogger.end('Generate Hash Router entry point');
|
|
27
118
|
return { collectedData: {} };
|
|
28
119
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
120
|
+
const createExecutor = props.siteConfig.future.experimental_faster
|
|
121
|
+
.ssgWorkerThreads
|
|
122
|
+
? createPooledSSGExecutor
|
|
123
|
+
: createSimpleSSGExecutor;
|
|
124
|
+
const executor = await createExecutor({ params, pathnames: props.routesPaths });
|
|
125
|
+
const result = await executor.run();
|
|
126
|
+
await executor.destroy();
|
|
127
|
+
return result;
|
|
34
128
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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 { AppRenderResult } from '../common';
|
|
9
|
+
export type SSGSuccess = {
|
|
10
|
+
success: true;
|
|
11
|
+
pathname: string;
|
|
12
|
+
result: {
|
|
13
|
+
collectedData: AppRenderResult['collectedData'];
|
|
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.
|
|
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 = {
|
|
@@ -52,44 +49,8 @@ async function loadAppRenderer({ serverBundlePath, }) {
|
|
|
52
49
|
shutdown,
|
|
53
50
|
};
|
|
54
51
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
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,55 +59,32 @@ 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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
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,
|
|
77
|
+
async function generateStaticFile({ pathname, appRenderer, params, htmlMinifier, ssgTemplate, }) {
|
|
140
78
|
try {
|
|
141
79
|
// This only renders the app HTML
|
|
142
|
-
const
|
|
80
|
+
const appRenderResult = await appRenderer.render({
|
|
143
81
|
pathname,
|
|
144
82
|
v4RemoveLegacyPostBuildHeadAttribute: params.v4RemoveLegacyPostBuildHeadAttribute,
|
|
145
83
|
});
|
|
146
84
|
// This renders the full page HTML, including head tags...
|
|
147
85
|
const fullPageHtml = (0, ssgTemplate_1.renderSSGTemplate)({
|
|
148
86
|
params,
|
|
149
|
-
result,
|
|
87
|
+
result: appRenderResult,
|
|
150
88
|
ssgTemplate,
|
|
151
89
|
});
|
|
152
90
|
const minifierResult = await htmlMinifier.minify(fullPageHtml);
|
|
@@ -156,18 +94,26 @@ async function generateStaticFile({ pathname, renderer, params, htmlMinifier, ss
|
|
|
156
94
|
params,
|
|
157
95
|
});
|
|
158
96
|
return {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
97
|
+
success: true,
|
|
98
|
+
pathname,
|
|
99
|
+
result: {
|
|
100
|
+
collectedData: appRenderResult.collectedData,
|
|
101
|
+
// As of today, only the html minifier can emit SSG warnings
|
|
102
|
+
warnings: minifierResult.warnings,
|
|
103
|
+
},
|
|
162
104
|
};
|
|
163
105
|
}
|
|
164
106
|
catch (errorUnknown) {
|
|
165
107
|
const error = errorUnknown;
|
|
166
108
|
const tips = getSSGErrorTips(error);
|
|
167
109
|
const message = logger_1.default.interpolate `Can't render static file for pathname path=${pathname}${tips ? `\n\n${tips}` : ''}`;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
pathname,
|
|
113
|
+
error: new Error(message, {
|
|
114
|
+
cause: error,
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
171
117
|
}
|
|
172
118
|
}
|
|
173
119
|
function getSSGErrorTips(error) {
|
package/lib/ssg/ssgUtils.d.ts
CHANGED
|
@@ -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;
|
package/lib/ssg/ssgUtils.js
CHANGED
|
@@ -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
|
+
}
|
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": "3.7.0-canary-
|
|
4
|
+
"version": "3.7.0-canary-6205",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"url": "https://github.com/facebook/docusaurus/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@docusaurus/babel": "3.7.0-canary-
|
|
37
|
-
"@docusaurus/bundler": "3.7.0-canary-
|
|
38
|
-
"@docusaurus/logger": "3.7.0-canary-
|
|
39
|
-
"@docusaurus/mdx-loader": "3.7.0-canary-
|
|
40
|
-
"@docusaurus/utils": "3.7.0-canary-
|
|
41
|
-
"@docusaurus/utils-common": "3.7.0-canary-
|
|
42
|
-
"@docusaurus/utils-validation": "3.7.0-canary-
|
|
36
|
+
"@docusaurus/babel": "3.7.0-canary-6205",
|
|
37
|
+
"@docusaurus/bundler": "3.7.0-canary-6205",
|
|
38
|
+
"@docusaurus/logger": "3.7.0-canary-6205",
|
|
39
|
+
"@docusaurus/mdx-loader": "3.7.0-canary-6205",
|
|
40
|
+
"@docusaurus/utils": "3.7.0-canary-6205",
|
|
41
|
+
"@docusaurus/utils-common": "3.7.0-canary-6205",
|
|
42
|
+
"@docusaurus/utils-validation": "3.7.0-canary-6205",
|
|
43
43
|
"boxen": "^6.2.1",
|
|
44
44
|
"chalk": "^4.1.2",
|
|
45
45
|
"chokidar": "^3.5.3",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"semver": "^7.5.4",
|
|
70
70
|
"serve-handler": "^6.1.6",
|
|
71
71
|
"shelljs": "^0.8.5",
|
|
72
|
+
"tinypool": "^1.0.2",
|
|
72
73
|
"tslib": "^2.6.0",
|
|
73
74
|
"update-notifier": "^6.0.2",
|
|
74
75
|
"webpack": "^5.95.0",
|
|
@@ -77,8 +78,8 @@
|
|
|
77
78
|
"webpack-merge": "^6.0.1"
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
|
-
"@docusaurus/module-type-aliases": "3.7.0-canary-
|
|
81
|
-
"@docusaurus/types": "3.7.0-canary-
|
|
81
|
+
"@docusaurus/module-type-aliases": "3.7.0-canary-6205",
|
|
82
|
+
"@docusaurus/types": "3.7.0-canary-6205",
|
|
82
83
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
83
84
|
"@types/detect-port": "^1.3.3",
|
|
84
85
|
"@types/react-dom": "^18.2.7",
|
|
@@ -98,5 +99,5 @@
|
|
|
98
99
|
"engines": {
|
|
99
100
|
"node": ">=18.0"
|
|
100
101
|
},
|
|
101
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "c43d056bff8c4540ee48669b04ae2dcdea4125e0"
|
|
102
103
|
}
|
package/lib/ssg/ssg.d.ts
DELETED
|
@@ -1,21 +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 { SSGParams } from './ssgParams';
|
|
8
|
-
import type { AppRenderer, SiteCollectedData } from '../common';
|
|
9
|
-
export declare function loadAppRenderer({ serverBundlePath, }: {
|
|
10
|
-
serverBundlePath: string;
|
|
11
|
-
}): Promise<AppRenderer>;
|
|
12
|
-
export declare function printSSGWarnings(results: {
|
|
13
|
-
pathname: string;
|
|
14
|
-
warnings: string[];
|
|
15
|
-
}[]): void;
|
|
16
|
-
export declare function generateStaticFiles({ pathnames, params, }: {
|
|
17
|
-
pathnames: string[];
|
|
18
|
-
params: SSGParams;
|
|
19
|
-
}): Promise<{
|
|
20
|
-
collectedData: SiteCollectedData;
|
|
21
|
-
}>;
|