feat: split decorator architecture and add universal entry points#41
feat: split decorator architecture and add universal entry points#41mattcosta7 wants to merge 8 commits intomainfrom
Conversation
Extract PerformanceMonitorCore from the React-specific decorator into a shared core (core/preview-core.ts) that can be used by any framework. Decorator split: - decorators/universal.ts: framework-agnostic decorator using PerformanceMonitorCore directly (browser-level metrics) - decorators/react.tsx: React.Profiler bridge that layers on top for React-specific render profiling - preview.ts: two-decorator stack [withPerformanceMonitor, withReactProfiler] New entry points: - index-universal.ts: universal (non-React) public API - index-react.ts: React-specific public API with Profiler support - preset-universal.ts: Storybook preset for non-React frameworks - preview-universal.ts: preview with universal decorator only - react/performance-decorator.tsx simplified to use PerformanceMonitorCore - performance-panel.tsx: refId keying for composed storybook isolation - package.json: new subpath exports (./universal, ./react, ./components) - New test suite for universal decorator
- Add packages/examples-html with Counter, LayoutThrashing, and WebComponent stories demonstrating the universal (non-React) addon - Update docs site: introduction, setup, collectors, metrics pages with HTML/universal usage details - Update storybook-config with HTML storybook sidebar links - Update CI workflow to build HTML storybook - Add root package.json scripts for HTML examples - Add universal-split changeset (minor)
97bb487 to
af41249
Compare
|
@mattcosta7 This is exactly the architecture we were hoping for. We integrated the addon into Spectrum Web Components (Storybook 10 + One suggestion: It would be helpful if the metrics schema gracefully handled missing groups -- e.g., if the universal decorator doesn't emit React Profiler fields, the panel could hide that section entirely rather than requiring them to be present as zeroes. That way framework-specific adapters don't need to be aware of each other's fields. |
Rajdeepc
left a comment
There was a problem hiding this comment.
This is the right architecture and liked the direction. Let me know if you want to collaborate to get this in. Would be very helpful for non-React projects.
| const createdCore = core | ||
| requestAnimationFrame(() => { | ||
| if (getActiveCore() !== createdCore) return | ||
|
|
||
| const root = document.getElementById('storybook-root') | ||
| if (root) { | ||
| createdCore.observeContainer(root) | ||
| } |
There was a problem hiding this comment.
The stale-core guard is good, but if the story renders but #storybook-root hasn't been populated yet (e.g., async rendering frameworks), root will be null and container observation silently fails. Can you possible add a retry with MutationObserver on document.body or requestAnimationFrame loop (with a bounded retry count) to catch late-mounting roots. This matters for frameworks like Vue/Svelte that may mount asynchronously.
There was a problem hiding this comment.
yes! I added a mutation observer for this here: 0b64f49
| return dirname(fileURLToPath(import.meta.url)) | ||
| } | ||
|
|
||
| export function managerEntries(entry: string[] = []): string[] { |
There was a problem hiding this comment.
No preview entries? The panel UI will be shown but decorator won't be auto a-pplied. We have to manually add withperformanceMonitor in preview.ts. React presets auto-applies both. Can you consider adding a previewAnnotation export to preset-universal.ts that points to preview-universal.ts for parity.
| }) | ||
| } | ||
|
|
||
| return storyFn() as unknown |
There was a problem hiding this comment.
This looses type safety. Can you consider using a generic parameter `Decorator to get proper typing without the cast?
There was a problem hiding this comment.
Yep! I think this was just to silence an eslint warning, we can disable instead - storyFn() returns any since it's framework specified - but safety wise disabling seems more correct than an unknown cast - so done!
Summary
Split the single React-only
withPerformanceMonitordecorator into a two-layer architecture, enabling framework-agnostic performance monitoring.What changed
Core extraction (
core/preview-core.ts)New
PerformanceMonitorCoreclass that encapsulates all browser-level metric collection:Decorator split
decorators/universal.ts— Framework-agnostic decorator usingPerformanceMonitorCoredirectly (no React dependency). Observes#storybook-rootcontainer.decorators/react.tsx— React.Profiler bridge that layers on top for React-specific render profiling.preview.ts— Two-decorator stack:[withPerformanceMonitor, withReactProfiler]New entry points
index-universal.tsindex-react.tspreset-universal.tspreview-universal.tsOther changes
react/performance-decorator.tsx— Simplified to usePerformanceMonitorCoreperformance-panel.tsx—refIdkeying for composed storybook isolationpackage.json— New subpath exports:./universal,./react,./componentsindex.ts— Updated to type-only exports (public API moved toindex-react.ts)performance-decorator-universal.browser.test.ts(11 tests x 3 browsers)Testing
Depends on