Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minify state names v2 #3728

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import os
import re
from pathlib import Path
from typing import Generator, Type

import pytest

import reflex.constants
from reflex.testing import AppHarness, AppHarnessProd

DISPLAY = None
Expand Down Expand Up @@ -63,15 +65,32 @@ def pytest_exception_interact(node, call, report):


@pytest.fixture(
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
scope="session",
params=[
AppHarness,
AppHarnessProd,
],
ids=[
reflex.constants.Env.DEV.value,
reflex.constants.Env.PROD.value,
],
)
def app_harness_env(request):
def app_harness_env(
request: pytest.FixtureRequest,
) -> Generator[Type[AppHarness], None, None]:
"""Parametrize the AppHarness class to use for the test, either dev or prod.

Args:
request: The pytest fixture request object.

Returns:
Yields:
The AppHarness class to use for the test.
"""
return request.param
harness: Type[AppHarness] = request.param
if issubclass(harness, AppHarnessProd):
os.environ[reflex.constants.base.ENV_MODE_ENV_VAR] = (
reflex.constants.base.Env.PROD.value
)
yield harness
if issubclass(harness, AppHarnessProd):
_ = os.environ.pop(reflex.constants.base.ENV_MODE_ENV_VAR, None)
1 change: 0 additions & 1 deletion integration/test_computed_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ def index() -> rx.Component:
),
)

# raise Exception(State.count3._deps(objclass=State))
app = rx.App()
app.add_page(index)

Expand Down
173 changes: 173 additions & 0 deletions integration/test_minified_states.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Integration tests for minified state names."""

from __future__ import annotations

import os
from functools import partial
from typing import Generator, Optional, Type

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver

from reflex.constants.compiler import ENV_MINIFY_STATES
from reflex.testing import AppHarness, AppHarnessProd


def TestApp(minify: bool | None) -> None:
"""A test app for minified state names.

Args:
minify: whether to minify state names
"""
import reflex as rx

class TestAppState(rx.State):
"""State for the TestApp app."""

pass

app = rx.App()

def index():
return rx.vstack(
rx.input(
value=TestAppState.router.session.client_token,
is_read_only=True,
id="token",
),
rx.text(f"minify: {minify}", id="minify"),
rx.text(TestAppState.get_name(), id="state_name"),
rx.text(TestAppState.get_full_name(), id="state_full_name"),
)

app.add_page(index)


@pytest.fixture(
params=[
pytest.param(False),
pytest.param(True),
pytest.param(None),
],
)
def minify_state_env(
request: pytest.FixtureRequest,
) -> Generator[Optional[bool], None, None]:
"""Set the environment variable to minify state names.

Args:
request: pytest fixture request

Yields:
minify_states: whether to minify state names
"""
minify_states: Optional[bool] = request.param
if minify_states is None:
_ = os.environ.pop(ENV_MINIFY_STATES, None)
else:
os.environ[ENV_MINIFY_STATES] = str(minify_states).lower()
yield minify_states
if minify_states is not None:
os.environ.pop(ENV_MINIFY_STATES, None)


@pytest.fixture
def test_app(
app_harness_env: Type[AppHarness],
tmp_path_factory: pytest.TempPathFactory,
minify_state_env: Optional[bool],
) -> Generator[AppHarness, None, None]:
"""Start TestApp app at tmp_path via AppHarness.

Args:
app_harness_env: either AppHarness (dev) or AppHarnessProd (prod)
tmp_path_factory: pytest tmp_path_factory fixture
minify_state_env: need to request this fixture to set env before the app starts

Yields:
running AppHarness instance

"""
name = f"testapp_{app_harness_env.__name__.lower()}"
with app_harness_env.create(
root=tmp_path_factory.mktemp(name),
app_name=name,
app_source=partial(TestApp, minify=minify_state_env), # type: ignore
) as harness:
yield harness


@pytest.fixture
def driver(test_app: AppHarness) -> Generator[WebDriver, None, None]:
"""Get an instance of the browser open to the test_app app.

Args:
test_app: harness for TestApp app

Yields:
WebDriver instance.

"""
assert test_app.app_instance is not None, "app is not running"
driver = test_app.frontend()
try:
yield driver
finally:
driver.quit()


def test_minified_states(
test_app: AppHarness,
driver: WebDriver,
minify_state_env: Optional[bool],
) -> None:
"""Test minified state names.

Args:
test_app: harness for TestApp
driver: WebDriver instance.
minify_state_env: whether state minification is enabled by env var.

