perf(storybook): stop action spies from serializing DOM events - #1288
Merged
Conversation
Storybook auto-wires an `action()` spy to every `on*` prop it finds in a component's argTypes. React Aria calls those spies with the raw React synthetic event, and the preview channel then serializes the payload with telejson at `maxDepth: 15` — once for the postMessage transport, and again for the dev-server websocket, which hardcodes that depth and ignores per-event options. A synthetic event reaches `nativeEvent`, `target` and `_targetInst` (the React fiber), so that walk drags in the whole component tree: ~174k `JSON.stringify` calls and ~435k regex tests, i.e. ~600ms of synchronous work on every focus change, in every story. Wrap each action arg so event-like arguments become a compact plain summary before the spy runs. Wrapping from the outside keeps this independent of both transports' depth settings. Forms/TextInput focus change: ~1150ms -> ~1.0ms. Forms/ColorPicker focus change: ~570ms -> ~1.0ms. `JSON.stringify` calls per two focus changes: 683787 -> 56. Dev-server-only; a production build has no channel and no action spies. Verified: focusing a TextInput in a Vite production bundle costs <1.1ms with all 629 `@property` rules still registered in the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 98bb3d7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📦 NPM canary releaseDeployed canary version 0.0.0-canary-6a59f5a. |
Contributor
🧪 Storybook is successfully deployed!
|
Contributor
🏋️ Size limit report
Click here if you want to find out what is changed in this build |
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.
What
Storybook auto-wires an
action()spy to everyon*prop it finds in a component's argTypes (addActionsFromArgTypes). React Aria calls those spies with the raw React synthetic event, and the preview channel then serializes the payload with telejson atmaxDepth: 15— once for the postMessage transport, and again for the dev-server websocket, which hardcodes that depth and ignores per-event options.A synthetic event reaches
nativeEvent,targetand_targetInst(the React fiber), so that walk drags in the entire component tree: ~174kJSON.stringifycalls and ~435k regex tests per focus change, in every story..storybook/actionArgs.jsadds anargsEnhancersentry that wraps each action arg so event-like arguments become a compact plain summary before the spy runs. Wrapping from the outside keeps it independent of both transports' depth settings.Forms/TextInputfocus changeForms/ColorPickerfocus changeJSON.stringifycalls / 2 focus changesWhy
Moving focus between elements cost 570–1250ms of synchronous browser work on any dev Storybook page rendering a Cube component, making forms unusable to develop against.
The original suspect was the 629
@propertyrules the Glaze palette registers. That was ruled out directly:--white-colorstopped resolving to its registered initial value) changed nothing: ~1100ms before, ~1100ms after.getComputedStyle, 0getBoundingClientRect, 0insertRule, 0setAttribute, 0 DOM mutations.:root— gives 2.0ms with the 629 rules and 2.0ms without.Bracketing the event path put the time entirely between the bubble listener on
#cube-ui-kit-rootand the one on#storybook-root(React's root listener), and stack sampling landed inPostMessageTransport.send→ telejsonstringify.Notes for reviewers
TextInputcosts <1.1ms with all 629@propertyrules still registered in the page. No change tosrc/tokens/palette.tsis warranted.{ name: "blur", args: { __event: "blur", target: "input#«r0»[data-qa=\"Input\"]", value: "" } }.expect(args/fn()— zero hits), but a future play function asserting against a live event object would see the summary instead.preview.jsxis not hot-reloaded — a runningpnpm storybookneeds a restart to pick this up..storybook/**is not part of the published package.🤖 Generated with Claude Code
Note
Low Risk
Storybook dev tooling only; no runtime library changes, with the only behavioral nuance being summarized action args in the Actions panel.
Overview
Fixes severe Storybook dev lag on focus changes (hundreds of ms per interaction) caused by telejson walking full React synthetic events when auto-wired Actions spies fire.
Adds
.storybook/actionArgs.jswith anargsEnhancershook that wraps eachisActionhandler so event-like and DOM node arguments are replaced with compact summaries (event type, target selector, key/value) before the spy runs and the preview channel serializes them.preview.jsxre-exports that enhancer.Dev-only — no published package behavior change; empty changeset for
.storybook/**. Actions panel still works; recorded args are summaries instead of live events (no stories assert on raw event objects today).Reviewed by Cursor Bugbot for commit 98bb3d7. Bugbot is set up for automated code reviews on this repo. Configure here.