Release: Prerelease 10.4.0-alpha.17#34716
Closed
github-actions[bot] wants to merge 70 commits into
Closed
Conversation
Registers .vue and .svelte parser plugins via the experimental_importParsers preset hook so single-file components become walkable in Storybook's change-detection dependency graph. Each parser cracks the SFC container with the framework's native compiler (vue/compiler-sfc, svelte/compiler), delegates script-block parsing back to the built-in oxc wrapper, and dedupes edges across <script>/<script setup>/<script module> blocks. Compiler deps are lazy-loaded so projects that opt out of change detection do not pay the import cost. vue/compiler-sfc is accessed as a deep import on the `vue` peer dep (available since Vue 3.2.13), avoiding an explicit @vue/compiler-sfc dependency. Also exposes ChangeDetectionFailureError and ChangeDetectionUnavailableError on storybook/internal/core-server so plugin code can throw the class the change-detection service already catches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `onModuleGraphChange` builder hook and its polling-based Vite module graph walker are superseded by the oxc-resolver–based change-detection service introduced in PR-A, which subscribes to raw file events via `changeDetectionAdapter` and builds its own graph independently of Vite's internal module graph. Removes: - `Builder.onModuleGraphChange` and the `ModuleGraph`/`ModuleGraphChangeEvent`/ `ModuleNode` types from storybook/internal/types - The `buildModuleGraph` walker over Vite's `moduleGraph.fileToModulesMap` - All polling/debounce/listener machinery in `@storybook/builder-vite` - The companion test suite (replaced by change-detection-adapter tests) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dev-only profiler gated on `STORYBOOK_CHANGE_DETECTION_PROFILE=1` that emits one-line summaries for every dependency-graph build and every IncrementalPatcher patch, plus per-operation counters for files parsed, specifiers resolved, and parser dispatches by file extension. The profiler is a zero-cost no-op when the env flag is unset. Introduces a vitest benchmark at `code/core/src/core-server/change-detection/ChangeDetectionService.bench.ts` that synthesises fixture projects of (N, D) — N stories each importing a linear chain of D dep modules — and measures (a) a cold dependency-graph build and (b) a single IncrementalPatcher.patch round-trip on a warm graph. The CI matrix is capped at N=500/D=3; the worst-case N=5000/D=10 matrix runs locally with `STORYBOOK_CD_BENCH_BIG=1`. Baseline numbers on the current implementation (M-series Mac): N=50 D=1: cold build 4.22 ms mean / patch warm 0.142 ms N=50 D=3: cold build 3.85 ms mean / patch warm 0.151 ms N=500 D=1: cold build 37.40 ms mean / patch warm 0.120 ms N=500 D=3: cold build 37.55 ms mean / patch warm 0.152 ms Vitest config changes support running benches against the `core` project without resolving oxc-parser/oxc-resolver through their WASM browser entry points, and exclude `.bench.ts` files from the browser-based `storybook-ui` project. No behavioural change; every code path remains untouched when the profile env var is absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vel substring prefilter
`collectRequireSpecifiers` recursively visits every own-property of every
AST node because oxc-parser's EcmaScriptModule does not surface require()
calls separately. On modern code the walk is nearly always wasted: `.mjs`
and `.mts` files cannot contain a CommonJS `require` edge at all, and
other JS/TS extensions almost never do.
Two cheap gates before paying the recursive walk:
1. Extension check for `.mjs`/`.mts` — skip unconditionally.
2. `source.includes('require(')` substring prefilter for everything else —
a literal `require(` call MUST include that exact token.
Both gates preserve the edge-set exactly; no change for files that really
do contain `require(...)` calls. Existing `.cjs` / `.js` require tests
remain green.
Measured deltas on the synthesized bench (mean ms, vitest bench --run):
cold build patch warm
N=50 D=1 4.22 → 3.72 (-11.8%) 0.142 → 0.110 (-22.5%)
N=50 D=3 3.85 → 3.66 ( -4.9%) 0.151 → 0.135 (-10.6%)
N=500 D=1 37.40 → 35.12 ( -6.1%) 0.120 → 0.109 ( -9.2%)
N=500 D=3 37.55 → 35.05 ( -6.7%) 0.152 → 0.126 (-17.1%)
Patch deltas are the most signal-rich metric (the patch path re-parses a
single file, so the require-walk share of total work is highest there).
Cold build deltas compress at large N because parse time is dominated by
oxc-parser itself, not the JS-level walk.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`walkFromStory` dedupes parsing via a shared `parseCache`, but every story
walk that reaches a shared module was re-running `resolver.resolve()` for
every one of its imports. Running the profiler on a real project showed
the pathological case: 274 stories, 827 parsed files, **111,456 resolver
calls** — roughly 135× redundancy, driven by component-library modules
imported by hundreds of stories.
Introduces a `resolveCache: Map<string, Promise<Set<string>>>` that
hoists the parse + resolve + in-scope filter to once per unique file.
Subsequent story walks that reach the same file `await` the cached
Promise and just enumerate its resolved Set to enqueue their per-story
reverse-index entries. The graph's `Map<string, Set<string>>` shape is
unchanged.
Correctness is covered by a new test asserting that a module imported
by two stories has its outgoing resolver call invoked exactly once:
await builder.build([storyA, storyB]);
expect(sharedEdgeResolves).toHaveLength(1); // was 2
Also downgrades the "Could not resolve" warn to debug in
`IncrementalPatcher` — matches the builder's level for the same message
and avoids noisy warnings for legitimately unresolvable specifiers
(CSS/asset imports that the walker already treats as opaque leaves).
Adds a `shared=20` variant to the synth bench matrix so the scenario
that motivates this change is reproducible. The synth bench does not
show a large delta because vitest bench iterations warm oxc-resolver's
native cache between iterations; the win materialises on true cold-boot
where each resolve is uncached. Real-project measurement is the arbiter
here — the test proves the algorithmic fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ready The `STORY_RENDERED` / `DOCS_PREPARED` channel gate meant the change- detection graph build did not kick off until the preview iframe had loaded, requested `/index.json`, loaded a story module, and rendered it. On real projects that is 5–6 seconds of pure waiting before the graph build even starts — the user sees no status badges for most of Storybook's cold-start window. The preview builder's `changeDetectionAdapter()` is ready as soon as `previewBuilder.start()` resolves (the Vite dev server is up at that point). Nothing in the graph build or the scan depends on a story having been rendered — indexing is driven by `storyIndexGeneratorPromise` which is awaited inside `ChangeDetectionService.startInternal` and already runs independently of any browser request. Starting change detection right when the adapter becomes ready parallelises the graph build (≈600 ms after the resolver-cache fix) with the preview's first transform pass and first story render. Wall-clock time to first status badge drops from ~7–8 s to roughly the max of (preview render time, ~600 ms build), which on a warm machine is essentially "as fast as the preview itself can render". No behavioural change beyond the removal of the wait — the adapter, service, and error-handling paths are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the `STORY_RENDERED` gate was removed, the dependency-graph build starts right when the preview builder resolves — in parallel with the preview's first transform and first story render. That made latency worse in practice, because `oxc-parser.parse` does its CPU work on the main thread (the native addon posts work to its own pool, but the returned promise is resolved with a sync-feeling callback and every parse we schedule still eats Node's main event loop for long enough that the dev-server's WebSocket channel stalls, pushing the preview's first render behind the scan instead of alongside it). Move the per-file parse into a small `worker_threads` pool. Size defaults to `min(4, max(1, cpus()-1))` and is tunable via `STORYBOOK_CHANGE_DETECTION_WORKERS`; pool usage can be opted out with `STORYBOOK_CHANGE_DETECTION_NO_WORKER=1`. The pool is lazy-initialised on first use, attached as a module singleton, and torn down from `ChangeDetectionService.dispose()`. Both `oxcImportParser.parse` and `ParserRegistry.parseScriptWithOxc` route through the pool, which means Vue/Svelte/MDX plugin parsers that call `ctx.parseScriptWithOxc` also get off-thread treatment for SFC script blocks. Bundling wrinkle: the pool module is inlined into `dist/core-server/index.js` at build time, so `import.meta.url` resolves to the bundle rather than the source path. The pool probes two candidate locations — direct sibling, and the descendant path where the dedicated worker entry actually ships — so it works in both bundled and unbundled (source) layouts. A `STORYBOOK_CHANGE_DETECTION_WORKER_PATH` override is available for bench and custom setups. Fixture bench (`N=500 D=3 shared=20`, worker pool on via path override): cold build 127 ms → 90 ms (~29% faster wall-clock). Patch-on-warm goes 0.10 ms → 0.14 ms, which is the worker-message round-trip; negligible for a single-file save. The bigger win is invisible in the bench: the main thread stays responsive during the build, so channel traffic and the preview's first render are no longer sequenced behind parsing.
- Implemented a test for recovering from a scenario where a changed file is not recorded in the reverse index. - Enhanced the IncrementalPatcher to re-walk stories when a previously unrecorded helper file is added. refactor: update parser registry to remove unused methods - Removed the walkableExtensions method from ParserRegistry as it was not utilized. - Cleaned up related tests to reflect the removal of walkableExtensions. feat: introduce scope utility functions for workspace detection - Added isInsideAnyWorkspace and isInScope functions to determine if a file is within a workspace or project scope. fix: improve error handling in OxcWorkerPool - Enhanced the OxcWorkerPool to reject only the tasks associated with a crashed worker. - Implemented a timeout mechanism for tasks to prevent hanging. test: expand OxcWorkerPool tests for robustness - Added tests for worker failure scenarios, including handling of hung tasks and unexpected exits. - Improved the test structure to ensure proper cleanup and state management. chore: update change detection benchmarks - Removed placeholders for single-edit and bulk-edit scenarios in the change detection benchmark script. - Adjusted reporting scripts to reflect the changes in benchmark results.
Removes five env-var overrides in favour of sensible module-level defaults: - STORYBOOK_CHANGE_DETECTION_NO_WORKER — inline fallback already kicks in automatically when the compiled worker script isn't on disk; no separate opt-out is needed. - STORYBOOK_CHANGE_DETECTION_WORKERS — pool size is min(4, cpus()-1). - STORYBOOK_CHANGE_DETECTION_WORKER_PATH — resolveWorkerScriptPath probes the two import.meta.url-relative candidates; bundling regressions are bugs, not configuration knobs. - STORYBOOK_CHANGE_DETECTION_WORKER_TIMEOUT_MS — hardcoded 30 s; the constructor still accepts a third arg so the regression test can pass 50 ms directly. - STORYBOOK_CHANGE_DETECTION_REQUIRE_WORKER — bench-only guard removed along with its getOxcParsePool import; getOxcParsePool is no longer re-exported from workers/index.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implement forward index (story -> Set<dep>) in ReverseIndex for O(N_story_deps) removeStory performance. - Optimize IncrementalPatcher to skip re-walking dependent stories when dependencies are unchanged. - Enhance MDX parser to capture 'export ... from' declarations. - Add unit test for MDX export extraction.
- Extract shared walkFromStory helper used by both DependencyGraphBuilder and IncrementalPatcher. - Maintain an inverse importer index in IncrementalPatcher so direct-importer recovery is O(1) instead of scanning the full graph on every non-story add. - Run per-story re-walks for unlink/change/recovery in parallel via Promise.all (the shared ParseResolveCache makes this safe). - Cache getStoryIdsByAbsolutePath by storyIndex identity so debounced scans don't rebuild the map every fire. - Replace `Math.min(...allEntries.values())` with an explicit loop to avoid the spread allocation and a RangeError on huge dep sets. - Skip the `previousStatuses` Map clone when nothing changed. - Drop the speculative second worker-script path; only one ever existed. - Simplify parseOnce return type from `ImportEdge[] | null` to `ImportEdge[]`. - Remove unused `private graph` on ChangeDetectionService. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Refresh storyFiles in onStoryIndexInvalidated and replay add/unlink events for stories that joined or left the index after startup, so the patcher recognises runtime-added story files. - Split OxcWorkerPool API: acquireOxcParsePool (init + ref) is paired with disposeOxcParsePool by ChangeDetectionService; getOxcParsePool peeks without changing the refcount and is what parseWithOxc consults. Fixes the unbounded refcount growth caused by per-parse acquires. - OxcWorkerPool.dispose now rejects queued tasks instead of silently clearing the queue, so callers can't hang on disposal. - Track oxcPoolAcquired flag on ChangeDetectionService so dispose only releases when an acquire actually succeeded. - Delete the duplicate argMappings.stories.ts (byte-for-byte copy of argMapping.stories.ts). - Update change-detection docs to describe the ChangeDetectionAdapter contract instead of the removed onModuleGraphChange hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the generic oxc parsing infrastructure (worker pool, inline parser, parseWithOxc adapter) out of change-detection/parser-registry/workers/ into a sibling sub-package at code/core/src/oxc-parser/, exposed via storybook/internal/oxc-parser. Change detection now consumes it as one of many possible callers — pure move + rename, no behavior changes. - Introduce OxcParseError (extends StorybookError) so the sub-package no longer depends back into change-detection/errors.ts. - ImportEdge moves to oxc-parser/types.ts; change-detection re-exports it so existing consumers keep working without churn. - WORKER_RELATIVE_PATH updated to '../oxc-parser/worker.js' to match the new dist layout (sibling of dist/core-server/index.js). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical correctness: - ParseResolveCache: invalidate importers on unlink so re-walks don't re-add the deleted dep from a stale resolve cache. - ChangeDetectionService.start: dispose on startup error and re-check disposed before acquiring the worker pool ref. - ChangeDetectionService.scan: await currentPatch so scans don't observe a transiently empty reverseIndex mid-patch. - ChangeDetectionService.dispose: tear down GitDiffProvider watchers. OxcWorkerPool: - dispose rejects pending/queued tasks before awaiting terminate so callers don't hang if termination stalls. - Respawn-failure with no alive workers drains the queue and goes terminal instead of leaving tasks unresolved. - _resetOxcParsePoolForTesting awaits dispose to clean up worker threads between test cases. Cleanup: - mdx-parse strips fenced and inline code regions before scanning to drop false-positive deps from doc examples. - DependencyGraphBuilder doc comment matches reality (graceful per-file degradation, not all-or-nothing); drop unused concurrency knob. - Svelte parser maps the script lang attribute to the matching virtual extension instead of forcing TS mode. - ResolverFactory.warnedRegexAliases moves to instance state. Docs: clarify Vite is the currently-supported builder and add a Troubleshooting section to change-detection.mdx. Tests: 14 new regression tests across IncrementalPatcher, ChangeDetectionService, OxcWorkerPool, mdx parser, and svelteImportParser.
Removes unjustified complexity flagged by Codex+Gemini review: - Production profiler deleted; benchmarking stays in scripts/bench - Worker-pool refcount singleton + supervisor collapsed to lazy module pool with inline-fallback safety net - IncrementalPatcher: drop inverseImporters / recoverViaDirectImporters and collapse the diff-path to lookup-affected-stories + walkStory - WorkspaceLocator removed; scope check reduces to projectRoot exclusion - STORYBOOK_BENCH_MARKER plumbing removed from service - worker.ts asserts parentPort presence instead of optional-chaining - Naming: isSameData -> deepEqual, currentPatch -> patchQueue - Test cleanup: drop implementation-coupled assertions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the builder-supplied resolve-config interface out of the change-detection adapter into the shared `core-common` types module and rename to `ModuleResolveConfig`. Builder adapters (vite today, webpack/rspack later) consume the same shape, so the type belongs alongside other cross-cutting builder types rather than under change-detection. JSDoc is re-toned to drop oxc-resolver-specific "opaque-leaf" wording. Also strip "change-detection startup" framing from the ParserRegistry / ImportParser JSDoc so the registry is described in terms of its actual contract (default + plugin parsers) rather than its current single consumer.
- oxfmt format fix for svelteImportParser (line-length wrap) - IncrementalPatcher.patch: on 'change' events, resolve the changed file's new dep set and compare against the stored graph entry; return early if the dep set is unchanged (comment-only / logic-only edits skip the BFS re-walk entirely, leaving the graph and reverse-index accurate as-is) - Add test covering the skip-re-walk optimisation path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
yarn deduped @emnapi/core, @emnapi/runtime, and @emnapi/wasi-threads down to a single version each (1.9.2 / 1.2.1), removing stale 1.9.0 entries. Fixes the build--linux CI step that runs git diff --exit-code after install. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace 30-line custom deepEqual with dequal (already a dep) - Fix experimental_importParsers function type: was () => Promise<T>, must be (existing: T[]) => T[] | Promise<T[]> (accumulator pattern) - Guard changeDetectionAdapter?.() probe in dev-server.ts with try/catch so a throwing adapter routes to unavailable instead of crashing startup - Remove stale ADR-F planning refs from ChangeDetectionService.test.ts and ReverseIndex.test.ts - Remove migration-history comment block from ChangeDetectionService.test.ts - Remove "Optional for tests" from constructor options JSDoc - Remove duplicate chokidar event filter comment in switch default (JSDoc already covers it) - Remove duplicate patchQueue serialization inline comment (field JSDoc already covers it) - Enable changeDetection feature flag by default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e changes and updating test cases to use async/await for improved readability.
…nline snapshots to use single quotes for consistency.
…able-high-contrast-border Fix ArgsTable borders not visible in Windows High Contrast Mode
Build: Fix tests by adding formatted diff comparison
06e5458 to
21b12a8
Compare
- dev-server: type adapter, demote stack trace to debug - ParseResolveCache: read/parse failures use debug log - ResolverFactory: shorter regex-alias warn, list moved to debug - builder-vite adapter: simplify event filter with Set + type guard
CI orchestrates the dev server via "yarn task dev -s dev" (selectedTask='dev')
and runs e2e-tests-dev as a separate downstream step. The earlier guard
limiting git init to selectedTask === 'e2e-tests-dev' therefore never matched
on CI, leaving the dev sandbox without a git repo and disabling change
detection ("Change detection unavailable: not a git repository").
Drop the guard and always init the dev sandbox's git repo. The init is
idempotent and Chromatic / publishing flows run in dedicated jobs with
their own checkout, so they are unaffected.
…perf Change Detection: Build module graph with oxc tooling
21b12a8 to
e5648e3
Compare
Core: Add "Clear filters" button and improve filter tooltips
e5648e3 to
faf6bb6
Compare
faf6bb6 to
f399956
Compare
f399956 to
78089ab
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is an automated pull request that bumps the version from
10.4.0-alpha.16to10.4.0-alpha.17.Once this pull request is merged, it will trigger a new release of version
10.4.0-alpha.17.If you're not a core maintainer with permissions to release you can ignore this pull request.
To do
Before merging the PR, there are a few QA steps to go through:
And for each change below:
This is a list of all the PRs merged and commits pushed directly to
next, that will be part of this release:storybook/docs-mdxwith inline implementation #34611storybook/tanstack-reactpackage #34403e56ff0edfe4f83855def36a599b264bdf098a9413e11a5206344d5115debb4d9d1e1775a0d74f553If you've made any changes doing the above QA (change PR titles, revert PRs), manually trigger a re-generation of this PR with this workflow and wait for it to finish. It will wipe your progress in this to do, which is expected.
Feel free to manually commit any changes necessary to this branch after you've done the last re-generation, following the Make Manual Changes section in the docs, especially if you're making changes to the changelog.
When everything above is done:
Generated changelog
10.4.0-alpha.17
storybook/docs-mdxwith inline implementation - #34611, thanks copilot-swe-agent!storybook/tanstack-reactpackage - #34403, thanks huang-julien!