Skip to content

Commit e53c76a

Browse files
wip fixing minifed state names + add cli
1 parent be13be5 commit e53c76a

8 files changed

Lines changed: 368 additions & 35 deletions

File tree

reflex/.templates/web/utils/state.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
onLoadInternalEvent,
1818
state_name,
1919
exception_state_name,
20+
main_state_name,
21+
update_vars_internal,
2022
} from "$/utils/context";
2123
import debounce from "$/utils/helpers/debounce";
2224
import throttle from "$/utils/helpers/throttle";
@@ -56,10 +58,10 @@ export const generateUUID = () => {
5658
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
5759
let r = Math.random() * 16;
5860
if (d > 0) {
59-
r = ((d + r) % 16) | 0;
61+
r = (d + r) % 16 | 0;
6062
d = Math.floor(d / 16);
6163
} else {
62-
r = ((d2 + r) % 16) | 0;
64+
r = (d2 + r) % 16 | 0;
6365
d2 = Math.floor(d2 / 16);
6466
}
6567
return (c == "x" ? r : (r & 0x7) | 0x8).toString(16);
@@ -134,7 +136,7 @@ export const isStateful = () => {
134136
if (event_queue.length === 0) {
135137
return false;
136138
}
137-
return event_queue.some((event) => event.name.startsWith("reflex___state"));
139+
return event_queue.some((event) => event.name.startsWith(main_state_name));
138140
};
139141

140142
/**
@@ -1034,10 +1036,9 @@ export const useEventLoop = (
10341036
if (storage_to_state_map[e.key]) {
10351037
const vars = {};
10361038
vars[storage_to_state_map[e.key]] = e.newValue;
1037-
const event = ReflexEvent(
1038-
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
1039-
{ vars: vars },
1040-
);
1039+
const event = ReflexEvent(`${state_name}.${update_vars_internal}`, {
1040+
vars: vars,
1041+
});
10411042
addEvents([event], e);
10421043
}
10431044
};
@@ -1072,7 +1073,7 @@ export const useEventLoop = (
10721073
}
10731074

10741075
// Equivalent to routeChangeStart - runs when navigation begins
1075-
const main_state_dispatch = dispatch["reflex___state____state"];
1076+
const main_state_dispatch = dispatch[main_state_name];
10761077
if (main_state_dispatch !== undefined) {
10771078
main_state_dispatch({ is_hydrated_rx_state_: false });
10781079
}

reflex/compiler/templates.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,20 @@ def context_template(
274274
Returns:
275275
Rendered context file content as string.
276276
"""
277+
# Import state classes to get dynamic names (supports minification)
278+
from reflex.state import (
279+
FrontendEventExceptionState,
280+
OnLoadInternalState,
281+
State,
282+
UpdateVarsInternalState,
283+
)
284+
285+
# Compute dynamic state names that respect minification settings
286+
main_state_name = State.get_name()
287+
on_load_internal = f"{OnLoadInternalState.get_name()}.on_load_internal"
288+
update_vars_internal = f"{UpdateVarsInternalState.get_name()}.update_vars_internal"
289+
exception_state_full = FrontendEventExceptionState.get_full_name()
290+
277291
initial_state = initial_state or {}
278292
state_contexts_str = "".join([
279293
f"{format_state_name(state_name)}: createContext(null),"
@@ -284,7 +298,11 @@ def context_template(
284298
rf"""
285299
export const state_name = "{state_name}"
286300
287-
export const exception_state_name = "{constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL}"
301+
export const main_state_name = "{main_state_name}"
302+
303+
export const update_vars_internal = "{update_vars_internal}"
304+
305+
export const exception_state_name = "{exception_state_full}"
288306
289307
// These events are triggered on initial load and each page navigation.
290308
export const onLoadInternalEvent = () => {{
@@ -296,15 +314,15 @@ def context_template(
296314
if (client_storage_vars && Object.keys(client_storage_vars).length !== 0) {{
297315
internal_events.push(
298316
ReflexEvent(
299-
'{state_name}.{constants.CompileVars.UPDATE_VARS_INTERNAL}',
317+
'{state_name}.{update_vars_internal}',
300318
{{vars: client_storage_vars}},
301319
),
302320
);
303321
}}
304322
305323
// `on_load_internal` triggers the correct on_load event(s) for the current page.
306324
// If the page does not define any on_load event, this will just set `is_hydrated = true`.
307-
internal_events.push(ReflexEvent('{state_name}.{constants.CompileVars.ON_LOAD_INTERNAL}'));
325+
internal_events.push(ReflexEvent('{state_name}.{on_load_internal}'));
308326
309327
return internal_events;
310328
}}
@@ -319,6 +337,10 @@ def context_template(
319337
else """
320338
export const state_name = undefined
321339
340+
export const main_state_name = undefined
341+
342+
export const update_vars_internal = undefined
343+
322344
export const exception_state_name = undefined
323345
324346
export const onLoadInternalEvent = () => []

reflex/constants/compiler.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,6 @@ class CompileVars(SimpleNamespace):
6565
CONNECT_ERROR = "connectErrors"
6666
# The name of the function for converting a dict to an event.
6767
TO_EVENT = "ReflexEvent"
68-
# The name of the internal on_load event.
69-
ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal"
70-
# The name of the internal event to update generic state vars.
71-
UPDATE_VARS_INTERNAL = (
72-
"reflex___state____update_vars_internal_state.update_vars_internal"
73-
)
74-
# The name of the frontend event exception state
75-
FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state"
76-
# The full name of the frontend exception state
77-
FRONTEND_EXCEPTION_STATE_FULL = (
78-
f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}"
79-
)
8068

8169

8270
class PageNames(SimpleNamespace):

reflex/reflex.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import operator
56
from importlib.util import find_spec
67
from pathlib import Path
78
from typing import TYPE_CHECKING
@@ -842,6 +843,189 @@ def rename(new_name: str):
842843
rename_app(new_name, get_config().loglevel)
843844

844845

846+
@cli.command(name="state-tree")
847+
@loglevel_option
848+
@click.option(
849+
"--json",
850+
"output_json",
851+
is_flag=True,
852+
help="Output as JSON.",
853+
)
854+
def state_tree(output_json: bool):
855+
"""Print the state tree with state_id's and event handlers with event_id's."""
856+
from reflex.event import EVENT_ID_MARKER
857+
from reflex.state import BaseState, State, _int_to_minified_name
858+
from reflex.utils import prerequisites
859+
860+
# Load the user's app to register all state classes
861+
prerequisites.get_app()
862+
863+
def build_state_tree(state_cls: type[BaseState]) -> dict:
864+
"""Recursively build state tree data.
865+
866+
Args:
867+
state_cls: The state class to build the tree for.
868+
869+
Returns:
870+
A dictionary containing the state tree data.
871+
"""
872+
state_id = state_cls._state_id
873+
874+
# Build event handlers list
875+
handlers = []
876+
for name, handler in state_cls.event_handlers.items():
877+
event_id = getattr(handler.fn, EVENT_ID_MARKER, None)
878+
handlers.append({
879+
"name": name,
880+
"event_id": event_id,
881+
"minified_name": (
882+
_int_to_minified_name(event_id) if event_id is not None else None
883+
),
884+
})
885+
handlers.sort(key=operator.itemgetter("name"))
886+
887+
# Build substates recursively
888+
substates = [
889+
build_state_tree(substate)
890+
for substate in sorted(state_cls.class_subclasses, key=lambda s: s.__name__)
891+
]
892+
893+
return {
894+
"name": state_cls.__name__,
895+
"full_name": state_cls.get_full_name(),
896+
"state_id": state_id,
897+
"minified_name": (
898+
_int_to_minified_name(state_id) if state_id is not None else None
899+
),
900+
"event_handlers": handlers,
901+
"substates": substates,
902+
}
903+
904+
def print_state_tree(state_data: dict, prefix: str = "", is_last: bool = True):
905+
"""Print a state and its children as a tree.
906+
907+
Args:
908+
state_data: The state data dictionary.
909+
prefix: The prefix for indentation.
910+
is_last: Whether this is the last item in the current level.
911+
"""
912+
state_id = state_data["state_id"]
913+
minified = state_data["minified_name"]
914+
915+
if state_id is not None:
916+
f'{state_data["name"]} (state_id={state_id} -> "{minified}")'
917+
else:
918+
f"{state_data['name']} (state_id=None)"
919+
920+
# Calculate new prefix for children
921+
child_prefix = prefix + (" " if is_last else "| ")
922+
923+
# Print event handlers
924+
handlers = state_data["event_handlers"]
925+
substates = state_data["substates"]
926+
has_substates = len(substates) > 0
927+
928+
if handlers:
929+
handler_prefix = child_prefix + ("| " if has_substates else " ")
930+
for i, handler in enumerate(handlers):
931+
is_last_handler = i == len(handlers) - 1
932+
event_id = handler["event_id"]
933+
if event_id is not None:
934+
_ = (
935+
handler_prefix,
936+
is_last_handler,
937+
) # silence unused variable warnings
938+
939+
# Print substates recursively
940+
for i, substate in enumerate(substates):
941+
is_last_substate = i == len(substates) - 1
942+
print_state_tree(substate, child_prefix, is_last_substate)
943+
944+
tree_data = build_state_tree(State)
945+
946+
if output_json:
947+
pass
948+
else:
949+
print_state_tree(tree_data)
950+
951+
952+
@cli.command(name="state-lookup")
953+
@loglevel_option
954+
@click.option(
955+
"--json",
956+
"output_json",
957+
is_flag=True,
958+
help="Output detailed info as JSON.",
959+
)
960+
@click.argument("minified_path")
961+
def state_lookup(output_json: bool, minified_path: str):
962+
"""Lookup a state by its minified path (e.g., 'a.bU')."""
963+
from reflex.state import _minified_name_to_int, _state_id_registry
964+
from reflex.utils import prerequisites
965+
966+
# Load the user's app to register all state classes
967+
prerequisites.get_app()
968+
969+
# Parse the dotted path
970+
parts = minified_path.split(".")
971+
972+
# Resolve each part
973+
result_parts = []
974+
for part in parts:
975+
try:
976+
state_id = _minified_name_to_int(part)
977+
except ValueError as err:
978+
raise SystemExit(1) from err
979+
980+
state_cls = _state_id_registry.get(state_id)
981+
if state_cls is None:
982+
raise SystemExit(1)
983+
984+
result_parts.append({
985+
"minified": part,
986+
"state_id": state_id,
987+
"module": state_cls.__module__,
988+
"class": state_cls.__name__,
989+
"full_name": state_cls.get_full_name(),
990+
})
991+
992+
if output_json:
993+
pass
994+
else:
995+
# Simple output: module.ClassName for each part
996+
for _info in result_parts:
997+
pass
998+
999+
1000+
@cli.command(name="state-next-id")
1001+
@loglevel_option
1002+
@click.option(
1003+
"--after-max",
1004+
is_flag=True,
1005+
help="Return max(state_id) + 1 instead of first gap.",
1006+
)
1007+
def state_next_id(after_max: bool):
1008+
"""Print the next available state_id."""
1009+
from reflex.state import _state_id_registry
1010+
from reflex.utils import prerequisites
1011+
1012+
# Load the user's app to register all state classes
1013+
prerequisites.get_app()
1014+
1015+
if not _state_id_registry:
1016+
return
1017+
1018+
if after_max:
1019+
# Return max + 1
1020+
next_id = max(_state_id_registry.keys()) + 1
1021+
else:
1022+
# Find first gap starting from 0
1023+
used_ids = set(_state_id_registry.keys())
1024+
next_id = 0
1025+
while next_id in used_ids:
1026+
next_id += 1
1027+
1028+
8451029
def _convert_reflex_loglevel_to_reflex_cli_loglevel(
8461030
loglevel: constants.LogLevel,
8471031
) -> HostingLogLevel:

0 commit comments

Comments
 (0)