1212import dataclasses
1313import functools
1414import json
15+ from collections .abc import Iterable
1516from pathlib import Path
1617from typing import TYPE_CHECKING , TypedDict
1718
@@ -122,67 +123,38 @@ def is_minify_enabled() -> bool:
122123
123124
124125@functools .cache
125- def is_state_minify_enabled () -> bool :
126- """Whether state-id minification is enabled.
127-
128- Returns:
129- ``True`` if ``REFLEX_MINIFY_STATES=enabled`` and ``minify.json`` exists.
130- """
131- from reflex .environment import MinifyMode , environment
132-
133- return (
134- environment .REFLEX_MINIFY_STATES .get () == MinifyMode .ENABLED
135- and get_minify_config () is not None
136- )
137-
126+ def _is_mode_enabled (env_var_name : str ) -> bool :
127+ """Whether the given ``REFLEX_MINIFY_*`` env var is on and a config exists.
138128
139- @ functools . cache
140- def is_event_minify_enabled () -> bool :
141- """Whether event-id minification is enabled .
129+ Args:
130+ env_var_name: The env-var attribute name on
131+ :class:`~reflex.environment.EnvironmentVariables` .
142132
143133 Returns:
144- ``True`` if ``REFLEX_MINIFY_EVENTS=enabled `` and ``minify.json`` exists.
134+ ``True`` if the env var is ``ENABLED `` and ``minify.json`` exists.
145135 """
146136 from reflex .environment import MinifyMode , environment
147137
148- return (
149- environment .REFLEX_MINIFY_EVENTS .get () == MinifyMode .ENABLED
150- and get_minify_config () is not None
151- )
138+ env_var = getattr (environment , env_var_name )
139+ return env_var .get () == MinifyMode .ENABLED and get_minify_config () is not None
152140
153141
154- def get_state_id (state_full_path : str ) -> str | None :
155- """Look up the minified id for a state path.
156-
157- Args:
158- state_full_path: e.g. ``"myapp.state.AppState.UserState"``.
142+ def is_state_minify_enabled () -> bool :
143+ """Whether state-id minification is enabled.
159144
160145 Returns:
161- The minified id, or ``None `` if not configured .
146+ ``True`` if ``REFLEX_MINIFY_STATES=enabled`` and ``minify.json `` exists .
162147 """
163- config = get_minify_config ()
164- if config is None :
165- return None
166- return config ["states" ].get (state_full_path )
148+ return _is_mode_enabled ("REFLEX_MINIFY_STATES" )
167149
168150
169- def get_event_id (state_full_path : str , handler_name : str ) -> str | None :
170- """Look up the minified id for an event handler.
171-
172- Args:
173- state_full_path: The full path to the state.
174- handler_name: The handler's original name.
151+ def is_event_minify_enabled () -> bool :
152+ """Whether event-id minification is enabled.
175153
176154 Returns:
177- The minified id, or ``None `` if not configured .
155+ ``True`` if ``REFLEX_MINIFY_EVENTS=enabled`` and ``minify.json `` exists .
178156 """
179- config = get_minify_config ()
180- if config is None :
181- return None
182- state_events = config ["events" ].get (state_full_path )
183- if state_events is None :
184- return None
185- return state_events .get (handler_name )
157+ return _is_mode_enabled ("REFLEX_MINIFY_EVENTS" )
186158
187159
188160def save_minify_config (config : MinifyConfig ) -> None :
@@ -245,8 +217,22 @@ def from_disk(cls) -> MinifyNameResolver:
245217 events_enabled = environment .REFLEX_MINIFY_EVENTS .get () == MinifyMode .ENABLED ,
246218 )
247219
220+ def _is_minify_allowed (self , state_cls : type [BaseState ], enabled : bool ) -> bool :
221+ """Whether minification applies to ``state_cls`` for the given mode.
222+
223+ Args:
224+ state_cls: The state class being resolved.
225+ enabled: Whether the relevant ``REFLEX_MINIFY_*`` env var is on.
226+
227+ Returns:
228+ ``True`` when the env var is on and ``state_cls`` is user-defined.
229+ """
230+ return enabled and not _is_framework_state (state_cls )
231+
248232 def resolve_state_name (self , state_cls : type [BaseState ]) -> str | None : # noqa: D102
249- if not (self .states_enabled and self .config ) or _is_framework_state (state_cls ):
233+ if self .config is None or not self ._is_minify_allowed (
234+ state_cls , self .states_enabled
235+ ):
250236 return None
251237 cached = self ._state_cache .get (state_cls )
252238 if cached is not None :
@@ -259,7 +245,9 @@ def resolve_state_name(self, state_cls: type[BaseState]) -> str | None: # noqa:
259245 def resolve_handler_name ( # noqa: D102
260246 self , state_cls : type [BaseState ], handler_name : str
261247 ) -> str | None :
262- if not (self .events_enabled and self .config ) or _is_framework_state (state_cls ):
248+ if self .config is None or not self ._is_minify_allowed (
249+ state_cls , self .events_enabled
250+ ):
263251 return None
264252 per_state = self ._event_cache .get (state_cls )
265253 if per_state is None :
@@ -329,8 +317,7 @@ def clear_config_cache() -> None:
329317 ``REFLEX_MINIFY_*`` env vars at runtime.
330318 """
331319 get_minify_config .cache_clear ()
332- is_state_minify_enabled .cache_clear ()
333- is_event_minify_enabled .cache_clear ()
320+ _is_mode_enabled .cache_clear ()
334321 install_minify_resolver ()
335322
336323
@@ -484,6 +471,51 @@ def generate_minify_config(
484471 )
485472
486473
474+ def _find_duplicate_ids (items : Iterable [tuple [str , str ]]) -> dict [str , list [str ]]:
475+ """Group ``(label, minified_id)`` pairs by id, keeping only collisions.
476+
477+ Args:
478+ items: Pairs of ``(label, minified_id)``.
479+
480+ Returns:
481+ Mapping from minified id to the labels sharing it (always ``len >= 2``).
482+ """
483+ by_id : dict [str , list [str ]] = {}
484+ for label , mid in items :
485+ by_id .setdefault (mid , []).append (label )
486+ return {mid : labels for mid , labels in by_id .items () if len (labels ) > 1 }
487+
488+
489+ def _assign_next_ids (
490+ new_keys : Iterable [str ],
491+ existing_ids : set [int ],
492+ reassign_deleted : bool ,
493+ ) -> dict [str , str ]:
494+ """Assign minified ids to ``new_keys`` while skipping ``existing_ids``.
495+
496+ Mutates ``existing_ids`` so callers can chain assignments against the same
497+ pool. Keys are sorted for deterministic output.
498+
499+ Args:
500+ new_keys: Keys needing new ids.
501+ existing_ids: Already-used integer ids in the same scope.
502+ reassign_deleted: When ``True``, scan from 0 (filling gaps);
503+ otherwise start past the current max.
504+
505+ Returns:
506+ Mapping from key to its newly-assigned minified id.
507+ """
508+ next_id = 0 if reassign_deleted else max (existing_ids , default = - 1 ) + 1
509+ out : dict [str , str ] = {}
510+ for key in sorted (new_keys ):
511+ while next_id in existing_ids :
512+ next_id += 1
513+ out [key ] = int_to_minified_name (next_id )
514+ existing_ids .add (next_id )
515+ next_id += 1
516+ return out
517+
518+
487519def validate_minify_config (
488520 config : MinifyConfig ,
489521 root_state : type [BaseState ] | None = None ,
@@ -506,40 +538,27 @@ def validate_minify_config(
506538
507539 all_states = collect_all_states (root_state )
508540
509- # Check for duplicate state IDs among siblings.
510- # Group by actual parent class (not string-split path) since children of
511- # the same parent can be defined in different modules.
541+ # Group sibling states by their actual parent class (not by string-split
542+ # path) since children of the same parent can live in different modules.
512543 path_to_cls = {get_state_full_path (s ): s for s in all_states }
513- parent_cls_to_state_ids : dict [type [BaseState ] | None , dict [ str , list [ str ]]] = {}
544+ parent_to_pairs : dict [type [BaseState ] | None , list [ tuple [ str , str ]]] = {}
514545 for state_path , minified_name in config ["states" ].items ():
515546 state_cls = path_to_cls .get (state_path )
516- parent_cls = state_cls .get_parent_state () if state_cls else None
517- parent_cls_to_state_ids .setdefault (parent_cls , {}).setdefault (
518- minified_name , []
519- ).append (state_path )
520-
521- for parent_cls , id_to_states in parent_cls_to_state_ids .items ():
522- for minified_name , state_paths in id_to_states .items ():
523- if len (state_paths ) > 1 :
524- parent_name = parent_cls .__name__ if parent_cls else "root"
525- errors .append (
526- f"Duplicate state_id='{ minified_name } ' under '{ parent_name } ': "
527- f"{ state_paths } "
528- )
529-
530- # Check for duplicate event IDs within same state
547+ parent = state_cls .get_parent_state () if state_cls else None
548+ parent_to_pairs .setdefault (parent , []).append ((state_path , minified_name ))
549+
550+ for parent , pairs in parent_to_pairs .items ():
551+ parent_name = parent .__name__ if parent else "root"
552+ errors .extend (
553+ f"Duplicate state_id='{ mid } ' under '{ parent_name } ': { paths } "
554+ for mid , paths in _find_duplicate_ids (pairs ).items ()
555+ )
556+
531557 for state_path , state_events in config ["events" ].items ():
532- id_to_handlers : dict [str , list [str ]] = {}
533- for handler_name , minified_name in state_events .items ():
534- if minified_name not in id_to_handlers :
535- id_to_handlers [minified_name ] = []
536- id_to_handlers [minified_name ].append (handler_name )
537-
538- for minified_name , handler_names in id_to_handlers .items ():
539- if len (handler_names ) > 1 :
540- errors .append (
541- f"Duplicate event_id='{ minified_name } ' in '{ state_path } ': { handler_names } "
542- )
558+ errors .extend (
559+ f"Duplicate event_id='{ mid } ' in '{ state_path } ': { handlers } "
560+ for mid , handlers in _find_duplicate_ids (state_events .items ()).items ()
561+ )
543562
544563 # Check for missing states (in code but not in config)
545564 code_state_paths = {get_state_full_path (s ) for s in all_states }
@@ -652,39 +671,21 @@ def sync_minify_config(
652671 parent = state_cls .get_parent_state ()
653672 parent_cls_to_new_children .setdefault (parent , []).append (state_path )
654673
655- # Assign new state IDs (unique among siblings of the same parent class)
674+ # Assign new state IDs (unique among siblings of the same parent class).
656675 for parent_cls , children in parent_cls_to_new_children .items ():
657676 existing_ids = parent_cls_to_existing_ids .get (parent_cls , set ()).copy ()
677+ new_states .update (_assign_next_ids (children , existing_ids , reassign_deleted ))
658678
659- # Assign IDs starting from max + 1 (or 0 if reassign_deleted and gaps exist)
660- next_id = 0 if reassign_deleted else (max (existing_ids , default = - 1 ) + 1 )
661-
662- for state_path in sorted (children ):
663- while next_id in existing_ids :
664- next_id += 1
665- new_states [state_path ] = int_to_minified_name (next_id )
666- existing_ids .add (next_id )
667- next_id += 1
668-
669- # Find events that need IDs assigned
679+ # Assign new event IDs (unique within each state).
670680 for state_cls in all_states :
671681 state_path = get_state_full_path (state_cls )
672682 state_events = new_events .get (state_path , {})
673683 new_handlers = [h for h in state_cls .event_handlers if h not in state_events ]
674-
675684 if new_handlers :
676- # Get existing IDs for this state's events
677685 existing_ids = {minified_name_to_int (eid ) for eid in state_events .values ()}
678-
679- next_id = 0 if reassign_deleted else (max (existing_ids , default = - 1 ) + 1 )
680-
681- for handler_name in sorted (new_handlers ):
682- while next_id in existing_ids :
683- next_id += 1
684- state_events [handler_name ] = int_to_minified_name (next_id )
685- existing_ids .add (next_id )
686- next_id += 1
687-
686+ state_events .update (
687+ _assign_next_ids (new_handlers , existing_ids , reassign_deleted )
688+ )
688689 new_events [state_path ] = state_events
689690
690691 return MinifyConfig (
0 commit comments