diff --git a/.cspell.json b/.cspell.json index 16dc7c67c9cf..f55f6265d105 100644 --- a/.cspell.json +++ b/.cspell.json @@ -21,6 +21,7 @@ ], "ignorePaths": [ "CHANGELOG.md", + "patches", "packages/docusaurus-theme-translations/locales", "package.json", "yarn.lock", diff --git a/package.json b/package.json index 71d5a11f49ed..457201472be5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index 4e646cc51fe8..9c8b575a48cc 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -130,6 +130,7 @@ export type FasterConfig = { lightningCssMinimizer: boolean; mdxCrossCompilerCache: boolean; rspackBundler: boolean; + ssgWorkerThreads: boolean; }; export type FutureV4Config = { diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 830b1f4e3398..7e4804593cf4 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -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", diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index 4d93bd7aee13..044bf277c4f8 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -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, diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap index d1dc993c6215..341f0cc9ae5c 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap @@ -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, diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index 63ea0c294d35..d99e1c8f7700 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -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 = 42; @@ -1579,6 +1614,112 @@ describe('future', () => { `); }); }); + + describe('ssgWorkerThreads', () => { + it('accepts - undefined', () => { + const faster: Partial = { + ssgWorkerThreads: undefined, + }; + expect( + normalizeConfig({ + future: { + experimental_faster: faster, + }, + }), + ).toEqual(fasterContaining({ssgWorkerThreads: false})); + }); + + it('accepts - true (v4: true)', () => { + const faster: Partial = { + ssgWorkerThreads: true, + }; + expect( + normalizeConfig({ + future: { + v4: true, + experimental_faster: faster, + }, + }), + ).toEqual(fasterContaining({ssgWorkerThreads: true})); + }); + + it('rejects - true (v4: false)', () => { + const faster: Partial = { + 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 = { + 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 = { + ssgWorkerThreads: false, + }; + expect( + normalizeConfig({ + future: { + experimental_faster: faster, + }, + }), + ).toEqual(fasterContaining({ssgWorkerThreads: false})); + }); + + it('rejects - null', () => { + // @ts-expect-error: invalid + const faster: Partial = {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 = {ssgWorkerThreads: 42}; + expect(() => + normalizeConfig({ + future: { + experimental_faster: faster, + }, + }), + ).toThrowErrorMatchingInlineSnapshot(` + ""future.experimental_faster.ssgWorkerThreads" must be a boolean + " + `); + }); + }); }); describe('v4', () => { diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 049dd819c3c0..5b066557f04a 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -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({ '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; } diff --git a/packages/docusaurus/src/ssg/ssgEnv.ts b/packages/docusaurus/src/ssg/ssgEnv.ts new file mode 100644 index 000000000000..869be2bca742 --- /dev/null +++ b/packages/docusaurus/src/ssg/ssgEnv.ts @@ -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 diff --git a/packages/docusaurus/src/ssg/ssgExecutor.ts b/packages/docusaurus/src/ssg/ssgExecutor.ts index 32f10e258a4d..2defc598c82c 100644 --- a/packages/docusaurus/src/ssg/ssgExecutor.ts +++ b/packages/docusaurus/src/ssg/ssgExecutor.ts @@ -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; + destroy: () => Promise; +}; + +type CreateSSGExecutor = (params: { + params: SSGParams; + pathnames: string[]; +}) => Promise; + +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; } diff --git a/packages/docusaurus/src/ssg/ssgGlobalResult.ts b/packages/docusaurus/src/ssg/ssgGlobalResult.ts new file mode 100644 index 000000000000..d23c092b98cb --- /dev/null +++ b/packages/docusaurus/src/ssg/ssgGlobalResult.ts @@ -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 { + 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}; +} diff --git a/packages/docusaurus/src/ssg/ssg.ts b/packages/docusaurus/src/ssg/ssgRenderer.ts similarity index 50% rename from packages/docusaurus/src/ssg/ssg.ts rename to packages/docusaurus/src/ssg/ssgRenderer.ts index 52cd7b2d4cb3..91dcc5f4e660 100644 --- a/packages/docusaurus/src/ssg/ssg.ts +++ b/packages/docusaurus/src/ssg/ssgRenderer.ts @@ -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 -}; - -type SSGSuccess = { +export type SSGSuccess = { + success: true; pathname: string; - error: null; - result: SSGSuccessResult; - warnings: 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 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; - } - - 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 type SSGRenderer = { + shutdown: () => Promise; + renderPathnames: (pathnames: string[]) => Promise; +}; -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 { + 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( + 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 { +}): Promise { 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, + }), + }; } } diff --git a/packages/docusaurus/src/ssg/ssgUtils.ts b/packages/docusaurus/src/ssg/ssgUtils.ts index 33f51d08bfc7..df01f3c9e8c2 100644 --- a/packages/docusaurus/src/ssg/ssgUtils.ts +++ b/packages/docusaurus/src/ssg/ssgUtils.ts @@ -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, diff --git a/packages/docusaurus/src/ssg/ssgWorkerInline.ts b/packages/docusaurus/src/ssg/ssgWorkerInline.ts new file mode 100644 index 000000000000..d70625f72b4a --- /dev/null +++ b/packages/docusaurus/src/ssg/ssgWorkerInline.ts @@ -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 { + const appRenderer = await loadSSGRenderer({params: arg.params}); + const ssgResults = appRenderer.renderPathnames(arg.pathnames); + await appRenderer.shutdown(); + return ssgResults; +} diff --git a/packages/docusaurus/src/ssg/ssgWorkerThread.ts b/packages/docusaurus/src/ssg/ssgWorkerThread.ts new file mode 100644 index 000000000000..91b0aa08d1f6 --- /dev/null +++ b/packages/docusaurus/src/ssg/ssgWorkerThread.ts @@ -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 { + 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; diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index 1ead788b14d9..fb6bfc5a3d6c 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -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). diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 89563500569f..8aa35595d9f8 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -358,7 +358,6 @@ export default async function createConfigAsync() { ], [ 'ideal-image', - { quality: 70, max: 1030, diff --git a/yarn.lock b/yarn.lock index a942e9c85636..3efea218d6f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"