6.4.3: `PolymorphicCallProps` rewrite causes 57× more `ComponentPropsWithRef` instantiations, +1.6 GB tsc heap in our app
Hi, thanks for maintaining styled-components.
Filing this because in our monorepo the 6.4.2 → 6.4.3 bump added enough tsc pressure to start OOMing our dev-server type checker. Feel free to close if you knew about the trade-off and decided it was worthwhile so this is WAI.
We bisected on the same commit and same lockfile, swapping only styled-components between 6.4.2 and 6.4.3, running tsc --extendedDiagnostics --noEmit cold each time (deleted .tsbuildinfo before every run, alternated the order A→B→A→B to rule out caching artifacts):
| Metric | 6.4.2 (run 1) | 6.4.3 (run 1) | 6.4.2 (run 2) | 6.4.3 (run 2) |
|---|---|---|---|---|
| Types | 1,487,074 | 1,892,984 | 1,487,074 | 1,892,984 |
| Instantiations | 33,223,024 | 47,784,078 | 33,223,024 | 47,784,078 |
| Symbols | 3,701,255 | 3,703,629 | 3,701,255 | 3,703,629 |
| Memory used | 3,427 MB | 5,244 MB | 3,555 MB | 5,133 MB |
| Check time | 70.0s | 91.4s | 70.9s | 103.2s |
Structural metrics are bit-identical per-version; memory/time jitter run-to-run as expected. Net effect of the 6.4.2 → 6.4.3 bump:
- +406,282 types (+27%)
- +14,561,054 instantiations (+44%)
- +1.6 GB heap (+45%)
- +22–33s check time (+30–45%)
We used --generateTrace to attribute the growth. It is not spread across the codebase — it is concentrated in a single symbol:
Symbol (from @types/react) |
6.4.2 | 6.4.3 | Delta |
|---|---|---|---|
ComponentPropsWithRef |
4,428 | 252,248 | +247,820 (57×) |
By flag: +258,286 new Conditional types and +146,454 new Intersection types, essentially entirely rooted in @types/react instantiations from styled-components.
Comparing styled-components/dist/types.d.ts between the two versions, the change is the new PolymorphicCallProps alias and the widened AsTarget bound in PolymorphicComponentProps (referenced in your own comment as ~6% more instantiations across call sites):
// 6.4.3 additions
export type PolymorphicComponentProps<R, BaseProps,
AsTarget extends StyledTarget<R> | (BaseProps extends { as?: infer A } ? A : never) | void,
ForwardedAsTarget extends StyledTarget<R> | void,
AsTargetProps extends BaseObject = AsTarget extends KnownTarget ? React.ComponentPropsWithRef<AsTarget> : {},
ForwardedAsTargetProps extends BaseObject = ForwardedAsTarget extends KnownTarget ? React.ComponentPropsWithRef<ForwardedAsTarget> : {},
> = ...
type PolymorphicCallProps<R, BaseProps, AsTarget, ForwardedAsTarget> =
[AsTarget] extends [string | AnyComponent] ? PolymorphicComponentProps<...> & { as: AsTarget }
: [ForwardedAsTarget] extends [void] ? OverrideStyle<...>
: PolymorphicComponentProps<R, BaseProps, void, ForwardedAsTarget> & { forwardedAs: ForwardedAsTarget }; Every JSX usage of a styled component now goes through the three-branch conditional, and each branch that lands on the target-props branch re-instantiates React.ComponentPropsWithRef (already a known TS hotspot). The comment on the type acknowledges the tradeoff for
KnownTarget narrowing but the multiplier we see in a real app is much larger than 6%.
Scale of usage on our side: a large SPA, heavy styled(Component) and styled.div usage across ~2000 components.
Repro: any large codebase with heavy styled() usage; alternate installs of 6.4.2 and 6.4.3 with .tsbuildinfo deleted each time, run tsc --extendedDiagnostics, and diff the numbers. ComponentPropsWithRef count via --generateTrace should show the jump.
Mitigation on our side: pinned to 6.4.2 for now.
Source: styled-components/styled-components