feat(core): Docusaurus Faster - SSG worker threads (#10826)

Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
Co-authored-by: Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
Co-authored-by: João Victor Lopes <joaof.victor@hotmail.com>
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
This commit is contained in:
Sébastien Lorber 2025-01-27 14:24:30 +01:00 committed by GitHub
parent 042070cf9d
commit 98aab81388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 767 additions and 204 deletions

View file

@ -21,6 +21,7 @@
],
"ignorePaths": [
"CHANGELOG.md",
"patches",
"packages/docusaurus-theme-translations/locales",
"package.json",
"yarn.lock",

View file

@ -50,7 +50,7 @@
"canary:bumpVersion": "yarn lerna version `yarn --silent canary:version` --exact --no-push --yes",
"canary:publish": "yarn lerna publish from-package --dist-tag canary --yes --no-verify-access",
"changelog": "lerna-changelog",
"postinstall": "yarn lock:update && yarn build:packages",
"postinstall": "patch-package && yarn lock:update && yarn build:packages",
"prepare": "husky install",
"format": "prettier --write .",
"format:diff": "prettier --list-different .",
@ -112,6 +112,8 @@
"lint-staged": "~13.2.3",
"lockfile-lint": "^4.14.0",
"npm-run-all": "^4.1.5",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8",
"react": "^18.0.0",
"react-dom": "^18.0.0",

View file

@ -130,6 +130,7 @@ export type FasterConfig = {
lightningCssMinimizer: boolean;
mdxCrossCompilerCache: boolean;
rspackBundler: boolean;
ssgWorkerThreads: boolean;
};
export type FutureV4Config = {

View file

@ -69,6 +69,7 @@
"semver": "^7.5.4",
"serve-handler": "^6.1.6",
"shelljs": "^0.8.5",
"tinypool": "^1.0.2",
"tslib": "^2.6.0",
"update-notifier": "^6.0.2",
"webpack": "^5.95.0",

View file

@ -12,6 +12,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -84,6 +85,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -156,6 +158,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -228,6 +231,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -300,6 +304,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -372,6 +377,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -444,6 +450,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -518,6 +525,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -592,6 +600,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
@ -669,6 +678,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,

View file

@ -86,6 +86,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
"lightningCssMinimizer": false,
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"ssgWorkerThreads": false,
"swcHtmlMinimizer": false,
"swcJsLoader": false,
"swcJsMinimizer": false,

View file

@ -58,6 +58,7 @@ describe('normalizeConfig', () => {
lightningCssMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
ssgWorkerThreads: true,
},
experimental_storage: {
type: 'sessionStorage',
@ -760,6 +761,7 @@ describe('future', () => {
lightningCssMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
ssgWorkerThreads: true,
},
experimental_storage: {
type: 'sessionStorage',
@ -1113,10 +1115,12 @@ describe('future', () => {
lightningCssMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
ssgWorkerThreads: true,
};
expect(
normalizeConfig({
future: {
v4: true,
experimental_faster: faster,
},
}),
@ -1131,14 +1135,45 @@ describe('future', () => {
).toEqual(fasterContaining(DEFAULT_FASTER_CONFIG));
});
it('accepts faster - true', () => {
it('accepts faster - true (v4: true)', () => {
expect(
normalizeConfig({
future: {experimental_faster: true},
future: {
v4: true,
experimental_faster: true,
},
}),
).toEqual(fasterContaining(DEFAULT_FASTER_CONFIG_TRUE));
});
it('rejects faster - true (v4: false)', () => {
expect(() =>
normalizeConfig({
future: {
v4: false,
experimental_faster: true,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
"Docusaurus config \`future.experimental_faster.ssgWorkerThreads\` requires the future flag \`future.v4.removeLegacyPostBuildHeadAttribute\` to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: \`{future: {v4: true}}\`"
`);
});
it('rejects faster - true (v4: undefined)', () => {
expect(() =>
normalizeConfig({
future: {
v4: false,
experimental_faster: true,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
"Docusaurus config \`future.experimental_faster.ssgWorkerThreads\` requires the future flag \`future.v4.removeLegacyPostBuildHeadAttribute\` to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: \`{future: {v4: true}}\`"
`);
});
it('rejects faster - number', () => {
// @ts-expect-error: invalid
const faster: Partial<FasterConfig> = 42;
@ -1579,6 +1614,112 @@ describe('future', () => {
`);
});
});
describe('ssgWorkerThreads', () => {
it('accepts - undefined', () => {
const faster: Partial<FasterConfig> = {
ssgWorkerThreads: undefined,
};
expect(
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({ssgWorkerThreads: false}));
});
it('accepts - true (v4: true)', () => {
const faster: Partial<FasterConfig> = {
ssgWorkerThreads: true,
};
expect(
normalizeConfig({
future: {
v4: true,
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({ssgWorkerThreads: true}));
});
it('rejects - true (v4: false)', () => {
const faster: Partial<FasterConfig> = {
ssgWorkerThreads: true,
};
expect(() =>
normalizeConfig({
future: {
v4: false,
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
"Docusaurus config \`future.experimental_faster.ssgWorkerThreads\` requires the future flag \`future.v4.removeLegacyPostBuildHeadAttribute\` to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: \`{future: {v4: true}}\`"
`);
});
it('rejects - true (v4: undefined)', () => {
const faster: Partial<FasterConfig> = {
ssgWorkerThreads: true,
};
expect(() =>
normalizeConfig({
future: {
v4: undefined,
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
"Docusaurus config \`future.experimental_faster.ssgWorkerThreads\` requires the future flag \`future.v4.removeLegacyPostBuildHeadAttribute\` to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: \`{future: {v4: true}}\`"
`);
});
it('accepts - false', () => {
const faster: Partial<FasterConfig> = {
ssgWorkerThreads: false,
};
expect(
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({ssgWorkerThreads: false}));
});
it('rejects - null', () => {
// @ts-expect-error: invalid
const faster: Partial<FasterConfig> = {ssgWorkerThreads: 42};
expect(() =>
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_faster.ssgWorkerThreads" must be a boolean
"
`);
});
it('rejects - number', () => {
// @ts-expect-error: invalid
const faster: Partial<FasterConfig> = {ssgWorkerThreads: 42};
expect(() =>
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_faster.ssgWorkerThreads" must be a boolean
"
`);
});
});
});
describe('v4', () => {

View file

@ -16,6 +16,7 @@ import {
addLeadingSlash,
removeTrailingSlash,
} from '@docusaurus/utils-common';
import logger from '@docusaurus/logger';
import type {
FasterConfig,
FutureConfig,
@ -49,6 +50,7 @@ export const DEFAULT_FASTER_CONFIG: FasterConfig = {
lightningCssMinimizer: false,
mdxCrossCompilerCache: false,
rspackBundler: false,
ssgWorkerThreads: false,
};
// When using the "faster: true" shortcut
@ -59,6 +61,7 @@ export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = {
lightningCssMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
ssgWorkerThreads: true,
};
export const DEFAULT_FUTURE_V4_CONFIG: FutureV4Config = {
@ -243,6 +246,9 @@ const FASTER_CONFIG_SCHEMA = Joi.alternatives()
DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache,
),
rspackBundler: Joi.boolean().default(DEFAULT_FASTER_CONFIG.rspackBundler),
ssgWorkerThreads: Joi.boolean().default(
DEFAULT_FASTER_CONFIG.ssgWorkerThreads,
),
}),
Joi.boolean()
.required()
@ -445,6 +451,26 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
'Docusaurus config validation warning. Field {#label}: {#warningMessage}',
});
// Expressing this kind of logic in Joi is a pain
// We also want to decouple logic from Joi: easier to remove it later!
function ensureDocusaurusConfigConsistency(config: DocusaurusConfig) {
if (
config.future.experimental_faster.ssgWorkerThreads &&
!config.future.v4.removeLegacyPostBuildHeadAttribute
) {
throw new Error(
`Docusaurus config ${logger.code(
'future.experimental_faster.ssgWorkerThreads',
)} requires the future flag ${logger.code(
'future.v4.removeLegacyPostBuildHeadAttribute',
)} to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: ${logger.code(
'{future: {v4: true}}',
)}`,
);
}
}
// TODO move to @docusaurus/utils-validation
export function validateConfig(
config: unknown,
@ -476,7 +502,9 @@ export function validateConfig(
? `${formattedError}These field(s) (${unknownFields}) are not recognized in ${siteConfigPath}.\nIf you still want these fields to be in your configuration, put them in the "customFields" field.\nSee https://docusaurus.io/docs/api/docusaurus-config/#customfields`
: formattedError;
throw new Error(formattedError);
} else {
return value;
}
ensureDocusaurusConfigConsistency(value);
return value;
}

View file

@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Secret way to set SSR plugin async concurrency option
// Waiting for feedback before documenting this officially?
// TODO Docusaurus v4, rename SSR => SSG
export const SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
: // Not easy to define a reasonable option default
// Will still be better than Infinity
// See also https://github.com/sindresorhus/p-map/issues/24
32;
// Secret way to set SSR plugin async concurrency option
// Waiting for feedback before documenting this officially?
export const SSGWorkerThreadCount: number | undefined = process.env
.DOCUSAURUS_SSG_WORKER_THREAD_COUNT
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_COUNT, 10)
: undefined;
// Number of pathnames to SSG per worker task
export const SSGWorkerThreadTaskSize: number = process.env
.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE
? parseInt(process.env.DOCUSAURUS_SSG_WORKER_THREAD_TASK_SIZE, 10)
: 10; // TODO need fine-tuning

View file

@ -5,15 +5,156 @@
* LICENSE file in the root directory of this source tree.
*/
import {PerfLogger} from '@docusaurus/logger';
import * as path from 'path';
import {pathToFileURL} from 'node:url';
import os from 'os';
import _ from 'lodash';
import logger, {PerfLogger} from '@docusaurus/logger';
import {createSSGParams} from './ssgParams';
import {generateStaticFiles} from './ssg';
import {renderHashRouterTemplate} from './ssgTemplate';
import {SSGWorkerThreadCount, SSGWorkerThreadTaskSize} from './ssgEnv';
import {generateHashRouterEntrypoint} from './ssgUtils';
import {createGlobalSSGResult} from './ssgGlobalResult';
import {executeSSGInlineTask} from './ssgWorkerInline';
import type {Props, RouterType} from '@docusaurus/types';
import type {SiteCollectedData} from '../common';
import type {SSGParams} from './ssgParams';
import type {SSGGlobalResult} from './ssgGlobalResult';
import type {ExecuteSSGWorkerThreadTask} from './ssgWorkerThread';
type SSGExecutor = {
run: () => Promise<SSGGlobalResult>;
destroy: () => Promise<void>;
};
type CreateSSGExecutor = (params: {
params: SSGParams;
pathnames: string[];
}) => Promise<SSGExecutor>;
const createSimpleSSGExecutor: CreateSSGExecutor = async ({
params,
pathnames,
}) => {
return {
run: () => {
return PerfLogger.async(
'Generate static files (current thread)',
async () => {
const ssgResults = await executeSSGInlineTask({
pathnames,
params,
});
return createGlobalSSGResult(ssgResults);
},
);
},
destroy: async () => {
// nothing to do
},
};
};
// Sensible default that gives decent performances
// It's hard to have a perfect formula that works for all hosts
// Each thread has some creation overhead
// Having 1 thread per cpu doesn't necessarily improve perf on small sites
// We want to ensure that we don't create a worker thread for less than x paths
function inferNumberOfThreads({
pageCount,
cpuCount,
minPagesPerCpu,
}: {
pageCount: number;
cpuCount: number;
minPagesPerCpu: number;
}) {
// Calculate "ideal" amount of threads based on the number of pages to render
const threadsByWorkload = Math.ceil(pageCount / minPagesPerCpu);
// Use the smallest of threadsByWorkload or cpuCount, ensuring min=1 thread
return Math.max(1, Math.min(threadsByWorkload, cpuCount));
}
function getNumberOfThreads(pathnames: string[]) {
if (typeof SSGWorkerThreadCount !== 'undefined') {
return SSGWorkerThreadCount;
}
return inferNumberOfThreads({
pageCount: pathnames.length,
// TODO use "physical CPUs" instead of "logical CPUs" (like Tinypool does)
// See also https://github.com/tinylibs/tinypool/pull/108
cpuCount: os.cpus().length,
// These are "magic value" that we should refine based on user feedback
// Local tests show that it's not worth spawning new workers for few pages
minPagesPerCpu: 100,
});
}
const createPooledSSGExecutor: CreateSSGExecutor = async ({
params,
pathnames,
}) => {
const numberOfThreads = getNumberOfThreads(pathnames);
// When the inferred or provided number of threads is just 1
// It's not worth it to use a thread pool
// This also allows users to disable the thread pool with the env variable
// DOCUSAURUS_SSG_WORKER_THREADS=1
if (numberOfThreads === 1) {
return createSimpleSSGExecutor({params, pathnames});
}
const pool = await PerfLogger.async(
`Create SSG pool - ${logger.cyan(numberOfThreads)} threads`,
async () => {
const Tinypool = await import('tinypool').then((m) => m.default);
const workerURL = pathToFileURL(
path.resolve(__dirname, 'ssgWorkerThread.js'),
);
return new Tinypool({
filename: workerURL.pathname,
minThreads: numberOfThreads,
maxThreads: numberOfThreads,
concurrentTasksPerWorker: 1,
runtime: 'worker_threads',
isolateWorkers: false,
workerData: {params},
});
},
);
const pathnamesChunks = _.chunk(pathnames, SSGWorkerThreadTaskSize);
// Tiny wrapper for type-safety
const submitTask: ExecuteSSGWorkerThreadTask = (task) => pool.run(task);
return {
run: async () => {
const results = await PerfLogger.async(
`Generate static files (${numberOfThreads} worker threads)`,
async () => {
return Promise.all(
pathnamesChunks.map((taskPathnames, taskIndex) => {
return submitTask({
id: taskIndex + 1,
pathnames: taskPathnames,
});
}),
);
},
);
const allResults = results.flat();
return createGlobalSSGResult(allResults);
},
destroy: async () => {
await pool.destroy();
},
};
};
// TODO Docusaurus v4 - introduce SSG worker threads
export async function executeSSG({
props,
serverBundlePath,
@ -31,6 +172,7 @@ export async function executeSSG({
props,
});
// TODO doesn't look like the appropriate place for hash router entry
if (router === 'hash') {
PerfLogger.start('Generate Hash Router entry point');
const content = await renderHashRouterTemplate({params});
@ -39,12 +181,13 @@ export async function executeSSG({
return {collectedData: {}};
}
const ssgResult = await PerfLogger.async('Generate static files', () =>
generateStaticFiles({
pathnames: props.routesPaths,
params,
}),
);
const createExecutor = props.siteConfig.future.experimental_faster
.ssgWorkerThreads
? createPooledSSGExecutor
: createSimpleSSGExecutor;
return ssgResult;
const executor = await createExecutor({params, pathnames: props.routesPaths});
const result = await executor.run();
await executor.destroy();
return result;
}

View file

@ -0,0 +1,104 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import logger from '@docusaurus/logger';
import type {SSGError, SSGResult, SSGSuccess} from './ssgRenderer';
import type {SiteCollectedData} from '../common';
// Consolidated successful SSG result of rendering all pathnames of a site
export type SSGGlobalResult = {
collectedData: SiteCollectedData;
// Don't include heavy un-needed data here
// We want to release heap memory as soon as we can
};
function printSSGWarnings(results: SSGSuccess[]): void {
// Escape hatch because SWC is quite aggressive to report errors
// See https://github.com/facebook/docusaurus/pull/10554
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
return;
}
const ignoredWarnings: string[] = [
// TODO Docusaurus v4: remove with React 19 upgrade
// React 18 emit NULL chars, and minifier detects it
// see https://github.com/facebook/docusaurus/issues/9985
'Unexpected null character',
];
const keepWarning = (warning: string) => {
return !ignoredWarnings.some((iw) => warning.includes(iw));
};
const resultsWithWarnings = results
.map((success) => {
return {
...success,
warnings: success.result.warnings.filter(keepWarning),
};
})
.filter((result) => result.warnings.length > 0);
if (resultsWithWarnings.length) {
const message = `Docusaurus static site generation process emitted warnings for ${
resultsWithWarnings.length
} path${resultsWithWarnings.length ? 's' : ''}
This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
- ${resultsWithWarnings
.map(
(result) => `${logger.path(result.pathname)}:
- ${result.warnings.join('\n - ')}
`,
)
.join('\n- ')}`;
logger.warn(message);
}
}
function throwSSGError(ssgErrors: SSGError[]): never {
const message = `Docusaurus static site generation failed for ${
ssgErrors.length
} path${ssgErrors.length ? 's' : ''}:\n- ${ssgErrors
.map((ssgError) => logger.path(ssgError.pathname))
.join('\n- ')}`;
// Note logging this error properly require using inspect(error,{depth})
// See https://github.com/nodejs/node/issues/51637
throw new Error(message, {
cause: new AggregateError(ssgErrors.map((ssgError) => ssgError.error)),
});
}
export async function createGlobalSSGResult(
ssgResults: SSGResult[],
): Promise<SSGGlobalResult> {
const [ssgSuccesses, ssgErrors] = _.partition(
ssgResults,
(result) => result.success,
);
// For now, only success results emit warnings
// For errors, we throw without warnings
printSSGWarnings(ssgSuccesses);
if (ssgErrors.length > 0) {
throwSSGError(ssgErrors);
}
// If we only have SSG successes, we can consolidate those in a single result
const collectedData: SiteCollectedData = _.chain(ssgSuccesses)
.keyBy((success) => success.pathname)
.mapValues((ssgSuccess) => ssgSuccess.result.collectedData)
.value();
return {collectedData};
}

View file

@ -7,7 +7,6 @@
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
// TODO eval is archived / unmaintained: https://github.com/pierrec/node-eval
// We should internalize/modernize it
import evaluate from 'eval';
@ -19,34 +18,34 @@ import {
renderSSGTemplate,
type SSGTemplateCompiled,
} from './ssgTemplate';
import {SSGConcurrency, writeStaticFile} from './ssgUtils';
import {SSGConcurrency} from './ssgEnv';
import {writeStaticFile} from './ssgUtils';
import {createSSGRequire} from './ssgNodeRequire';
import type {SSGParams} from './ssgParams';
import type {AppRenderer, AppRenderResult, SiteCollectedData} from '../common';
import type {AppRenderer, AppRenderResult} from '../common';
import type {HtmlMinifier} from '@docusaurus/bundler';
type SSGSuccessResult = {
collectedData: AppRenderResult['collectedData'];
// html: we don't include it on purpose!
// we don't need to aggregate all html contents in memory!
// html contents can be GC as soon as they are written to disk
export type SSGSuccess = {
success: true;
pathname: string;
result: {
collectedData: AppRenderResult['collectedData'];
warnings: string[];
// html: we don't include it on purpose!
// we don't need to aggregate all html contents in memory!
// html contents can be GC as soon as they are written to disk
};
};
type SSGSuccess = {
pathname: string;
error: null;
result: SSGSuccessResult;
warnings: string[];
};
type SSGError = {
export type SSGError = {
success: false;
pathname: string;
error: Error;
result: null;
warnings: string[];
};
type SSGResult = SSGSuccess | SSGError;
export async function loadAppRenderer({
export type SSGResult = SSGSuccess | SSGError;
async function loadAppRenderer({
serverBundlePath,
}: {
serverBundlePath: string;
@ -54,9 +53,6 @@ export async function loadAppRenderer({
const source = await PerfLogger.async(`Load server bundle`, () =>
fs.readFile(serverBundlePath),
);
PerfLogger.log(
`Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`,
);
const filename = path.basename(serverBundlePath);
@ -101,65 +97,17 @@ export async function loadAppRenderer({
};
}
export function printSSGWarnings(
results: {
pathname: string;
warnings: string[];
}[],
): void {
// Escape hatch because SWC is quite aggressive to report errors
// See https://github.com/facebook/docusaurus/pull/10554
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
return;
}
export type SSGRenderer = {
shutdown: () => Promise<void>;
renderPathnames: (pathnames: string[]) => Promise<SSGResult[]>;
};
const ignoredWarnings: string[] = [
// TODO React/Docusaurus emit NULL chars, and minifier detects it
// see https://github.com/facebook/docusaurus/issues/9985
'Unexpected null character',
];
const keepWarning = (warning: string) => {
return !ignoredWarnings.some((iw) => warning.includes(iw));
};
const resultsWithWarnings = results
.map((result) => {
return {
...result,
warnings: result.warnings.filter(keepWarning),
};
})
.filter((result) => result.warnings.length > 0);
if (resultsWithWarnings.length) {
const message = `Docusaurus static site generation process emitted warnings for ${
resultsWithWarnings.length
} path${resultsWithWarnings.length ? 's' : ''}
This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
- ${resultsWithWarnings
.map(
(result) => `${logger.path(result.pathname)}:
- ${result.warnings.join('\n - ')}
`,
)
.join('\n- ')}`;
logger.warn(message);
}
}
export async function generateStaticFiles({
pathnames,
export async function loadSSGRenderer({
params,
}: {
pathnames: string[];
params: SSGParams;
}): Promise<{collectedData: SiteCollectedData}> {
const [renderer, htmlMinifier, ssgTemplate] = await Promise.all([
}): Promise<SSGRenderer> {
const [appRenderer, htmlMinifier, ssgTemplate] = await Promise.all([
PerfLogger.async('Load App renderer', () =>
loadAppRenderer({
serverBundlePath: params.serverBundlePath,
@ -175,81 +123,43 @@ export async function generateStaticFiles({
),
]);
// Note that we catch all async errors on purpose
// Docusaurus presents all the SSG errors to the user, not just the first one
const results: SSGResult[] = await pMap(
pathnames,
async (pathname) =>
generateStaticFile({
pathname,
renderer,
params,
htmlMinifier,
ssgTemplate,
}).then(
(result) => ({
pathname,
result,
error: null,
warnings: result.warnings,
}),
(error) => ({
pathname,
result: null,
error: error as Error,
warnings: [],
}),
),
{concurrency: SSGConcurrency},
);
await renderer.shutdown();
printSSGWarnings(results);
const [allSSGErrors, allSSGSuccesses] = _.partition(
results,
(result): result is SSGError => !!result.error,
);
if (allSSGErrors.length > 0) {
const message = `Docusaurus static site generation failed for ${
allSSGErrors.length
} path${allSSGErrors.length ? 's' : ''}:\n- ${allSSGErrors
.map((ssgError) => logger.path(ssgError.pathname))
.join('\n- ')}`;
// Note logging this error properly require using inspect(error,{depth})
// See https://github.com/nodejs/node/issues/51637
throw new Error(message, {
cause: new AggregateError(allSSGErrors.map((ssgError) => ssgError.error)),
});
}
const collectedData: SiteCollectedData = _.chain(allSSGSuccesses)
.keyBy((success) => success.pathname)
.mapValues((ssgSuccess) => ssgSuccess.result.collectedData)
.value();
return {collectedData};
return {
renderPathnames: (pathnames) => {
return pMap<string, SSGResult>(
pathnames,
async (pathname) =>
generateStaticFile({
pathname,
appRenderer,
params,
htmlMinifier,
ssgTemplate,
}),
{concurrency: SSGConcurrency},
);
},
shutdown: async () => {
await appRenderer.shutdown();
},
};
}
async function generateStaticFile({
pathname,
renderer,
appRenderer,
params,
htmlMinifier,
ssgTemplate,
}: {
pathname: string;
renderer: AppRenderer;
appRenderer: AppRenderer;
params: SSGParams;
htmlMinifier: HtmlMinifier;
ssgTemplate: SSGTemplateCompiled;
}): Promise<SSGSuccessResult & {warnings: string[]}> {
}): Promise<SSGResult> {
try {
// This only renders the app HTML
const result = await renderer.render({
const appRenderResult = await appRenderer.render({
pathname,
v4RemoveLegacyPostBuildHeadAttribute:
params.v4RemoveLegacyPostBuildHeadAttribute,
@ -257,7 +167,7 @@ async function generateStaticFile({
// This renders the full page HTML, including head tags...
const fullPageHtml = renderSSGTemplate({
params,
result,
result: appRenderResult,
ssgTemplate,
});
const minifierResult = await htmlMinifier.minify(fullPageHtml);
@ -267,9 +177,13 @@ async function generateStaticFile({
params,
});
return {
collectedData: result.collectedData,
// As of today, only the html minifier can emit SSG warnings
warnings: minifierResult.warnings,
success: true,
pathname,
result: {
collectedData: appRenderResult.collectedData,
// As of today, only the html minifier can emit SSG warnings
warnings: minifierResult.warnings,
},
};
} catch (errorUnknown) {
const error = errorUnknown as Error;
@ -277,9 +191,13 @@ async function generateStaticFile({
const message = logger.interpolate`Can't render static file for pathname path=${pathname}${
tips ? `\n\n${tips}` : ''
}`;
throw new Error(message, {
cause: error,
});
return {
success: false,
pathname,
error: new Error(message, {
cause: error,
}),
};
}
}

View file

@ -9,15 +9,6 @@ import fs from 'fs-extra';
import path from 'path';
import type {SSGParams} from './ssgParams';
// Secret way to set SSR plugin concurrency option
// Waiting for feedback before documenting this officially?
export const SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
: // Not easy to define a reasonable option default
// Will still be better than Infinity
// See also https://github.com/sindresorhus/p-map/issues/24
32;
function pathnameToFilename({
pathname,
trailingSlash,

View file

@ -0,0 +1,20 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {loadSSGRenderer, type SSGResult} from './ssgRenderer';
import type {SSGParams} from './ssgParams';
// "inline" means in the current thread, not in a worker
export async function executeSSGInlineTask(arg: {
pathnames: string[];
params: SSGParams;
}): Promise<SSGResult[]> {
const appRenderer = await loadSSGRenderer({params: arg.params});
const ssgResults = appRenderer.renderPathnames(arg.pathnames);
await appRenderer.shutdown();
return ssgResults;
}

View file

@ -0,0 +1,60 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {workerData} from 'node:worker_threads';
import logger, {PerfLogger} from '@docusaurus/logger';
import {loadSSGRenderer, type SSGResult} from './ssgRenderer.js';
import type {SSGParams} from './ssgParams.js';
// eslint-disable-next-line no-underscore-dangle
const workerId = process?.__tinypool_state__?.workerId;
if (!workerId) {
throw new Error('SSG Worker Thread not executing in Tinypool context?');
}
const params: SSGParams = workerData?.[1]?.params;
if (!params) {
throw new Error(`SSG Worker Thread workerData params missing`);
}
const WorkerLogPrefix = `SSG Worker ${logger.name(workerId)}`;
// We only load once the SSG rendered (expensive), NOT once per worker task
// TODO check potential memory leak?
const appRendererPromise = PerfLogger.async(
`${WorkerLogPrefix} - Initialization`,
() =>
loadSSGRenderer({
params,
}),
);
export type SSGWorkerThreadTask = {
id: number;
pathnames: string[];
};
export default async function executeSSGWorkerThreadTask(
task: SSGWorkerThreadTask,
): Promise<SSGResult[]> {
const appRenderer = await appRendererPromise;
const ssgResults = await PerfLogger.async(
`${WorkerLogPrefix} - Task ${logger.name(
task.id,
)} - Rendering ${logger.cyan(task.pathnames.length)} pathnames`,
() => appRenderer.renderPathnames(task.pathnames),
);
// Afaik it's not needed to shutdown here,
// The thread pool destroys worker thread and releases worker thread memory
// await appRenderer.shutdown();
return ssgResults;
}
export type ExecuteSSGWorkerThreadTask = typeof executeSSGWorkerThreadTask;

View file

@ -206,6 +206,7 @@ export default {
swcHtmlMinimizer: true,
lightningCssMinimizer: true,
rspackBundler: true,
ssgWorkerThreads: true,
mdxCrossCompilerCache: true,
},
experimental_storage: {
@ -226,6 +227,7 @@ export default {
- [`lightningCssMinimizer`](https://github.com/facebook/docusaurus/pull/10522): Use [Lightning CSS](https://lightningcss.dev/) to minify CSS (instead of [cssnano](https://github.com/cssnano/cssnano) and [clean-css](https://github.com/clean-css/clean-css)).
- [`rspackBundler`](https://github.com/facebook/docusaurus/pull/10402): Use [Rspack](https://rspack.dev/) to bundle your app (instead of [webpack](https://webpack.js.org/)).
- [`mdxCrossCompilerCache`](https://github.com/facebook/docusaurus/pull/10479): Compile MDX files only once for both browser/Node.js environments instead of twice.
- [`ssgWorkerThreads`](https://github.com/facebook/docusaurus/pull/10826): Using a Node.js worker thread pool to execute the static site generation phase faster. Requires `future.v4.removeLegacyPostBuildHeadAttribute` to be turned on.
- `experimental_storage`: Site-wide browser storage options that theme authors should strive to respect.
- `type`: The browser storage theme authors should use. Possible values are `localStorage` and `sessionStorage`. Defaults to `localStorage`.
- `namespace`: Whether to namespace the browser storage keys to avoid storage key conflicts when Docusaurus sites are hosted under the same domain, or on localhost. Possible values are `string | boolean`. The namespace is appended at the end of the storage keys `key-namespace`. Use `true` to automatically generate a random namespace from your site `url + baseUrl`. Defaults to `false` (no namespace, historical behavior).

View file

@ -358,7 +358,6 @@ export default async function createConfigAsync() {
],
[
'ideal-image',
{
quality: 70,
max: 1030,

180
yarn.lock
View file

@ -5644,16 +5644,31 @@ cacheable-request@^10.2.8:
normalize-url "^8.0.0"
responselike "^3.0.0"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bind@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
dependencies:
call-bind-apply-helpers "^1.0.0"
es-define-property "^1.0.0"
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
set-function-length "^1.2.2"
call-bound@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681"
integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==
dependencies:
call-bind-apply-helpers "^1.0.1"
get-intrinsic "^1.2.6"
callsites@^3.0.0, callsites@^3.1.0:
version "3.1.0"
@ -5877,7 +5892,7 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
ci-info@^3.2.0, ci-info@^3.6.1:
ci-info@^3.2.0, ci-info@^3.6.1, ci-info@^3.7.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
@ -7640,6 +7655,15 @@ dotenv@~10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@ -7861,12 +7885,10 @@ es-abstract@^1.17.5, es-abstract@^1.20.4, es-abstract@^1.22.1, es-abstract@^1.22
unbox-primitive "^1.0.2"
which-typed-array "^1.1.15"
es-define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-define-property@^1.0.0, es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.2.1, es-errors@^1.3.0:
version "1.3.0"
@ -8738,6 +8760,13 @@ find-up@^6.3.0:
locate-path "^7.1.0"
path-exists "^5.0.0"
find-yarn-workspace-root@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
dependencies:
micromatch "^4.0.2"
flat-cache@^3.0.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
@ -8969,16 +8998,21 @@ get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4, get-intrinsic@^1.2.6:
version "1.2.7"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044"
integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.0.0"
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
get-proto "^1.0.0"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
@ -9005,6 +9039,14 @@ get-port@5.1.1:
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
get-proto@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-stdin@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575"
@ -9242,12 +9284,10 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
gopd@^1.0.1, gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
got@^12.1.0:
version "12.6.1"
@ -9357,15 +9397,15 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
dependencies:
es-define-property "^1.0.0"
has-proto@^1.0.1, has-proto@^1.0.3:
has-proto@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-symbols@^1.0.2, has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
version "1.0.2"
@ -10482,7 +10522,7 @@ is-weakset@^2.0.3:
call-bind "^1.0.7"
get-intrinsic "^1.2.4"
is-wsl@^2.2.0:
is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@ -11123,6 +11163,17 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json-stable-stringify@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz#addb683c2b78014d0b78d704c2fcbdf0695a60e2"
integrity sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==
dependencies:
call-bind "^1.0.8"
call-bound "^1.0.3"
isarray "^2.0.5"
jsonify "^0.0.1"
object-keys "^1.1.1"
json-stream-stringify@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-stream-stringify/-/json-stream-stringify-3.0.1.tgz#e383df35f9845a400afa5c112b281821dc4ee017"
@ -11169,6 +11220,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
jsonparse@^1.2.0, jsonparse@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@ -11223,6 +11279,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klaw-sync@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
dependencies:
graceful-fs "^4.1.11"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@ -11891,6 +11954,11 @@ marked@^13.0.2:
resolved "https://registry.yarnpkg.com/marked/-/marked-13.0.3.tgz#5c5b4a5d0198060c7c9bc6ef9420a7fed30f822d"
integrity sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mathml-tag-names@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@ -13550,6 +13618,14 @@ onetime@^6.0.0:
dependencies:
mimic-fn "^4.0.0"
open@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
dependencies:
is-docker "^2.0.0"
is-wsl "^2.1.1"
open@^8.0.9, open@^8.4.0, open@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
@ -13938,6 +14014,27 @@ pascal-case@^3.1.2:
no-case "^3.0.4"
tslib "^2.0.3"
patch-package@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
chalk "^4.1.2"
ci-info "^3.7.0"
cross-spawn "^7.0.3"
find-yarn-workspace-root "^2.0.0"
fs-extra "^9.0.0"
json-stable-stringify "^1.0.2"
klaw-sync "^6.0.0"
minimist "^1.2.6"
open "^7.4.2"
rimraf "^2.6.3"
semver "^7.5.3"
slash "^2.0.0"
tmp "^0.0.33"
yaml "^2.2.2"
path-browserify@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
@ -14723,6 +14820,11 @@ postcss@^8.2.x, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.
picocolors "^1.1.0"
source-map-js "^1.2.1"
postinstall-postinstall@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
prebuild-install@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
@ -15859,7 +15961,7 @@ rfdc@^1.3.0:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rimraf@2:
rimraf@2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@ -16211,7 +16313,7 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
set-function-length@^1.2.1:
set-function-length@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
@ -16398,6 +16500,11 @@ slash@3.0.0, slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slash@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
@ -17312,6 +17419,11 @@ tinyglobby@^0.2.9:
fdir "^6.4.0"
picomatch "^4.0.2"
tinypool@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2"
integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==
tmp-promise@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7"