Skip to content

Review/refactor batch - #2274

Merged
mg901 merged 15 commits into
masterfrom
review/refactor-batch
Aug 27, 2026
Merged

Review/refactor batch#2274
mg901 merged 15 commits into
masterfrom
review/refactor-batch

Conversation

@mg901

@mg901 mg901 commented Aug 27, 2026

Copy link
Copy Markdown
Owner

No description provided.

mg901 added 15 commits August 27, 2026 10:57
React calls getSnapshot on every render, and the previous implementation
built a fresh MediaQueryList through window.matchMedia on each of those
calls. Measured on a single hook instance: 4 matchMedia calls on mount and
2 more on every re-render (doubled again under StrictMode).

Caching the MediaQueryList in a ref, keyed by the normalized query, brings
that down to 1 call on mount and 0 per re-render. The list is still resolved
lazily inside subscribe/getSnapshot, so the hook remains server-safe, and
the cache is per hook instance, so it never outlives the component or leaks
between environments.

Also drops the duplicate normalize() pass that ran on every subscribe and
every snapshot.

Costs 33 B brotlied, so the size budget moves from 145 B to 180 B.
breakpoints.keys was typed as readonly string[], so it could not be fed
back into the API it belongs to:

  theme.breakpoints.keys.map((k) => theme.breakpoints.up(k))
  // TS2345: 'string' is not assignable to '"xs" | "sm" | ... | "xxl"'

Narrowing it to readonly (keyof T & string)[] makes iterating over the
breakpoints type-check without a cast, and the narrowed type reaches
consumers through the published declaration file.

buildBreakpointsMap now carries the key type through sort/map instead of
letting Object.fromEntries widen everything to any, so the one remaining
assertion is the unavoidable Object.entries one.
The wrapper built its methods by looping over Object.keys(validators) and
spreading Object.fromEntries(...) into the theme. That spread is typed any,
so the declared StyledBreakpointsTheme<T> return type was never enforced:
replacing a method body with `return 42` compiled cleanly. The validators
themselves took (...args: any[]), so nothing downstream was checked either.

Each guarded method is now built explicitly, with its argument tuple inferred
from the method it wraps, and memoize carries that tuple through instead of
collapsing to any[]. The same mistake now fails with TS2322, and the file
contains no `any`.

Validator params became string | undefined, which is what they always received
at runtime for omitted arguments — the arity check already covers that case,
so every error message is byte-for-byte unchanged.

Also drops the buildBreakpointValidators export, which had no callers outside
this module.

Costs ~20 B brotlied: the dev bundle goes 1.52 kB -> 1.54 kB against a
1.55 kB budget, so headroom is now 10 B.
The orientation union was spelled out inline in 10 places and the
`Record<string, \`${number}px\`>` constraint in 3, even though a Values alias
already existed. The type test had to declare its own Orientation because the
library never exported one.

- adds an exported Orientation alias, used everywhere the union appeared
- uses the existing Values alias for both generic constraints
- derives the up/down/between/only implementations from ThemeBreakpoints<T>
  instead of respelling each parameter list in create-theme
- gives withValidation an explicit StyledBreakpointsTheme<T> return type
- re-exports Config, Orientation, StyledBreakpointsTheme, ThemeBreakpoints
  and Values from the public entry

Occurrences drop from 10 to 1 and from 3 to 1. The generated declaration file
now returns the named StyledBreakpointsTheme<T> instead of an inlined shape,
and all 47 type-level assertions still pass unchanged, so the public type
contract is identical.
validateConfig threw inside its own try block purely so the catch could
re-throw the same message with the error prefix attached. That required
silencing the preserve-caught-error lint rule, and the catch-all swallowed
genuine failures too — an unexpected TypeError would have been reported to
users as "Theme configuration failed".

The checks now return the issue as a string and validateConfig throws once,
at the top. No try/catch, no eslint-disable, and an unexpected error
propagates with its own stack instead of being relabelled.

All eight configuration error snapshots are unchanged. The slightly
misleading wording for { breakpoints: {} } — reported as "breakpoints" must
be defined even though the key is present but empty — is left as is, since
changing user-facing error text is a separate decision.
toQuoted was exported but had no callers outside formatters.ts, where it is
only reached through toQuotedList. Dropping the export makes the module's
surface match what it actually offers.

No size change — tree-shaking already kept the function.
Reverts the parts of the previous commit that put named aliases into the
generated declaration file. Orientation, the Values constraint on the public
entry, and an explicit StyledBreakpointsTheme<T> return type on withValidation
all made hovers and signature help show a bare alias name, so a caller could no
longer see which orientation values are accepted or what the factory returns
without chasing definitions.

What stays from that commit is the part that is invisible to consumers:
create-theme derives up/down/between/only from ThemeBreakpoints<T> rather than
respelling every parameter list, and uses the Values alias for its internal
generic constraint.

types.ts and with-validation.ts now carry comments explaining why the inline
union and the inline constraint must not be factored out.

Verified: dist/dev/index.d.ts is byte-identical to master.
The caching added in the previous commit had no permanent coverage — it was
verified with a throwaway probe that counted window.matchMedia calls and then
deleted. Nothing stopped a later change from moving the matchMedia call back
into getSnapshot.

Two tests now pin both directions:
- re-rendering must not resolve another MediaQueryList. Against the previous
  implementation this fails with 8 calls growing to 16 over two re-renders
  (StrictMode doubles React's getSnapshot calls).
- changing the query must resolve a new one, so the cache cannot go stale.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@codspeed-hq

codspeed-hq Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 10 untouched benchmarks


Comparing review/refactor-batch (2ad39bc) with master (b200aae)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (653f594) during the generation of this report, so b200aae was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b200aae) to head (2ad39bc).
⚠️ Report is 2813 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #2274   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           10        10           
  Lines          163       163           
  Branches        27        29    +2     
=========================================
  Hits           163       163           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mg901
mg901 merged commit a2bd3a0 into master Aug 27, 2026
7 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 15.0.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant