diff --git a/.changeset/late-timers-repair.md b/.changeset/late-timers-repair.md new file mode 100644 index 000000000000..ed812dd5fdb4 --- /dev/null +++ b/.changeset/late-timers-repair.md @@ -0,0 +1,7 @@ +--- +'@modern-js/builder-rspack-provider': patch +--- + +chore(builder): support the use of the RSPACK_PROFILE environment variable for Rspack build performance profile + +chore(builder):支持使用 RSPACK_PROFILE 环境变量来进行 Rspack 构建性能分析 diff --git a/packages/builder/builder-rspack-provider/src/plugins/rspack-profile.ts b/packages/builder/builder-rspack-provider/src/plugins/rspack-profile.ts new file mode 100644 index 000000000000..0acc2609601c --- /dev/null +++ b/packages/builder/builder-rspack-provider/src/plugins/rspack-profile.ts @@ -0,0 +1,103 @@ +import type { BuilderPlugin } from '../types'; +import path from 'path'; +import { + experimental_registerGlobalTrace as registerGlobalTrace, + experimental_cleanupGlobalTrace as cleanupGlobalTrace, +} from '@rspack/core'; +// eslint-disable-next-line node/no-unsupported-features/node-builtins +import inspector from 'inspector'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { fs } from '@modern-js/utils'; +import { logger } from '@modern-js/builder-shared'; + +export const stopProfiler = ( + output: string, + profileSession?: inspector.Session, +) => { + if (!profileSession) { + return; + } + profileSession.post('Profiler.stop', (error, param) => { + if (error) { + logger.error('Failed to generate JS CPU profile:', error); + return; + } + fs.writeFileSync(output, JSON.stringify(param.profile)); + }); +}; + +// Reference rspack-cli +// https://github.com/modern-js-dev/rspack/blob/509abcfc523bc20125459f5d428dc1645751700c/packages/rspack-cli/src/utils/profile.ts +export const builderPluginRspackProfile = (): BuilderPlugin => ({ + name: 'builder-plugin-rspack-profile', + + setup(api) { + /** + * RSPACK_PROFILE=ALL + * RSPACK_PROFILE=TRACE|CPU|LOGGING + */ + const RSPACK_PROFILE = process.env.RSPACK_PROFILE?.toUpperCase(); + + if (!RSPACK_PROFILE) { + return; + } + + const timestamp = Date.now(); + const profileDir = path.join( + api.context.distPath, + `rspack-profile-${timestamp}`, + ); + + let profileSession: inspector.Session | undefined; + + const enableProfileTrace = + RSPACK_PROFILE === 'ALL' || RSPACK_PROFILE.includes('TRACE'); + + const enableCPUProfile = + RSPACK_PROFILE === 'ALL' || RSPACK_PROFILE.includes('CPU'); + + const enableLogging = + RSPACK_PROFILE === 'ALL' || RSPACK_PROFILE.includes('LOGGING'); + + const traceFilePath = path.join(profileDir, 'trace.json'); + const cpuProfilePath = path.join(profileDir, 'jscpuprofile.json'); + const loggingFilePath = path.join(profileDir, 'logging.json'); + + const onStart = () => { + fs.ensureDirSync(profileDir); + + if (enableProfileTrace) { + registerGlobalTrace('trace', 'chrome', traceFilePath); + } + + if (enableCPUProfile) { + profileSession = new inspector.Session(); + profileSession.connect(); + profileSession.post('Profiler.enable'); + profileSession.post('Profiler.start'); + } + }; + + api.onBeforeBuild(onStart); + api.onBeforeStartDevServer(onStart); + + api.onAfterBuild(async ({ stats }) => { + if (enableLogging && stats) { + const logging = stats.toJson({ + all: false, + logging: 'verbose', + loggingTrace: true, + }); + fs.writeFileSync(loggingFilePath, JSON.stringify(logging)); + } + }); + + api.onExit(() => { + enableProfileTrace && cleanupGlobalTrace(); + + stopProfiler(cpuProfilePath, profileSession); + + logger.info(`Saved Rspack profile file to ${profileDir}`); + }); + }, +}); diff --git a/packages/builder/builder-rspack-provider/src/shared/plugin.ts b/packages/builder/builder-rspack-provider/src/shared/plugin.ts index 910396b7c092..8e032aadc6df 100644 --- a/packages/builder/builder-rspack-provider/src/shared/plugin.ts +++ b/packages/builder/builder-rspack-provider/src/shared/plugin.ts @@ -51,5 +51,8 @@ export const applyDefaultPlugins = (plugins: Plugins) => plugins.networkPerformance(), plugins.preloadOrPrefetch(), plugins.performance(), + import('../plugins/rspack-profile').then(m => + m.builderPluginRspackProfile(), + ), import('../plugins/fallback').then(m => m.builderPluginFallback()), // fallback should be the last plugin ]); diff --git a/packages/builder/builder-shared/src/types/stats.ts b/packages/builder/builder-shared/src/types/stats.ts index 6f7d7eaf38f6..8db18750db90 100644 --- a/packages/builder/builder-shared/src/types/stats.ts +++ b/packages/builder/builder-shared/src/types/stats.ts @@ -10,6 +10,8 @@ interface StatsOptionsObj { errors?: boolean; errorsCount?: boolean; colors?: boolean; + logging?: boolean | 'none' | 'error' | 'warn' | 'info' | 'log' | 'verbose'; + loggingTrace?: boolean; /** Rspack not support below opts */ cachedAssets?: boolean; diff --git a/packages/document/builder-doc/docs/en/guide/advanced/rspack-start.mdx b/packages/document/builder-doc/docs/en/guide/advanced/rspack-start.mdx index 4e80bf23c4c1..03f95564c946 100644 --- a/packages/document/builder-doc/docs/en/guide/advanced/rspack-start.mdx +++ b/packages/document/builder-doc/docs/en/guide/advanced/rspack-start.mdx @@ -241,6 +241,20 @@ export default { }; ``` +## Build performance profile + +Builder supports the use of the `RSPACK_PROFILE` environment variable for build performance profile. + +```bash +$ RSPACK_PROFILE=ALL pnpm build +``` + +This command will generate a `rspack-profile-${timestamp}` folder in the dist folder, and it will contain `logging.json`, `trace.json` and `jscpuprofile.json` files. + +- `trace.json`: The time spent on each phase of the Rust side is recorded at a granular level using tracing and can be viewed using [ui.perfetto.dev](https://ui.perfetto.dev/) +- `jscpuprofile.json`: The time spent at each stage on the JavaScript side is recorded at a granular level using [Node.js inspector](https://nodejs.org/dist/latest-v18.x/docs/api/inspector.html) and can be viewed using [speedscope.app](https://www.speedscope.app/) +- `logging.json`: Includes some logging information that keeps a coarse-grained record of how long each phase of the build took + ## Compare with Rspack CLI Builder Rspack build mode comparison [Rspack CLI](https://www.rspack.dev/guide/quick-start.html#using-the-rspack-cli) adds a lot of out-of-the-box capabilities. At the same time, these capabilities will bring a certain degree of performance overhead and behavioral differences: diff --git a/packages/document/builder-doc/docs/zh/guide/advanced/rspack-start.mdx b/packages/document/builder-doc/docs/zh/guide/advanced/rspack-start.mdx index 45dcc78d26f6..5f13bb4541a3 100644 --- a/packages/document/builder-doc/docs/zh/guide/advanced/rspack-start.mdx +++ b/packages/document/builder-doc/docs/zh/guide/advanced/rspack-start.mdx @@ -239,6 +239,20 @@ export default { }; ``` +## Rspack 性能分析 + +Builder 支持使用 `RSPACK_PROFILE` 环境变量来进行构建性能分析。 + +```bash +$ RSPACK_PROFILE=ALL pnpm build +``` + +执行该命令后会在当前产物目录下生成一个 `rspack-profile-${timestamp}` 文件夹,该文件夹下会包含 `logging.json`、`trace.json` 和 `jscpuprofile.json` 三个文件 + +- `trace.json`:使用 tracing 细粒度地记录了 Rust 侧各个阶段的耗时,可以使用 [ui.perfetto.dev](https://ui.perfetto.dev/) 进行查看 +- `jscpuprofile.json`:使用 [Node.js inspector](https://nodejs.org/dist/latest-v18.x/docs/api/inspector.html) 细粒度地记录了 JavaScript 侧的各个阶段的耗时,可以使用 [speedscope.app](https://www.speedscope.app/) 进行查看 +- `logging.json`:包含一些日志信息,粗粒度地记录了构建的各个阶段耗时 + ## 对比 Rspack CLI Builder Rspack 构建模式对比 [Rspack CLI](https://www.rspack.dev/zh/guide/quick-start.html#%E4%BD%BF%E7%94%A8-rspack-cli) 添加了很多开箱即用的能力,同时,这些能力的封装会带来一定程度上的性能开销和行为表现上的差异: