@docusaurus/core 3.7.0-canary-6200 → 3.7.0-canary-6204
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/client/serverEntry.js +10 -4
- package/lib/client/serverHelmetUtils.d.ts +11 -0
- package/lib/client/serverHelmetUtils.js +39 -0
- package/lib/commands/build/buildLocale.js +5 -1
- package/lib/server/configValidation.d.ts +3 -1
- package/lib/server/configValidation.js +34 -4
- 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/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} +36 -89
- 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/package.json +12 -11
- package/lib/ssg/ssg.d.ts +0 -21
|
@@ -12,7 +12,8 @@ import { renderToHtml } from './renderToHtml';
|
|
|
12
12
|
import preload from './preload';
|
|
13
13
|
import App from './App';
|
|
14
14
|
import { createStatefulBrokenLinks, BrokenLinksProvider, } from './BrokenLinksContext';
|
|
15
|
-
|
|
15
|
+
import { toPageCollectedMetadata } from './serverHelmetUtils';
|
|
16
|
+
const render = async ({ pathname, v4RemoveLegacyPostBuildHeadAttribute, }) => {
|
|
16
17
|
await preload(pathname);
|
|
17
18
|
const modules = new Set();
|
|
18
19
|
const routerContext = {};
|
|
@@ -30,10 +31,15 @@ const render = async ({ pathname }) => {
|
|
|
30
31
|
</HelmetProvider>
|
|
31
32
|
</Loadable.Capture>);
|
|
32
33
|
const html = await renderToHtml(app);
|
|
34
|
+
const { helmet } = helmetContext;
|
|
35
|
+
const metadata = toPageCollectedMetadata({ helmet });
|
|
36
|
+
// TODO Docusaurus v4 remove with deprecated postBuild({head}) API
|
|
37
|
+
// the returned collectedData must be serializable to run in workers
|
|
38
|
+
if (v4RemoveLegacyPostBuildHeadAttribute) {
|
|
39
|
+
metadata.helmet = null;
|
|
40
|
+
}
|
|
33
41
|
const collectedData = {
|
|
34
|
-
|
|
35
|
-
// this makes it impossible to run SSG in a worker thread
|
|
36
|
-
helmet: helmetContext.helmet,
|
|
42
|
+
metadata,
|
|
37
43
|
anchors: statefulBrokenLinks.getCollectedAnchors(),
|
|
38
44
|
links: statefulBrokenLinks.getCollectedLinks(),
|
|
39
45
|
modules: Array.from(modules),
|
|
@@ -0,0 +1,11 @@
|
|
|
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 { PageCollectedMetadata } from '../common';
|
|
8
|
+
import type { HelmetServerState } from 'react-helmet-async';
|
|
9
|
+
export declare function toPageCollectedMetadata({ helmet, }: {
|
|
10
|
+
helmet: HelmetServerState;
|
|
11
|
+
}): PageCollectedMetadata;
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
function getBuildMetaTags(helmet) {
|
|
8
|
+
// @ts-expect-error: see https://github.com/staylor/react-helmet-async/pull/167
|
|
9
|
+
const metaElements = helmet.meta.toComponent() ?? [];
|
|
10
|
+
return metaElements.map((el) => el.props);
|
|
11
|
+
}
|
|
12
|
+
function isNoIndexTag(tag) {
|
|
13
|
+
if (!tag.name || !tag.content) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return (
|
|
17
|
+
// meta name is not case-sensitive
|
|
18
|
+
tag.name.toLowerCase() === 'robots' &&
|
|
19
|
+
// Robots directives are not case-sensitive
|
|
20
|
+
tag.content.toLowerCase().includes('noindex'));
|
|
21
|
+
}
|
|
22
|
+
export function toPageCollectedMetadata({ helmet, }) {
|
|
23
|
+
const tags = getBuildMetaTags(helmet);
|
|
24
|
+
const noIndex = tags.some(isNoIndexTag);
|
|
25
|
+
return {
|
|
26
|
+
helmet, // TODO Docusaurus v4 remove
|
|
27
|
+
public: {
|
|
28
|
+
noIndex,
|
|
29
|
+
},
|
|
30
|
+
internal: {
|
|
31
|
+
htmlAttributes: helmet.htmlAttributes.toString(),
|
|
32
|
+
bodyAttributes: helmet.bodyAttributes.toString(),
|
|
33
|
+
title: helmet.title.toString(),
|
|
34
|
+
meta: helmet.meta.toString(),
|
|
35
|
+
link: helmet.link.toString(),
|
|
36
|
+
script: helmet.script.toString(),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -71,7 +71,10 @@ async function buildLocale({ siteDir, locale, cliOptions, }) {
|
|
|
71
71
|
logger_1.default.success `Generated static files in path=${path_1.default.relative(process.cwd(), outDir)}.`;
|
|
72
72
|
}
|
|
73
73
|
async function executePluginsPostBuild({ plugins, props, collectedData, }) {
|
|
74
|
-
const head =
|
|
74
|
+
const head = props.siteConfig.future.v4.removeLegacyPostBuildHeadAttribute
|
|
75
|
+
? {}
|
|
76
|
+
: lodash_1.default.mapValues(collectedData, (d) => d.metadata.helmet);
|
|
77
|
+
const routesBuildMetadata = lodash_1.default.mapValues(collectedData, (d) => d.metadata.public);
|
|
75
78
|
await Promise.all(plugins.map(async (plugin) => {
|
|
76
79
|
if (!plugin.postBuild) {
|
|
77
80
|
return;
|
|
@@ -79,6 +82,7 @@ async function executePluginsPostBuild({ plugins, props, collectedData, }) {
|
|
|
79
82
|
await plugin.postBuild({
|
|
80
83
|
...props,
|
|
81
84
|
head,
|
|
85
|
+
routesBuildMetadata,
|
|
82
86
|
content: plugin.content,
|
|
83
87
|
});
|
|
84
88
|
}));
|
|
@@ -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,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,8 +41,17 @@ exports.DEFAULT_FASTER_CONFIG_TRUE = {
|
|
|
38
41
|
lightningCssMinimizer: true,
|
|
39
42
|
mdxCrossCompilerCache: true,
|
|
40
43
|
rspackBundler: true,
|
|
44
|
+
ssgWorkerThreads: true,
|
|
45
|
+
};
|
|
46
|
+
exports.DEFAULT_FUTURE_V4_CONFIG = {
|
|
47
|
+
removeLegacyPostBuildHeadAttribute: false,
|
|
48
|
+
};
|
|
49
|
+
// When using the "v4: true" shortcut
|
|
50
|
+
exports.DEFAULT_FUTURE_V4_CONFIG_TRUE = {
|
|
51
|
+
removeLegacyPostBuildHeadAttribute: true,
|
|
41
52
|
};
|
|
42
53
|
exports.DEFAULT_FUTURE_CONFIG = {
|
|
54
|
+
v4: exports.DEFAULT_FUTURE_V4_CONFIG,
|
|
43
55
|
experimental_faster: exports.DEFAULT_FASTER_CONFIG,
|
|
44
56
|
experimental_storage: exports.DEFAULT_STORAGE_CONFIG,
|
|
45
57
|
experimental_router: 'browser',
|
|
@@ -155,11 +167,20 @@ const FASTER_CONFIG_SCHEMA = utils_validation_1.Joi.alternatives()
|
|
|
155
167
|
lightningCssMinimizer: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.lightningCssMinimizer),
|
|
156
168
|
mdxCrossCompilerCache: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache),
|
|
157
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),
|
|
158
171
|
}), utils_validation_1.Joi.boolean()
|
|
159
172
|
.required()
|
|
160
173
|
.custom((bool) => bool ? exports.DEFAULT_FASTER_CONFIG_TRUE : exports.DEFAULT_FASTER_CONFIG))
|
|
161
174
|
.optional()
|
|
162
175
|
.default(exports.DEFAULT_FASTER_CONFIG);
|
|
176
|
+
const FUTURE_V4_SCHEMA = utils_validation_1.Joi.alternatives()
|
|
177
|
+
.try(utils_validation_1.Joi.object({
|
|
178
|
+
removeLegacyPostBuildHeadAttribute: utils_validation_1.Joi.boolean().default(exports.DEFAULT_FUTURE_V4_CONFIG.removeLegacyPostBuildHeadAttribute),
|
|
179
|
+
}), utils_validation_1.Joi.boolean()
|
|
180
|
+
.required()
|
|
181
|
+
.custom((bool) => bool ? exports.DEFAULT_FUTURE_V4_CONFIG_TRUE : exports.DEFAULT_FUTURE_V4_CONFIG))
|
|
182
|
+
.optional()
|
|
183
|
+
.default(exports.DEFAULT_FUTURE_V4_CONFIG);
|
|
163
184
|
const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
164
185
|
type: utils_validation_1.Joi.string()
|
|
165
186
|
.equal('localStorage', 'sessionStorage')
|
|
@@ -171,6 +192,7 @@ const STORAGE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
|
171
192
|
.optional()
|
|
172
193
|
.default(exports.DEFAULT_STORAGE_CONFIG);
|
|
173
194
|
const FUTURE_CONFIG_SCHEMA = utils_validation_1.Joi.object({
|
|
195
|
+
v4: FUTURE_V4_SCHEMA,
|
|
174
196
|
experimental_faster: FASTER_CONFIG_SCHEMA,
|
|
175
197
|
experimental_storage: STORAGE_CONFIG_SCHEMA,
|
|
176
198
|
experimental_router: utils_validation_1.Joi.string()
|
|
@@ -308,6 +330,15 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
|
|
|
308
330
|
}).messages({
|
|
309
331
|
'docusaurus.configValidationWarning': 'Docusaurus config validation warning. Field {#label}: {#warningMessage}',
|
|
310
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
|
+
}
|
|
311
342
|
// TODO move to @docusaurus/utils-validation
|
|
312
343
|
function validateConfig(config, siteConfigPath) {
|
|
313
344
|
const { error, warning, value } = exports.ConfigSchema.validate(config, {
|
|
@@ -329,7 +360,6 @@ function validateConfig(config, siteConfigPath) {
|
|
|
329
360
|
: formattedError;
|
|
330
361
|
throw new Error(formattedError);
|
|
331
362
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
363
|
+
ensureDocusaurusConfigConsistency(value);
|
|
364
|
+
return value;
|
|
335
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
|
+
}
|
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);
|
|
@@ -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,54 +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,
|
|
82
|
+
v4RemoveLegacyPostBuildHeadAttribute: params.v4RemoveLegacyPostBuildHeadAttribute,
|
|
144
83
|
});
|
|
145
84
|
// This renders the full page HTML, including head tags...
|
|
146
85
|
const fullPageHtml = (0, ssgTemplate_1.renderSSGTemplate)({
|
|
147
86
|
params,
|
|
148
|
-
result,
|
|
87
|
+
result: appRenderResult,
|
|
149
88
|
ssgTemplate,
|
|
150
89
|
});
|
|
151
90
|
const minifierResult = await htmlMinifier.minify(fullPageHtml);
|
|
@@ -155,18 +94,26 @@ async function generateStaticFile({ pathname, renderer, params, htmlMinifier, ss
|
|
|
155
94
|
params,
|
|
156
95
|
});
|
|
157
96
|
return {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
},
|
|
161
104
|
};
|
|
162
105
|
}
|
|
163
106
|
catch (errorUnknown) {
|
|
164
107
|
const error = errorUnknown;
|
|
165
108
|
const tips = getSSGErrorTips(error);
|
|
166
109
|
const message = logger_1.default.interpolate `Can't render static file for pathname path=${pathname}${tips ? `\n\n${tips}` : ''}`;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
pathname,
|
|
113
|
+
error: new Error(message, {
|
|
114
|
+
cause: error,
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
170
117
|
}
|
|
171
118
|
}
|
|
172
119
|
function getSSGErrorTips(error) {
|
package/lib/ssg/ssgTemplate.js
CHANGED
|
@@ -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,
|
|
38
|
+
const { html: appHtml, collectedData: { modules, metadata }, } = result;
|
|
39
39
|
const { scripts, stylesheets } = getScriptsAndStylesheets({ manifest, modules });
|
|
40
|
-
const htmlAttributes =
|
|
41
|
-
const bodyAttributes = helmet.bodyAttributes.toString();
|
|
40
|
+
const { htmlAttributes, bodyAttributes } = metadata.internal;
|
|
42
41
|
const metaStrings = [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 = {
|
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-6204",
|
|
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-6204",
|
|
37
|
+
"@docusaurus/bundler": "3.7.0-canary-6204",
|
|
38
|
+
"@docusaurus/logger": "3.7.0-canary-6204",
|
|
39
|
+
"@docusaurus/mdx-loader": "3.7.0-canary-6204",
|
|
40
|
+
"@docusaurus/utils": "3.7.0-canary-6204",
|
|
41
|
+
"@docusaurus/utils-common": "3.7.0-canary-6204",
|
|
42
|
+
"@docusaurus/utils-validation": "3.7.0-canary-6204",
|
|
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-6204",
|
|
82
|
+
"@docusaurus/types": "3.7.0-canary-6204",
|
|
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": "4b43b97230585b110910edb3d67dc844aedaf4ed"
|
|
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
|
-
}>;
|