Skip to content

Commit 377ef0a

Browse files
authored
change behavior around default style css (#5077)
* change behavior around default style css * change name away from styles.css * woops
1 parent 8356e82 commit 377ef0a

File tree

4 files changed

+124
-69
lines changed

4 files changed

+124
-69
lines changed

reflex/.templates/jinja/web/pages/_app.js.jinja2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% from "web/pages/macros.js.jinja2" import renderHooks %}
33

44
{% block early_imports %}
5-
import '$/styles/styles.css'
5+
import '$/styles/__reflex_global_styles.css'
66
{% endblock %}
77

88
{% block declaration %}

reflex/compiler/compiler.py

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
StatefulComponent,
1818
)
1919
from reflex.config import environment, get_config
20+
from reflex.constants.compiler import PageNames
2021
from reflex.state import BaseState
2122
from reflex.style import SYSTEM_COLOR_MODE
2223
from reflex.utils import console, path_ops
@@ -174,6 +175,42 @@ def compile_root_stylesheet(stylesheets: list[str]) -> tuple[str, str]:
174175
return output_path, code
175176

176177

178+
def _validate_stylesheet(stylesheet_full_path: Path, assets_app_path: Path) -> None:
179+
"""Validate the stylesheet.
180+
181+
Args:
182+
stylesheet_full_path: The stylesheet to validate.
183+
assets_app_path: The path to the assets directory.
184+
185+
Raises:
186+
ValueError: If the stylesheet is not supported.
187+
FileNotFoundError: If the stylesheet is not found.
188+
"""
189+
suffix = stylesheet_full_path.suffix[1:] if stylesheet_full_path.suffix else ""
190+
if suffix not in constants.Reflex.STYLESHEETS_SUPPORTED:
191+
raise ValueError(f"Stylesheet file {stylesheet_full_path} is not supported.")
192+
if not stylesheet_full_path.absolute().is_relative_to(assets_app_path.absolute()):
193+
raise FileNotFoundError(
194+
f"Cannot include stylesheets from outside the assets directory: {stylesheet_full_path}"
195+
)
196+
if not stylesheet_full_path.name:
197+
raise ValueError(
198+
f"Stylesheet file name cannot be empty: {stylesheet_full_path}"
199+
)
200+
if (
201+
len(
202+
stylesheet_full_path.absolute()
203+
.relative_to(assets_app_path.absolute())
204+
.parts
205+
)
206+
== 1
207+
and stylesheet_full_path.stem == PageNames.STYLESHEET_ROOT
208+
):
209+
raise ValueError(
210+
f"Stylesheet file name cannot be '{PageNames.STYLESHEET_ROOT}': {stylesheet_full_path}"
211+
)
212+
213+
177214
def _compile_root_stylesheet(stylesheets: list[str]) -> str:
178215
"""Compile the root stylesheet.
179216
@@ -194,10 +231,14 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
194231
)
195232

196233
failed_to_import_sass = False
234+
assets_app_path = Path.cwd() / constants.Dirs.APP_ASSETS
235+
236+
stylesheets_files: list[Path] = []
237+
stylesheets_urls = []
238+
197239
for stylesheet in stylesheets:
198240
if not utils.is_valid_url(stylesheet):
199241
# check if stylesheet provided exists.
200-
assets_app_path = Path.cwd() / constants.Dirs.APP_ASSETS
201242
stylesheet_full_path = assets_app_path / stylesheet.strip("/")
202243

203244
if not stylesheet_full_path.exists():
@@ -206,53 +247,51 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
206247
)
207248

208249
if stylesheet_full_path.is_dir():
209-
# NOTE: this can create an infinite loop, for example:
210-
# assets/
211-
# | dir_a/
212-
# | | dir_c/ (symlink to "assets/dir_a")
213-
# | dir_b/
214-
# so to avoid the infinite loop, we don't include symbolic links
215-
stylesheets += [
216-
str(p.relative_to(assets_app_path))
217-
for p in stylesheet_full_path.iterdir()
218-
if not (p.is_symlink() and p.is_dir())
219-
]
220-
continue
221-
222-
if (
223-
stylesheet_full_path.suffix[1:].lower()
224-
in constants.Reflex.STYLESHEETS_SUPPORTED
225-
):
226-
target = (
227-
get_web_dir()
228-
/ constants.Dirs.STYLES
229-
/ (stylesheet.rsplit(".", 1)[0].strip("/") + ".css")
250+
all_files = (
251+
file
252+
for ext in constants.Reflex.STYLESHEETS_SUPPORTED
253+
for file in stylesheet_full_path.rglob("*." + ext)
230254
)
231-
target.parent.mkdir(parents=True, exist_ok=True)
232-
233-
if stylesheet_full_path.suffix == ".css":
234-
path_ops.cp(src=stylesheet_full_path, dest=target, overwrite=True)
235-
else:
236-
try:
237-
from sass import compile as sass_compile
238-
239-
target.write_text(
240-
data=sass_compile(
241-
filename=str(stylesheet_full_path),
242-
output_style="compressed",
243-
),
244-
encoding="utf8",
245-
)
246-
except ImportError:
247-
failed_to_import_sass = True
255+
for file in all_files:
256+
if file.is_dir():
257+
continue
258+
# Validate the stylesheet.
259+
_validate_stylesheet(file, assets_app_path)
260+
stylesheets_files.append(file)
261+
248262
else:
249-
raise FileNotFoundError(
250-
f'The stylesheet file "{stylesheet_full_path}" is not a valid file.'
251-
)
263+
# Validate the stylesheet.
264+
_validate_stylesheet(stylesheet_full_path, assets_app_path)
265+
stylesheets_files.append(stylesheet_full_path)
266+
else:
267+
stylesheets_urls.append(stylesheet)
252268

253-
stylesheet = f"./{stylesheet.rsplit('.', 1)[0].strip('/')}.css"
269+
sheets.extend(dict.fromkeys(stylesheets_urls))
270+
271+
for stylesheet in stylesheets_files:
272+
target_path = stylesheet.relative_to(assets_app_path).with_suffix(".css")
273+
target = get_web_dir() / constants.Dirs.STYLES / target_path
274+
275+
target.parent.mkdir(parents=True, exist_ok=True)
276+
277+
if stylesheet.suffix == ".css":
278+
path_ops.cp(src=stylesheet, dest=target, overwrite=True)
279+
else:
280+
try:
281+
from sass import compile as sass_compile
282+
283+
target.write_text(
284+
data=sass_compile(
285+
filename=str(stylesheet),
286+
output_style="compressed",
287+
),
288+
encoding="utf8",
289+
)
290+
except ImportError:
291+
failed_to_import_sass = True
254292

255-
sheets.append(stylesheet) if stylesheet not in sheets else None
293+
str_target_path = "./" + str(target_path)
294+
sheets.append(str_target_path) if str_target_path not in sheets else None
256295

257296
if failed_to_import_sass:
258297
console.error(

reflex/constants/compiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class PageNames(SimpleNamespace):
8585
# The name of the app root page.
8686
APP_ROOT = "_app"
8787
# The root stylesheet filename.
88-
STYLESHEET_ROOT = "styles"
88+
STYLESHEET_ROOT = "__reflex_global_styles"
8989
# The name of the document root page.
9090
DOCUMENT_ROOT = "_document"
9191
# The name of the theme page.

tests/units/compiler/test_compiler.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from reflex import constants
88
from reflex.compiler import compiler, utils
9+
from reflex.constants.compiler import PageNames
910
from reflex.utils.imports import ImportVar, ParsedImportDict
1011

1112

@@ -127,7 +128,7 @@ def test_compile_stylesheets(tmp_path: Path, mocker):
127128
assets_dir = project / "assets"
128129
assets_dir.mkdir()
129130

130-
(assets_dir / "styles.css").write_text(
131+
(assets_dir / "style.css").write_text(
131132
"button.rt-Button {\n\tborder-radius:unset !important;\n}"
132133
)
133134
mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
@@ -142,21 +143,26 @@ def test_compile_stylesheets(tmp_path: Path, mocker):
142143
stylesheets = [
143144
"https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple",
144145
"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css",
145-
"/styles.css",
146+
"/style.css",
146147
"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-theme.min.css",
147148
]
148149

149150
assert compiler.compile_root_stylesheet(stylesheets) == (
150-
str(project / constants.Dirs.WEB / "styles" / "styles.css"),
151+
str(
152+
project
153+
/ constants.Dirs.WEB
154+
/ "styles"
155+
/ (PageNames.STYLESHEET_ROOT + ".css")
156+
),
151157
"@import url('./tailwind.css'); \n"
152158
"@import url('https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple'); \n"
153159
"@import url('https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'); \n"
154-
"@import url('./styles.css'); \n"
155-
"@import url('https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-theme.min.css'); \n",
160+
"@import url('https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-theme.min.css'); \n"
161+
"@import url('./style.css'); \n",
156162
)
157163

158-
assert (project / constants.Dirs.WEB / "styles" / "styles.css").read_text() == (
159-
assets_dir / "styles.css"
164+
assert (project / constants.Dirs.WEB / "styles" / "style.css").read_text() == (
165+
assets_dir / "style.css"
160166
).read_text()
161167

162168

@@ -177,7 +183,7 @@ def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker):
177183
assets_preprocess_dir = assets_dir / "preprocess"
178184
assets_preprocess_dir.mkdir()
179185

180-
(assets_dir / "styles.css").write_text(
186+
(assets_dir / "style.css").write_text(
181187
"button.rt-Button {\n\tborder-radius:unset !important;\n}"
182188
)
183189
(assets_preprocess_dir / "styles_a.sass").write_text(
@@ -196,34 +202,44 @@ def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker):
196202
)
197203

198204
stylesheets = [
199-
"/styles.css",
205+
"/style.css",
200206
"/preprocess/styles_a.sass",
201207
"/preprocess/styles_b.scss",
202208
]
203209

204210
assert compiler.compile_root_stylesheet(stylesheets) == (
205-
str(project / constants.Dirs.WEB / "styles" / "styles.css"),
211+
str(
212+
project
213+
/ constants.Dirs.WEB
214+
/ "styles"
215+
/ (PageNames.STYLESHEET_ROOT + ".css")
216+
),
206217
"@import url('./tailwind.css'); \n"
207-
"@import url('./styles.css'); \n"
218+
"@import url('./style.css'); \n"
208219
f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}'); \n"
209220
f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}'); \n",
210221
)
211222

212223
stylesheets = [
213-
"/styles.css",
224+
"/style.css",
214225
"/preprocess", # this is a folder containing "styles_a.sass" and "styles_b.scss"
215226
]
216227

217228
assert compiler.compile_root_stylesheet(stylesheets) == (
218-
str(project / constants.Dirs.WEB / "styles" / "styles.css"),
229+
str(
230+
project
231+
/ constants.Dirs.WEB
232+
/ "styles"
233+
/ (PageNames.STYLESHEET_ROOT + ".css")
234+
),
219235
"@import url('./tailwind.css'); \n"
220-
"@import url('./styles.css'); \n"
221-
f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}'); \n"
222-
f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}'); \n",
236+
"@import url('./style.css'); \n"
237+
f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}'); \n"
238+
f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}'); \n",
223239
)
224240

225-
assert (project / constants.Dirs.WEB / "styles" / "styles.css").read_text() == (
226-
assets_dir / "styles.css"
241+
assert (project / constants.Dirs.WEB / "styles" / "style.css").read_text() == (
242+
assets_dir / "style.css"
227243
).read_text()
228244

229245
expected_result = "button.rt-Button{border-radius:unset !important}\n"
@@ -252,16 +268,16 @@ def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
252268
mocker.patch.object(mock, "tailwind", None)
253269
mocker.patch("reflex.compiler.compiler.get_config", return_value=mock)
254270

255-
(assets_dir / "styles.css").touch()
271+
(assets_dir / "style.css").touch()
256272
mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
257273

258274
stylesheets = [
259-
"/styles.css",
275+
"/style.css",
260276
]
261277

262278
assert compiler.compile_root_stylesheet(stylesheets) == (
263-
str(Path(".web") / "styles" / "styles.css"),
264-
"@import url('./styles.css'); \n",
279+
str(Path(".web") / "styles" / (PageNames.STYLESHEET_ROOT + ".css")),
280+
"@import url('./style.css'); \n",
265281
)
266282

267283

@@ -280,7 +296,7 @@ def test_compile_nonexistent_stylesheet(tmp_path, mocker):
280296

281297
mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
282298

283-
stylesheets = ["/styles.css"]
299+
stylesheets = ["/style.css"]
284300

285301
with pytest.raises(FileNotFoundError):
286302
compiler.compile_root_stylesheet(stylesheets)

0 commit comments

Comments
 (0)