Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 38 additions & 2 deletions .github/workflows/integration_app_harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,46 @@ jobs:
python-version: ${{ matrix.python-version }}
run-uv-sync: true

- name: Run app harness tests
env:
REFLEX_REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
run: uv run pytest tests/integration --ignore=tests/integration/tests_playwright --reruns 3 -v --maxfail=5 --splits 2 --group ${{matrix.split_index}}

# Playwright tests run in a separate job because the pytest-playwright plugin
# keeps an asyncio event loop running on the main thread for the entire
# session, which is incompatible with pytest-asyncio tests.
integration-app-harness-playwright:
timeout-minutes: 30
strategy:
matrix:
state_manager: ["redis", "memory"]
python-version: ["3.11", "3.12", "3.13", "3.14"]
fail-fast: false
runs-on: ubuntu-22.04
services:
redis:
image: ${{ matrix.state_manager == 'redis' && 'redis' || '' }}
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
- uses: ./.github/actions/setup_build_env
with:
python-version: ${{ matrix.python-version }}
run-uv-sync: true

- name: Install playwright
run: uv run playwright install chromium --only-shell

- name: Run app harness tests
- name: Run playwright tests
env:
REFLEX_REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
run: uv run pytest tests/integration --reruns 3 -v --maxfail=5 --splits 2 --group ${{matrix.split_index}}
run: uv run pytest tests/integration/tests_playwright --reruns 3 -v --maxfail=5
4 changes: 2 additions & 2 deletions packages/reflex-base/src/reflex_base/compiler/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ def vite_config_template(
"""Template for vite.config.js.

Args:
base: The base path for the Vite config.
base: The base path for the Vite config (for handling frontend_path config).
hmr: Whether to enable hot module replacement.
force_full_reload: Whether to force a full reload on changes.
experimental_hmr: Whether to enable experimental HMR features.
Expand Down Expand Up @@ -562,13 +562,13 @@ def vite_config_template(
}}

export default defineConfig((config) => ({{
base: "{base}",
plugins: [
alwaysUseReactDomServerNode(),
reactRouter(),
safariCacheBustPlugin(),
].concat({"[fullReload()]" if force_full_reload else "[]"}),
build: {{
assetsDir: "{base}assets".slice(1),
sourcemap: {"true" if sourcemap is True else "false" if sourcemap is False else repr(sourcemap)},
rollupOptions: {{
onwarn(warning, warn) {{
Expand Down
13 changes: 13 additions & 0 deletions packages/reflex-base/src/reflex_base/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,19 @@ def json(self) -> str:

return json.dumps(self, default=serialize)

def prepend_frontend_path(self, path: str) -> str:
"""Prepend the frontend path to a given path.

Args:
path: The path to prepend the frontend path to.

Returns:
The path with the frontend path prepended if it begins with a slash, otherwise the original path.
"""
if self.frontend_path and path.startswith("/"):
return f"/{self.frontend_path.strip('/')}{path}"
return path

@property
def app_module(self) -> ModuleType | None:
"""Return the app module if `app_module_import` is set.
Expand Down
9 changes: 8 additions & 1 deletion reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@ def get_compilation_time() -> str:
)

# try to be somewhat accurate - but still not 100%
adhoc_steps_without_executor = 7
adhoc_steps_without_executor = 8
fixed_pages_within_executor = 4
plugin_count = len(config.plugins)
progress.start()
Expand Down Expand Up @@ -1278,6 +1278,13 @@ def get_compilation_time() -> str:

progress.advance(task)

# Reinitialize vite config in case runtime options have changed.
compile_results.append((
constants.ReactRouter.VITE_CONFIG_FILE,
frontend_skeleton._compile_vite_config(config),
))
progress.advance(task)

# Track imports found.
all_imports = {}

Expand Down
5 changes: 3 additions & 2 deletions reflex/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

from reflex_base import constants
from reflex_base.config import get_config
from reflex_base.environment import EnvironmentVariables


Expand Down Expand Up @@ -92,7 +93,7 @@ def asset(
if not backend_only and not src_file_local.exists():
msg = f"File not found: {src_file_local}"
raise FileNotFoundError(msg)
return f"/{path}"
return get_config().prepend_frontend_path(f"/{path}")

# Shared asset handling
# Determine the file by which the asset is exposed.
Expand Down Expand Up @@ -128,4 +129,4 @@ def asset(
dst_file.unlink()
dst_file.symlink_to(src_file_shared)

return f"/{external}/{subfolder}/{path}"
return get_config().prepend_frontend_path(f"/{external}/{subfolder}/{path}")
11 changes: 6 additions & 5 deletions reflex/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,18 +896,19 @@ def _run_frontend(self):
/ reflex.utils.prerequisites.get_web_dir()
/ reflex.constants.Dirs.STATIC
)
error_page_map = {
404: web_root / "404.html",
}
config = reflex.config.get_config()
with Subdir404TCPServer(
("", 0),
SimpleHTTPRequestHandlerCustomErrors,
root=web_root,
error_page_map=error_page_map,
error_page_map={
404: web_root / config.prepend_frontend_path("/404.html").lstrip("/"),
},
) as self.frontend_server:
frontend_path = config.frontend_path.strip("/")
self.frontend_url = "http://localhost:{1}".format(
*self.frontend_server.socket.getsockname()
)
) + (f"/{frontend_path}/" if frontend_path else "/")
self.frontend_server.serve_forever()

def _start_frontend(self):
Expand Down
1 change: 1 addition & 0 deletions reflex/utils/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def build():
config = get_config()

if frontend_path := config.frontend_path.strip("/"):
# Create a subdirectory that matches the configured frontend_path.
frontend_path = PosixPath(frontend_path)
first_part = frontend_path.parts[0]
for child in list((wdir / constants.Dirs.STATIC).iterdir()):
Expand Down
2 changes: 1 addition & 1 deletion reflex/utils/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def get_frontend_mount():
config = get_config()

return Mount(
"/" + config.frontend_path.strip("/"),
config.prepend_frontend_path("/"),
app=StaticFiles(
directory=prerequisites.get_web_dir()
/ constants.Dirs.STATIC
Expand Down
11 changes: 2 additions & 9 deletions reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,8 @@ def update_react_router_config(prerender_routes: bool = False):


def _update_react_router_config(config: Config, prerender_routes: bool = False):
basename = "/" + (config.frontend_path or "").strip("/")
if not basename.endswith("/"):
basename += "/"

react_router_config = {
"basename": basename,
"basename": config.prepend_frontend_path("/"),
"future": {
"unstable_optimizeDeps": True,
},
Expand Down Expand Up @@ -244,11 +240,8 @@ def initialize_package_json():

def _compile_vite_config(config: Config):
# base must have exactly one trailing slash
base = "/"
if frontend_path := config.frontend_path.strip("/"):
base += frontend_path + "/"
return templates.vite_config_template(
base=base,
base=config.prepend_frontend_path("/"),
hmr=environment.VITE_HMR.get(),
force_full_reload=environment.VITE_FORCE_FULL_RELOAD.get(),
experimental_hmr=environment.VITE_EXPERIMENTAL_HMR.get(),
Expand Down
Loading
Loading