Draft
Conversation
Thorough analysis of styled-components from performance and bundle size perspectives, identifying key optimization opportunities: 1. GroupedTag.indexOfGroup() - O(n) to O(1) complexity 2. Flatten function - reduce allocations with imperative approach 3. Consolidate @emotion dependencies - inline unitless, lazy is-prop-valid 4. Hash function - SIMD-friendly implementation 5. Stylis integration - reduce regex and string operations Each big rock includes problem analysis, proposed solutions, impact estimates, and do's/don'ts for implementation. Co-authored-by: x <[email protected]>
|
Cursor Agent can help with this pull request. Just |
🦋 Changeset detectedLatest commit: b84345e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Implement lazy prefix sum caching for indexOfGroup lookups: - Add indexOfGroupCache Uint32Array to store cumulative rule indices - Track cacheValidUpTo to know which prefix sums are current - Invalidate cache from modified group onward on insertRules/clearGroup - Incrementally rebuild cache on demand during lookups Benchmark results show massive improvements for large apps: indexOfGroup performance (ops/sec): - 50 groups: 44M -> 155M (3.5x faster) - 100 groups: 21M -> 710M (33x faster) - 500 groups: 4M -> 774M (195x faster) - 1000 groups: 2M -> 641M (314x faster) insertRules performance (ms to populate): - 500 groups: 0.19ms -> 0.05ms (3.7x faster) - 1000 groups: 0.52ms -> 0.11ms (4.7x faster) getGroup performance (ops/sec): - 500 groups: 4M -> 38M (9x faster) - 1000 groups: 2M -> 37M (17x faster) Memory overhead: +4 bytes per group slot (negligible) This optimization is particularly impactful for: - Large applications with 500+ styled components - Server-side rendering (SSR) where getGroup is called frequently - Dynamic style updates in complex UIs Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
Replace recursive flatMap-based implementation with imperative flattenInto: - Add flattenInto() helper that pushes directly into a shared result array - Modify objToCssArray() to accept optional result array parameter - Eliminate intermediate array allocations from Array.prototype.concat.apply - Use simple for loops instead of array.map() for iteration Benchmark results show consistent improvements: Simple string arrays: - 10 strings: 1.61μs -> 0.86μs (1.9x faster) - 50 strings: 5.22μs -> 3.41μs (1.5x faster) - 100 strings: 9.90μs -> 6.15μs (1.6x faster) Nested arrays (where flatMap overhead was highest): - depth=2, 25 items: 25.9μs -> 12.2μs (2.1x faster) - depth=3, 64 items: 62.6μs -> 28.5μs (2.2x faster) - depth=4, 81 items: 75.6μs -> 32.2μs (2.4x faster) Mixed interpolations: - 20 items: 5.43μs -> 3.38μs (1.6x faster) - 50 items: 10.5μs -> 8.26μs (1.3x faster) Object style syntax: - Simple (4 props): 1.64μs -> 1.37μs (1.2x faster) - Nested (depth=3): 5.25μs -> 2.69μs (2.0x faster) Additional benefits: - Reduced GC pressure from fewer intermediate allocations - More predictable memory usage pattern - Removed dependency on EMPTY_ARRAY for concat operations Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
Add caching layer for selector RegExp patterns used in self-reference replacement plugin: - Add selectorRegexpCache Map to store compiled regex by selector - Add getSelectorRegexp() function with LRU-like cache eviction - Reset lastIndex before returning cached regex to ensure correct matching - Cache size limited to 512 entries before clearing This optimization eliminates redundant RegExp compilation on every stringifyRules call. For apps with many components using the same selector patterns repeatedly, this reduces regex compilation overhead. The selector regex is used to handle self-reference selectors like '& + &' where the second ampersand should reference the static component class. Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
- Remove GroupIDAllocator: eliminate numeric group ID indirection - Simplify GroupedTag: use Map<string, Range> instead of Uint32Array - Simplify Rehydration: no longer parse/serialize numeric group IDs - Inline @emotion/unitless: remove external dependency (~300 bytes) - Remove unused shallowequal dependency - Remove legacy Symbol.for polyfill check - Compress domElements string representation Bundle size reduction: 282 bytes gzipped (2.3% smaller) - Before: 12,378 bytes gzipped - After: 12,096 bytes gzipped BREAKING CHANGE (internal only): SSR output format changed from data-styled.g1[id="..."] to data-styled.g[id="..."] This only affects rehydration of styles from older versions. Co-authored-by: x <[email protected]>
- Add ssr-benchmark.js for rapid SSR performance testing - Simplify GroupIDAllocator to remove reverseRegister Map - Simplify SSR output format (no numeric group in markers) - Update snapshot for keyframes ordering change - Bundle size: 11.92KB gzipped Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
…mode The getGroup call was being made on every server render, even when not in RSC mode. This was expensive because it retrieves all CSS rules for the component. Now we only call it when IS_RSC is true. Benchmark results (Triangle with 729 unique styles): - Before: 20.07ms renderToString - After: 18.10ms renderToString (10% improvement) SSR microbenchmark improvements: - 100 unique styles: 24% faster than v6 - getStyleTags: 25% faster than v6 Co-authored-by: x <[email protected]>
Co-authored-by: x <[email protected]>
Pass styleSheet and stylis directly to useInjectedStyle instead of calling useStyleSheetContext twice per render. Also adds Emotion to the SSR benchmark for comparison. Production mode results: - Deep Tree: 3.60ms (vs Emotion 2.22ms) - Wide Tree: 17.49ms (vs Emotion 11.65ms) - Triangle: 12.31ms (vs Emotion 4.45ms) Development mode results show ~2x overhead due to dev checks. Co-authored-by: x <[email protected]>
Documents 6 major refactoring opportunities to close the gap with Emotion: 1. Lazy Style Compilation - defer flatten to render time 2. Compiled Style Functions - pre-compile interpolations 3. Context Elimination - skip useContext for default case 4. Monomorphic Factory - consistent object shapes 5. Streaming Hash - compute hash incrementally 6. Skip forwardRef - for React 19+ string tags Current production mode performance: - Deep Tree: 3.59ms (Emotion: 2.23ms) +61% - Wide Tree: 17.72ms (Emotion: 11.87ms) +49% - Triangle: 12.49ms (Emotion: 4.13ms) +202% Co-authored-by: x <[email protected]>
- Add SUPPORTS_REF_AS_PROP constant to detect React 19+ via version parsing - For React 19+ with string tags: use plain function component (refs passed as props) - For React 16-18 or composite components: use React.forwardRef() (backward compatible) - Bundle size slightly reduced: 11.98KB (from 12KB) - Update PERFORMANCE_REFACTORS.md to reflect implementation status Co-authored-by: x <[email protected]>
- Add SUPPORTS_REF_AS_PROP constant to detect React 19+ at runtime - Skip React.forwardRef for string tags in React 19 (refs passed as props) - Use WeakSet registry for reliable isStyledComponent detection - Update isStatelessFunction to exclude styled components - Fix React 19 type compatibility (defaultProps on function components) - Skip legacy renderToNodeStream tests on React 19 (API removed) - Update SSR test snapshots for React 19 element format This is a non-breaking change: - React 16-18: Uses forwardRef as before - React 19+: Uses plain function components for DOM elements Co-authored-by: x <[email protected]>
…chmarks - styled-components tests now use React 18 (via jest moduleNameMapper) - Benchmarks use React 19 for performance testing - SSR benchmark uses module resolution override to ensure consistent React version - Updated pnpm overrides to force @types/react to v18 for type compatibility - Reverted React 19-specific type assertions (not needed for React 18) Co-authored-by: x <[email protected]>
- Skip useContext calls when no StyleSheetManager or ThemeProvider is used - Add styleSheetManagerActive flag in StyleSheetManager - Add themeProviderActive flag and useContextTheme hook in ThemeProvider - Reduces useContext calls from 2 per render to 0 for default case - Update StyledComponent, StyledNativeComponent, and createGlobalStyle to use optimized hooks - Mark Refactor 3 as complete in PERFORMANCE_REFACTORS.md Co-authored-by: x <[email protected]>
- Alphabetized property assignments in createStyledComponent - Updated PERFORMANCE_REFACTORS.md with Refactor 4 investigation results - Monomorphic factory provides minimal benefit over current forwardRef optimization Co-authored-by: x <[email protected]>
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.
Add an optimization exploration document to outline key areas for improving runtime performance and reducing bundle size.
This document details 5 "big rocks" for optimization: improving
GroupedTag.indexOfGroupto O(1), optimizing theflattenfunction to reduce allocations, consolidating@emotiondependencies for bundle size, optimizing the hash function, and streamlining Stylis integration by reducing regex and string operations.