"""
assert test_app.app_instance is not None, "app is not running"

is_prod = isinstance(test_app, AppHarnessProd)

# default to minifying in production
should_minify: bool = is_prod

# env overrides default
if minify_state_env is not None:
should_minify = minify_state_env

# TODO: reload internal states, or refactor VarData to reference state object instead of name
if should_minify:
pytest.skip(
"minify tests are currently not working, because _var_set_states writes the state names during import time"
)

# get a reference to the connected client
token_input = driver.find_element(By.ID, "token")
assert token_input

# wait for the backend connection to send the token
token = test_app.poll_for_value(token_input)
assert token

state_name_text = driver.find_element(By.ID, "state_name")
assert state_name_text
state_name = state_name_text.text

state_full_name_text = driver.find_element(By.ID, "state_full_name")
assert state_full_name_text
_ = state_full_name_text.text

assert test_app.app_module
module_state_prefix = test_app.app_module.__name__.replace(".", "___")
# prod_module_suffix = "prod" if is_prod else ""

if should_minify:
assert len(state_name) == 1
else:
assert state_name == f"{module_state_prefix}____test_app_state"
2 changes: 2 additions & 0 deletions reflex/.templates/jinja/web/utils/context.js.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const clientStorage = {{ client_storage|json_dumps }}
export const clientStorage = {}
{% endif %}

export const main_state_name = "{{const.main_state_name}}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is already coming in as state_name.

reflex/compiler/compiler.py:90

Copy link
Contributor Author

@benedikt-bartscher benedikt-bartscher Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc this was the state name for rx.app.App.state. will check

Copy link
Contributor Author

@benedikt-bartscher benedikt-bartscher Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are not the same. compile_contexts gets called from reflex.app.App._compile with self.state

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for clarity, we should call the var root_state_name instead of main_state_name just to indicate that it's the name of rx.State, the root of the state "tree"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about main_state_dispatch should we rename that one to root_state_dispatch as well?

export const update_vars_internal = "{{const.update_vars_internal}}"
{% if state_name %}
export const state_name = "{{state_name}}"

Expand Down
8 changes: 5 additions & 3 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
onLoadInternalEvent,
state_name,
exception_state_name,
main_state_name,
update_vars_internal,
} from "utils/context.js";
import debounce from "/utils/helpers/debounce";
import throttle from "/utils/helpers/throttle";
Expand Down Expand Up @@ -117,7 +119,7 @@ export const isStateful = () => {
if (event_queue.length === 0) {
return false;
}
return event_queue.some(event => event.name.startsWith("reflex___state"));
return event_queue.some(event => event.name.includes("___"));
}

/**
Expand Down Expand Up @@ -768,7 +770,7 @@ export const useEventLoop = (
const vars = {};
vars[storage_to_state_map[e.key]] = e.newValue;
const event = Event(
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
`${state_name}.${update_vars_internal}`,
{ vars: vars }
);
addEvents([event], e);
Expand All @@ -782,7 +784,7 @@ export const useEventLoop = (
// Route after the initial page hydration.
useEffect(() => {
const change_start = () => {
const main_state_dispatch = dispatch["reflex___state____state"]
const main_state_dispatch = dispatch[main_state_name];
if (main_state_dispatch !== undefined) {
main_state_dispatch({ is_hydrated: false })
}
Expand Down
22 changes: 11 additions & 11 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _compile_document_root(root: Component) -> str:
Returns:
The compiled document root.
"""
return templates.DOCUMENT_ROOT.render(
return templates.document_root().render(
imports=utils.compile_imports(root._get_all_imports()),
document=root.render(),
)
Expand All @@ -49,7 +49,7 @@ def _compile_app(app_root: Component) -> str:
Returns:
The compiled app.
"""
return templates.APP_ROOT.render(
return templates.app_root().render(
imports=utils.compile_imports(app_root._get_all_imports()),
custom_codes=app_root._get_all_custom_code(),
hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()},
Expand All @@ -66,7 +66,7 @@ def _compile_theme(theme: dict) -> str:
Returns:
The compiled theme.
"""
return templates.THEME.render(theme=theme)
return templates.theme().render(theme=theme)


def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None) -> str:
Expand All @@ -85,7 +85,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)

last_compiled_time = str(datetime.now())
return (
templates.CONTEXT.render(
templates.context().render(
initial_state=utils.compile_state(state),
state_name=state.get_name(),
client_storage=utils.compile_client_storage(state),
Expand All @@ -94,7 +94,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)
default_color_mode=appearance,
)
if state
else templates.CONTEXT.render(
else templates.context().render(
is_dev_mode=not is_prod_mode(),
default_color_mode=appearance,
last_compiled_time=last_compiled_time,
Expand All @@ -121,7 +121,7 @@ def _compile_page(
# Compile the code to render the component.
kwargs = {"state_name": state.get_name()} if state else {}

return templates.PAGE.render(
return templates.page().render(
imports=imports,
dynamic_imports=component._get_all_dynamic_imports(),
custom_codes=component._get_all_custom_code(),
Expand Down Expand Up @@ -177,7 +177,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
)
stylesheet = f"../{constants.Dirs.PUBLIC}/{stylesheet.strip('/')}"
sheets.append(stylesheet) if stylesheet not in sheets else None
return templates.STYLE.render(stylesheets=sheets)
return templates.style().render(stylesheets=sheets)


def _compile_component(component: Component | StatefulComponent) -> str:
Expand All @@ -189,7 +189,7 @@ def _compile_component(component: Component | StatefulComponent) -> str:
Returns:
The compiled component.
"""
return templates.COMPONENT.render(component=component)
return templates.component().render(component=component)


def _compile_components(
Expand Down Expand Up @@ -217,7 +217,7 @@ def _compile_components(

# Compile the components page.
return (
templates.COMPONENTS.render(
templates.components().render(
imports=utils.compile_imports(imports),
components=component_renders,
),
Expand Down Expand Up @@ -295,7 +295,7 @@ def get_shared_components_recursive(component: BaseComponent):
f"/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None
)

return templates.STATEFUL_COMPONENTS.render(
return templates.stateful_components().render(
imports=utils.compile_imports(all_imports),
memoized_code="\n".join(rendered_components),
)
Expand All @@ -312,7 +312,7 @@ def _compile_tailwind(
Returns:
The compiled Tailwind config.
"""
return templates.TAILWIND_CONFIG.render(
return templates.tailwind_config().render(
**config,
)

Expand Down
Loading
Loading