diff --git a/.gitignore b/.gitignore index 51636c8c18d4..d44178396ae3 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ website/i18n/**/* #!website/i18n/fr/**/* .netlify + +website/rspack-tracing.json +website/bundler-cpu-profile.json diff --git a/package.json b/package.json index ceb6c4a1893e..57a627693512 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "scripts": { "start": "yarn build:packages && yarn start:website", "start:website": "yarn workspace website start", + "start:website:profile": "DOCUSAURUS_BUNDLER_CPU_PROFILE=true DOCUSAURUS_RSPACK_TRACE=true yarn workspace website start", "start:website:baseUrl": "yarn workspace website start:baseUrl", "start:website:blogOnly": "yarn workspace website start:blogOnly", "start:website:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace website start", @@ -22,6 +23,7 @@ "build": "yarn build:packages && yarn build:website", "build:packages": "lerna run build --no-private", "build:website": "yarn workspace website build", + "build:website:profile": "DOCUSAURUS_BUNDLER_CPU_PROFILE=true DOCUSAURUS_RSPACK_TRACE=true yarn workspace website build", "build:website:baseUrl": "yarn workspace website build:baseUrl", "build:website:blogOnly": "yarn workspace website build:blogOnly", "build:website:deployPreview:testWrap": "yarn workspace website test:swizzle:wrap:ts", diff --git a/packages/docusaurus-faster/src/index.ts b/packages/docusaurus-faster/src/index.ts index cba2c68730f2..ba0b2ae8c900 100644 --- a/packages/docusaurus-faster/src/index.ts +++ b/packages/docusaurus-faster/src/index.ts @@ -11,6 +11,16 @@ import browserslist from 'browserslist'; import {minify as swcHtmlMinifier} from '@swc/html'; import type {JsMinifyOptions, Options as SwcOptions} from '@swc/core'; +// See https://rspack.dev/contribute/development/profiling +// File can be opened with https://ui.perfetto.dev/ +if (process.env.DOCUSAURUS_RSPACK_TRACE) { + Rspack.experiments.globalTrace.register( + 'trace', + 'chrome', + './rspack-tracing.json', + ); +} + export const swcLoader = require.resolve('swc-loader'); export const getSwcLoaderOptions = ({ diff --git a/packages/docusaurus/src/commands/start/start.ts b/packages/docusaurus/src/commands/start/start.ts index 3b4c8d3fde8c..6cf7824b2ab6 100644 --- a/packages/docusaurus/src/commands/start/start.ts +++ b/packages/docusaurus/src/commands/start/start.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import logger from '@docusaurus/logger'; +import logger, {PerfLogger} from '@docusaurus/logger'; import openBrowser from '../utils/openBrowser/openBrowser'; import {setupSiteFileWatchers} from './watcher'; import {createWebpackDevServer} from './webpack'; @@ -21,7 +21,7 @@ export type StartCLIOptions = HostPortOptions & minify?: boolean; }; -export async function start( +async function doStart( siteDirParam: string = '.', cliOptions: Partial = {}, ): Promise { @@ -62,3 +62,10 @@ export async function start( await openBrowser(reloadableSite.getOpenUrl()); } } + +export async function start( + siteDirParam: string = '.', + cliOptions: Partial = {}, +): Promise { + return PerfLogger.async('CLI start', () => doStart(siteDirParam, cliOptions)); +} diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts index 3ab34f87a97e..ecd02d0e0e32 100644 --- a/packages/docusaurus/src/webpack/base.ts +++ b/packages/docusaurus/src/webpack/base.ts @@ -9,13 +9,14 @@ import fs from 'fs-extra'; import path from 'path'; import {getCustomBabelConfigFilePath} from '@docusaurus/babel'; import { + createJsLoaderFactory, getCSSExtractPlugin, getMinimizers, - createJsLoaderFactory, } from '@docusaurus/bundler'; -import {md5Hash, getFileLoaderUtils} from '@docusaurus/utils'; -import {loadThemeAliases, loadDocusaurusAliases} from './aliases'; +import {getFileLoaderUtils, md5Hash} from '@docusaurus/utils'; +import {loadDocusaurusAliases, loadThemeAliases} from './aliases'; +import {BundlerCPUProfilerPlugin} from './plugins/BundlerCPUProfilerPlugin'; import type {Configuration} from 'webpack'; import type { ConfigureWebpackUtils, @@ -339,6 +340,8 @@ export async function createBaseConfig({ // for more reasoning ignoreOrder: true, }), - ], + process.env.DOCUSAURUS_BUNDLER_CPU_PROFILE && + new BundlerCPUProfilerPlugin(), + ].filter(Boolean), }; } diff --git a/packages/docusaurus/src/webpack/plugins/BundlerCPUProfilerPlugin.ts b/packages/docusaurus/src/webpack/plugins/BundlerCPUProfilerPlugin.ts new file mode 100644 index 000000000000..f16fdbd3c9e1 --- /dev/null +++ b/packages/docusaurus/src/webpack/plugins/BundlerCPUProfilerPlugin.ts @@ -0,0 +1,49 @@ +/** + * 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 inspector from 'node:inspector'; +import fs from 'fs-extra'; +import type {Compiler} from 'webpack'; + +// Bundle CPU profiling plugin, contributed by the Rspack team +// Can be opened in https://www.speedscope.app/ +// See also https://github.com/jerrykingxyz/docusaurus/pull/1 +// See also https://github.com/facebook/docusaurus/pull/10985 +export class BundlerCPUProfilerPlugin { + output: string; + + constructor(output?: string) { + this.output = output ?? './bundler-cpu-profile.json'; + } + + apply(compiler: Compiler): void { + const session = new inspector.Session(); + session.connect(); + session.post('Profiler.enable'); + session.post('Profiler.start'); + + // In dev/watch mode, we restart the profiler before each compilation + compiler.hooks.watchRun.tapPromise( + BundlerCPUProfilerPlugin.name, + async () => { + session.post('Profiler.start'); + }, + ); + + compiler.hooks.done.tapPromise(BundlerCPUProfilerPlugin.name, async () => { + session.post('Profiler.stop', (error, param) => { + if (error) { + console.error('Failed to generate JS CPU profile:', error); + return; + } + fs.writeFile(this.output, JSON.stringify(param.profile)).catch( + console.error, + ); + }); + }); + } +}