Core: Improve startup performance by deferring change detection initialization#34498
Core: Improve startup performance by deferring change detection initialization#34498ghengeveld merged 1 commit intonextfrom
Conversation
|
View your CI Pipeline Execution ↗ for commit eaff7fe
☁️ Nx Cloud last updated this comment at |
📝 WalkthroughWalkthroughThe pull request refactors change detection startup from immediate initialization to lazy, event-driven activation. Module-graph listener registration is now allowed after builder initialization. The Vite builder's change subscription lifecycle changed from a "registration closed" gate to a "tracking started" gate with guarded initialization. Dev-server triggers change detection lazily upon story rendering or docs preparation events. Changes
Sequence Diagram(s)sequenceDiagram
participant DevServer as Dev Server
participant Channel as Options Channel
participant ChangeDetectionService as Change Detection<br/>Service
participant ViteBuilder as Vite Builder
participant ModuleGraph as Module Graph
Note over DevServer: changeDetectionStarted = false
Channel->>Channel: emit STORY_RENDERED or<br/>DOCS_PREPARED
Channel-->>DevServer: listener triggered
alt changeDetectionStarted = false
DevServer->>ChangeDetectionService: start(..., features.changeDetection !== false)
Note over DevServer: changeDetectionStarted = true
ChangeDetectionService->>ViteBuilder: startModuleGraphTracking()
alt startModuleGraphTracking guards
ViteBuilder->>ViteBuilder: Validate server + lastBuilderOptions
ViteBuilder->>ViteBuilder: Install debounced watcherChangeHandler
ViteBuilder->>ViteBuilder: startChangeDetection(server, handler)
Note over ViteBuilder: Listener registration now allowed
else Error during startChangeDetection
ViteBuilder-->>DevServer: Error logged + emit unavailable/error
DevServer->>DevServer: setChangeDetectionReadiness({status: error})
end
else changeDetectionStarted = true
DevServer->>DevServer: Skip (already started)
end
ModuleGraph->>ModuleGraph: fileToModulesMap changes
ModuleGraph-->>ViteBuilder: watcher 'change' event
ViteBuilder->>ViteBuilder: debounced watcherChangeHandler
ViteBuilder->>ViteBuilder: buildModuleGraph + notify listeners
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
code/builders/builder-vite/src/index.ts (1)
73-90:⚠️ Potential issue | 🟠 MajorCancel stale startup work across
bail()/ restart.
startChangeDetection()keeps reading the module-levelserverandwatcherChangeHandlerafter async boundaries. Ifbail()runs and a newstart()reinitializes those globals before the old startup settles, the stale continuation can attach the'all'watcher to the new Vite server and double-deliver updates. Please snapshot the current run and ignore post-awaitwork from older runs.Also applies to: 92-115, 183-195
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/builders/builder-vite/src/index.ts` around lines 73 - 90, The startup flow can race: bail() may replace globals (server, watcherChangeHandler) while async work in startChangeDetection() or similar functions (the areas around lines 92-115 and 183-195) continues and attaches watchers to a new server; fix by snapshotting a run identifier at the start of startChangeDetection()/other async startup paths (e.g., capture a local const runId = currentRunId++ or a capturedServer reference) and checking that snapshot immediately after each await before doing any side-effect (attaching watcher, modifying watcherChangeHandler, or using server). Ensure bail() increments/invalidates that runId (or clears the capturedServer) so older continuations return early and do not reattach 'all' handlers to a newer Vite server.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@code/core/src/core-server/dev-server.ts`:
- Line 2: Replace uses of the STORY_RENDERED event with DOCS_PREPARED in this
dev-server code: update any event listeners/subscriptions that wait for
STORY_RENDERED to instead wait for DOCS_PREPARED (the imported symbols
DOCS_PREPARED and STORY_RENDERED are present) so the Vite warmup triggers after
docs are prepared rather than after the first story paint; also update the other
occurrence(s) in the same file where STORY_RENDERED is used (the later listener
around the Vite warmup logic) to use DOCS_PREPARED as well.
---
Outside diff comments:
In `@code/builders/builder-vite/src/index.ts`:
- Around line 73-90: The startup flow can race: bail() may replace globals
(server, watcherChangeHandler) while async work in startChangeDetection() or
similar functions (the areas around lines 92-115 and 183-195) continues and
attaches watchers to a new server; fix by snapshotting a run identifier at the
start of startChangeDetection()/other async startup paths (e.g., capture a local
const runId = currentRunId++ or a capturedServer reference) and checking that
snapshot immediately after each await before doing any side-effect (attaching
watcher, modifying watcherChangeHandler, or using server). Ensure bail()
increments/invalidates that runId (or clears the capturedServer) so older
continuations return early and do not reattach 'all' handlers to a newer Vite
server.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a61ab052-133a-458a-b59d-4826c9bf0b2d
📒 Files selected for processing (3)
code/builders/builder-vite/src/index.test.tscode/builders/builder-vite/src/index.tscode/core/src/core-server/dev-server.ts
What I did
Deferred initialization of the ChangeDetectionService and - more importantly - Vite module graph pre-warming, until whenever the preview has rendered the first story or docs page.
Change detection requires the module graph to include all story files, which we ensure by firing a warmup request for each story file. However, this had significant impact on Storybook's startup speed, due to resource contention (warmup is async, but Node is still single-threaded).
Benchmarks
In Storybook's monorepo,
changeDetectionwas adding significant overhead:With
changeDetection=false, average startup time (till story rendered) was 8.0s. WithchangeDetection=true, this increased to 14.5s on average, a 6.5s (~81%) slowdown.With deferred initialization, the average startup time with
changeDetection=truewas 8.1s, only very slightly (and acceptably) slower than without change detection.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Caution
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the
@storybookjs/coreteam here.core team members can create a canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>Summary by CodeRabbit
Release Notes
New Features
Bug Fixes