FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

perf(core): cache media query matches between device changes by edusperoni · Pull Request #11512 · NativeScript/NativeScript · GitHub

Repository navigation

perf(core): cache media query matches between device changes - #11512

Draft
edusperoni wants to merge 2 commits into
mainfrom
perf/cache-media-query-matches
Draft

edusperoni wants to merge 2 commits into
mainfrom
perf/cache-media-query-matches

Conversation

edusperoni commented Oct 7, 2026 •
edited
Loading

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

Every CSS state change on a view (a class added or removed, a pseudo-class toggled) re-collects selector candidates, and for each @media selector scope in the stylesheet that means calling checkIfMediaQueryMatches. That function reads Screen.mainScreen.widthPixels / heightPixels, the orientation and the system appearance through native getters, and re-parses the media query string with regexes, on every call, before it even looks at which features the query uses.

collectCandidates only remembers queries that matched, in an array created fresh per view, so a query that does not match is re-evaluated for every scope that uses it, on every view, on every style pass. A query that uses a feature core does not evaluate at all (for example the @media (color-gamut: p3) blocks that Tailwind 4 and postcss-preset-env emit as fallbacks) is treated the same way: it returns false, so it is never cached, and it is re-checked forever even though it can never become true.

In a profile of a screen with ~500 labels whose classes change together, with 52 such media scopes in the stylesheet, ~80% of the main-thread time of each interaction was inside checkIfMediaQueryMatches (native screen/appearance getters plus parseQuery); the actual selector matching was under 2%.

What is the new behavior?

Two commits:

1. Cache match results between device changes

  • media-query-list: match results are cached per query string. The cache is cleared on the application orientationChanged and systemAppearanceChanged events (the same events MediaQueryList already uses for change notifications), so results stay correct across rotations and dark/light switches. onDeviceChange also clears the cache before re-evaluating the registered lists, so listener order cannot leave a list reading a stale result.
  • css-mediaquery: matchQuery memoizes the parsed form of each query string. parseQuery itself is unchanged and still throws on invalid input, which is never cached.
  • application/helpers-common: toggleApplicationEventListeners now queues listeners requested before the platform installs its handler and replays them in setToggleApplicationEventListenersCallback. This guarantees the cache invalidation hook is registered even when a media query is evaluated before application.ios / application.android finishes loading, and also fixes the pre-existing case where a MediaQueryList created that early never received change events.

2. Drop unevaluable media scopes and read device state lazily

  • css-mediaquery: new isEvaluableMediaQuery(query) returns whether a query parses and uses only features the runtime evaluates (width, height, device-width, device-height, orientation, prefers-color-scheme, with min-/max- prefixes).
  • style-scope: _populateRules skips an @media block (and anything nested in it) when its query is not evaluable, with one trace warning in the MediaQuery category. Such a block could never match, so this changes no rendering; it only stops the block from being re-checked on every style pass. @supports blocks were already dropped this way.
  • media-query-list: checkIfMediaQueryMatches checks evaluability first and returns false for an unevaluable query without touching native state. For evaluable queries the values object uses lazy getters, so a width query no longer reads orientation or appearance, an orientation query no longer resolves the screen, and the screen is resolved once per evaluation (shared by width and device-width) rather than once per feature.

Unit tests cover: cache reuse and invalidation on device change, MediaQueryList notifications staying in sync with the cache, the pending-listener replay, isEvaluableMediaQuery accept/reject cases, zero device reads for unevaluable queries, feature-driven reads for evaluable ones, and scope dropping (top-level and nested) in _populateRules.

Not covered: a change in Screen.mainScreen metrics that is not accompanied by an orientation or appearance event (an external display connecting, for example). Previously such a change was picked up on the next style pass; now it is picked up on the next orientation or appearance change.

Every CSS state change re-evaluated each @media selector scope through the
native screen and application property getters and re-parsed the query
string, so a view tree with many media-scoped rules paid thousands of native
calls per class change.

Match results are now cached and cleared on orientation and system
appearance changes, parsed queries are memoized, and application event
listeners requested before the platform installs its handler are replayed
once it arrives so the invalidation hook is never lost.

nx-cloud Bot commented Oct 7, 2026 •
edited
Loading

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit a243425

Command Status Duration Result
nx run-many -t build -p core,webpack5,vite ✅ Succeeded 24s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-07 18:36:11 UTC

pkg-pr-new Bot commented Oct 7, 2026 •
edited
Loading

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11512
npm i https://pkg.pr.new/@nativescript/vite@11512
npm i https://pkg.pr.new/@nativescript/webpack@11512

commit: a243425

A media query that fails to parse or names a feature the runtime does not
evaluate (color-gamut, hover, prefers-reduced-motion, ...) can never match,
so its rules are now dropped when the stylesheet is built instead of being
re-checked on every style pass.

For the queries that remain, device state is read only when a feature asks
for it: a width query no longer reads the orientation and appearance, an
orientation query no longer resolves the screen, and the screen is resolved
once per evaluation rather than once per feature.
NathanWalker added this to the 9.2 milestone Oct 8, 2026

This branch has not been deployed

No deployments
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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL