Repository: reflex-dev/reflex

Issue:
[REF-3004] postcss-import causes regression using local stylesheets
**Describe the bug**
Postcss-import wants to resolve CSS files at compile time, but it cannot find them and raises an exception.

**To Reproduce**
See https://github.com/orgs/reflex-dev/discussions/3414#discussioncomment-9650758

**Specifics (please complete the following information):**
 - Python Version: 3.11
 - Reflex Version: 0.5.3a1
 - OS: 
 - Browser (Optional):
 
**Additional context**
Add any other context about the problem here.


<sub>[REF-3004](https://linear.app/reflex-dev/issue/REF-3004/postcss-import-causes-regression-using-local-stylesheets)</sub>


Discussion:

Patch:
diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py
index 3f4f9a4bed2..128b2906a0f 100644
--- a/reflex/compiler/compiler.py
+++ b/reflex/compiler/compiler.py
@@ -169,7 +169,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
                 raise FileNotFoundError(
                     f"The stylesheet file {stylesheet_full_path} does not exist."
                 )
-            stylesheet = f"@/{stylesheet.strip('/')}"
+            stylesheet = f"../{constants.Dirs.PUBLIC}/{stylesheet.strip('/')}"
         sheets.append(stylesheet) if stylesheet not in sheets else None
     return templates.STYLE.render(stylesheets=sheets)
 
diff --git a/reflex/constants/base.py b/reflex/constants/base.py
index 94559c3467a..fbbe517072c 100644
--- a/reflex/constants/base.py
+++ b/reflex/constants/base.py
@@ -27,6 +27,8 @@ class Dirs(SimpleNamespace):
     UTILS = "utils"
     # The name of the output static directory.
     STATIC = "_static"
+    # The name of the public html directory served at "/"
+    PUBLIC = "public"
     # The name of the state file.
     STATE_PATH = "/".join([UTILS, "state"])
     # The name of the components file.
@@ -40,7 +42,7 @@ class Dirs(SimpleNamespace):
     # The directory where the utils file is located.
     WEB_UTILS = os.path.join(WEB, UTILS)
     # The directory where the assets are located.
-    WEB_ASSETS = os.path.join(WEB, "public")
+    WEB_ASSETS = os.path.join(WEB, PUBLIC)
     # The env json file.
     ENV_JSON = os.path.join(WEB, "env.json")
     # The reflex json file.
diff --git a/reflex/constants/base.pyi b/reflex/constants/base.pyi
deleted file mode 100644
index 2fbeafd7928..00000000000
--- a/reflex/constants/base.pyi
+++ /dev/null
@@ -1,99 +0,0 @@
-"""Stub file for reflex/constants/base.py"""
-# ------------------- DO NOT EDIT ----------------------
-# This file was generated by `reflex/utils/pyi_generator.py`!
-# ------------------------------------------------------
-
-from typing import Any, Dict, Literal, Optional, Union, overload
-from reflex.vars import Var, BaseVar, ComputedVar
-from reflex.event import EventChain, EventHandler, EventSpec
-from reflex.style import Style
-import os
-import platform
-from enum import Enum
-from importlib import metadata
-from types import SimpleNamespace
-from platformdirs import PlatformDirs
-
-IS_WINDOWS = platform.system() == "Windows"
-IS_WINDOWS_BUN_SUPPORTED_MACHINE = IS_WINDOWS and platform.machine() in [
-    "AMD64",
-    "x86_64",
-]
-
-class Dirs(SimpleNamespace):
-    WEB = ".web"
-    APP_ASSETS = "assets"
-    EXTERNAL_APP_ASSETS = "external"
-    UTILS = "utils"
-    STATIC = "_static"
-    STATE_PATH = "/".join([UTILS, "state"])
-    COMPONENTS_PATH = "/".join([UTILS, "components"])
-    CONTEXTS_PATH = "/".join([UTILS, "context"])
-    WEB_PAGES = os.path.join(WEB, "pages")
-    WEB_STATIC = os.path.join(WEB, STATIC)
-    WEB_UTILS = os.path.join(WEB, UTILS)
-    WEB_ASSETS = os.path.join(WEB, "public")
-    ENV_JSON = os.path.join(WEB, "env.json")
-    REFLEX_JSON = os.path.join(WEB, "reflex.json")
-    POSTCSS_JS = os.path.join(WEB, "postcss.config.js")
-
-class Reflex(SimpleNamespace):
-    MODULE_NAME = "reflex"
-    VERSION = metadata.version(MODULE_NAME)
-    JSON = os.path.join(Dirs.WEB, "reflex.json")
-    _dir = os.environ.get("REFLEX_DIR", "")
-    DIR = _dir or PlatformDirs(MODULE_NAME, False).user_data_dir
-    ROOT_DIR = os.path.dirname(
-        os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-    )
-
-class ReflexHostingCLI(SimpleNamespace):
-    MODULE_NAME = "reflex-hosting-cli"
-
-class Templates(SimpleNamespace):
-    APP_TEMPLATES_ROUTE = "/app-templates"
-    DEFAULT = "blank"
-
-    class Dirs(SimpleNamespace):
-        BASE = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates")
-        WEB_TEMPLATE = os.path.join(BASE, "web")
-        JINJA_TEMPLATE = os.path.join(BASE, "jinja")
-        CODE = "code"
-
-class Next(SimpleNamespace):
-    CONFIG_FILE = "next.config.js"
-    SITEMAP_CONFIG_FILE = os.path.join(Dirs.WEB, "next-sitemap.config.js")
-    NODE_MODULES = "node_modules"
-    PACKAGE_LOCK = "package-lock.json"
-    FRONTEND_LISTENING_REGEX = "Local:[\\s]+(.*)"
-
-class ColorMode(SimpleNamespace):
-    NAME = "colorMode"
-    USE = "useColorMode"
-    TOGGLE = "toggleColorMode"
-
-class Env(str, Enum):
-    DEV = "dev"
-    PROD = "prod"
-
-class LogLevel(str, Enum):
-    DEBUG = "debug"
-    INFO = "info"
-    WARNING = "warning"
-    ERROR = "error"
-    CRITICAL = "critical"
-
-POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
-
-class Ping(SimpleNamespace):
-    INTERVAL = 25
-    TIMEOUT = 120
-
-COOKIES = "cookies"
-LOCAL_STORAGE = "local_storage"
-SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
-ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
-PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
-RELOAD_CONFIG = "__REFLEX_RELOAD_CONFIG"
-REFLEX_VAR_OPENING_TAG = "<reflex.Var>"
-REFLEX_VAR_CLOSING_TAG = "</reflex.Var>"


Test patch:
diff --git a/integration/test_tailwind.py b/integration/test_tailwind.py
index 6155729c90b..c7320cb2195 100644
--- a/integration/test_tailwind.py
+++ b/integration/test_tailwind.py
@@ -25,6 +25,8 @@ def TailwindApp(
         paragraph_text: Text for the paragraph.
         paragraph_class_name: Tailwind class_name for the paragraph.
     """
+    from pathlib import Path
+
     import reflex as rx
 
     class UnusedState(rx.State):
@@ -35,10 +37,15 @@ def index():
             rx.chakra.text(paragraph_text, class_name=paragraph_class_name),
             rx.el.p(paragraph_text, class_name=paragraph_class_name),
             rx.text(paragraph_text, as_="p", class_name=paragraph_class_name),
+            rx.el.div("Test external stylesheet", class_name="external"),
             id="p-content",
         )
 
-    app = rx.App(style={"font_family": "monospace"})
+    assets = Path(__file__).resolve().parent.parent / "assets"
+    assets.mkdir(exist_ok=True)
+    stylesheet = assets / "test_styles.css"
+    stylesheet.write_text(".external { color: rgba(0, 0, 255, 0.5) }")
+    app = rx.App(style={"font_family": "monospace"}, stylesheets=[stylesheet.name])
     app.add_page(index)
     if tailwind_disabled:
         config = rx.config.get_config()
@@ -107,3 +114,9 @@ def test_tailwind_app(tailwind_app: AppHarness, tailwind_disabled: bool):
         else:
             # expect "text-red-500" from tailwind utility class
             assert p.value_of_css_property("color") in TEXT_RED_500_COLOR
+
+    # Assert external stylesheet is applying rules
+    external = driver.find_elements(By.CLASS_NAME, "external")
+    assert len(external) == 1
+    for ext_div in external:
+        assert ext_div.value_of_css_property("color") == "rgba(0, 0, 255, 0.5)"
diff --git a/tests/compiler/test_compiler.py b/tests/compiler/test_compiler.py
index b6191974a19..20dc7dd3124 100644
--- a/tests/compiler/test_compiler.py
+++ b/tests/compiler/test_compiler.py
@@ -135,7 +135,7 @@ def test_compile_stylesheets(tmp_path, mocker):
         f"@import url('./tailwind.css'); \n"
         f"@import url('https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple'); \n"
         f"@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css'); \n"
-        f"@import url('@/styles.css'); \n"
+        f"@import url('../public/styles.css'); \n"
         f"@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css'); \n",
     )
 
@@ -166,7 +166,7 @@ def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
 
     assert compiler.compile_root_stylesheet(stylesheets) == (
         os.path.join(".web", "styles", "styles.css"),
-        "@import url('@/styles.css'); \n",
+        "@import url('../public/styles.css'); \n",
     )