Move custom styles to root App file(_app.js) (#1764)
This commit is contained in:
parent
63ae96ede3
commit
74d227d2fd
5
reflex/.templates/jinja/web/styles/styles.css.jinja2
Normal file
5
reflex/.templates/jinja/web/styles/styles.css.jinja2
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{%- block imports_styles %}
|
||||||
|
{% for sheet_name in stylesheets %}
|
||||||
|
{{- "@import url('" + sheet_name + "'); " }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "."
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["public/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ import theme from "/utils/theme";
|
|||||||
import { clientStorage, initialEvents, initialState, StateContext, EventLoopContext } from "/utils/context.js";
|
import { clientStorage, initialEvents, initialState, StateContext, EventLoopContext } from "/utils/context.js";
|
||||||
import { useEventLoop } from "utils/state";
|
import { useEventLoop } from "utils/state";
|
||||||
|
|
||||||
import '../styles/tailwind.css'
|
import '/styles/styles.css'
|
||||||
|
|
||||||
const GlobalStyles = css`
|
const GlobalStyles = css`
|
||||||
/* Hide the blue border around Chakra components. */
|
/* Hide the blue border around Chakra components. */
|
||||||
|
@ -612,8 +612,11 @@ class App(Base):
|
|||||||
for component in custom_components:
|
for component in custom_components:
|
||||||
all_imports.update(component.get_imports())
|
all_imports.update(component.get_imports())
|
||||||
|
|
||||||
# Compile the root document with base styles and fonts
|
# Compile the root stylesheet with base styles.
|
||||||
compile_results.append(compiler.compile_document_root(self.stylesheets))
|
compile_results.append(compiler.compile_root_stylesheet(self.stylesheets))
|
||||||
|
|
||||||
|
# Compile the root document.
|
||||||
|
compile_results.append(compiler.compile_document_root())
|
||||||
|
|
||||||
# Compile the theme.
|
# Compile the theme.
|
||||||
compile_results.append(compiler.compile_theme(self.style))
|
compile_results.append(compiler.compile_theme(self.style))
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Compiler for the reflex apps."""
|
"""Compiler for the reflex apps."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from typing import List, Set, Tuple, Type
|
from typing import List, Set, Tuple, Type
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
@ -118,6 +120,50 @@ def _compile_page(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_root_stylesheet(stylesheets: List[str]) -> Tuple[str, str]:
|
||||||
|
"""Compile the root stylesheet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stylesheets: The stylesheets to include in the root stylesheet.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path and code of the compiled root stylesheet.
|
||||||
|
"""
|
||||||
|
output_path = utils.get_root_stylesheet_path()
|
||||||
|
|
||||||
|
code = _compile_root_stylesheet(stylesheets)
|
||||||
|
|
||||||
|
return output_path, code
|
||||||
|
|
||||||
|
|
||||||
|
def _compile_root_stylesheet(stylesheets: List[str]) -> str:
|
||||||
|
"""Compile the root stylesheet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stylesheets: The stylesheets to include in the root stylesheet.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The compiled root stylesheet.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If a specified stylesheet in assets directory does not exist.
|
||||||
|
"""
|
||||||
|
sheets = [constants.TAILWIND_ROOT_STYLE_PATH]
|
||||||
|
for stylesheet in stylesheets:
|
||||||
|
if not utils.is_valid_url(stylesheet):
|
||||||
|
# check if stylesheet provided exists.
|
||||||
|
stylesheet_full_path = (
|
||||||
|
Path.cwd() / constants.APP_ASSETS_DIR / stylesheet.strip("/")
|
||||||
|
)
|
||||||
|
if not os.path.exists(stylesheet_full_path):
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"The stylesheet file {stylesheet_full_path} does not exist."
|
||||||
|
)
|
||||||
|
stylesheet = f"@/{stylesheet.strip('/')}"
|
||||||
|
sheets.append(stylesheet) if stylesheet not in sheets else None
|
||||||
|
return templates.STYLE.render(stylesheets=sheets)
|
||||||
|
|
||||||
|
|
||||||
def _compile_components(components: Set[CustomComponent]) -> str:
|
def _compile_components(components: Set[CustomComponent]) -> str:
|
||||||
"""Compile the components.
|
"""Compile the components.
|
||||||
|
|
||||||
@ -162,12 +208,9 @@ def _compile_tailwind(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def compile_document_root(stylesheets: List[str]) -> Tuple[str, str]:
|
def compile_document_root() -> Tuple[str, str]:
|
||||||
"""Compile the document root.
|
"""Compile the document root.
|
||||||
|
|
||||||
Args:
|
|
||||||
stylesheets: The stylesheets to include in the document root.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The path and code of the compiled document root.
|
The path and code of the compiled document root.
|
||||||
"""
|
"""
|
||||||
@ -175,8 +218,7 @@ def compile_document_root(stylesheets: List[str]) -> Tuple[str, str]:
|
|||||||
output_path = utils.get_page_path(constants.DOCUMENT_ROOT)
|
output_path = utils.get_page_path(constants.DOCUMENT_ROOT)
|
||||||
|
|
||||||
# Create the document root.
|
# Create the document root.
|
||||||
document_root = utils.create_document_root(stylesheets)
|
document_root = utils.create_document_root()
|
||||||
|
|
||||||
# Compile the document root.
|
# Compile the document root.
|
||||||
code = _compile_document_root(document_root)
|
code = _compile_document_root(document_root)
|
||||||
return output_path, code
|
return output_path, code
|
||||||
@ -279,5 +321,4 @@ def compile_tailwind(
|
|||||||
|
|
||||||
def purge_web_pages_dir():
|
def purge_web_pages_dir():
|
||||||
"""Empty out .web directory."""
|
"""Empty out .web directory."""
|
||||||
template_files = ["_app.js"]
|
utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=["_app.js"])
|
||||||
utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=template_files)
|
|
||||||
|
@ -77,3 +77,6 @@ COMPONENTS = get_template("web/pages/custom_component.js.jinja2")
|
|||||||
|
|
||||||
# Sitemap config file.
|
# Sitemap config file.
|
||||||
SITEMAP_CONFIG = "module.exports = {config}".format
|
SITEMAP_CONFIG = "module.exports = {config}".format
|
||||||
|
|
||||||
|
# Code to render the root stylesheet.
|
||||||
|
STYLE = get_template("web/styles/styles.css.jinja2")
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple, Type
|
from typing import Any, Dict, List, Optional, Set, Tuple, Type
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from pydantic.fields import ModelField
|
from pydantic.fields import ModelField
|
||||||
|
|
||||||
@ -18,7 +19,6 @@ from reflex.components.base import (
|
|||||||
Main,
|
Main,
|
||||||
Meta,
|
Meta,
|
||||||
NextScript,
|
NextScript,
|
||||||
RawLink,
|
|
||||||
Title,
|
Title,
|
||||||
)
|
)
|
||||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||||
@ -257,18 +257,14 @@ def compile_custom_component(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_document_root(stylesheets: List[str]) -> Component:
|
def create_document_root() -> Component:
|
||||||
"""Create the document root.
|
"""Create the document root.
|
||||||
|
|
||||||
Args:
|
|
||||||
stylesheets: The list of stylesheets to include in the document root.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The document root.
|
The document root.
|
||||||
"""
|
"""
|
||||||
sheets = [RawLink.create(rel="stylesheet", href=href) for href in stylesheets]
|
|
||||||
return Html.create(
|
return Html.create(
|
||||||
DocumentHead.create(*sheets),
|
DocumentHead.create(),
|
||||||
Body.create(
|
Body.create(
|
||||||
ColorModeScript.create(),
|
ColorModeScript.create(),
|
||||||
Main.create(),
|
Main.create(),
|
||||||
@ -324,6 +320,17 @@ def get_theme_path() -> str:
|
|||||||
return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
|
return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
|
||||||
|
|
||||||
|
|
||||||
|
def get_root_stylesheet_path() -> str:
|
||||||
|
"""Get the path of the app root file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path of the app root file.
|
||||||
|
"""
|
||||||
|
return os.path.join(
|
||||||
|
constants.STYLES_DIR, constants.STYLESHEET_ROOT + constants.CSS_EXT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_context_path() -> str:
|
def get_context_path() -> str:
|
||||||
"""Get the path of the context / initial state file.
|
"""Get the path of the context / initial state file.
|
||||||
|
|
||||||
@ -415,3 +422,16 @@ def empty_dir(path: str, keep_files: Optional[List[str]] = None):
|
|||||||
for element in directory_contents:
|
for element in directory_contents:
|
||||||
if element not in keep_files:
|
if element not in keep_files:
|
||||||
path_ops.rm(os.path.join(path, element))
|
path_ops.rm(os.path.join(path, element))
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_url(url) -> bool:
|
||||||
|
"""Check if a url is valid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The Url to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether url is valid.
|
||||||
|
"""
|
||||||
|
result = urlparse(url)
|
||||||
|
return all([result.scheme, result.netloc])
|
||||||
|
@ -126,10 +126,14 @@ WEB_STATIC_DIR = os.path.join(WEB_DIR, STATIC_DIR)
|
|||||||
WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR)
|
WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR)
|
||||||
# The directory where the assets are located.
|
# The directory where the assets are located.
|
||||||
WEB_ASSETS_DIR = os.path.join(WEB_DIR, "public")
|
WEB_ASSETS_DIR = os.path.join(WEB_DIR, "public")
|
||||||
|
# The directory where styles are located.
|
||||||
|
STYLES_DIR = os.path.join(WEB_DIR, "styles")
|
||||||
# The Tailwind config.
|
# The Tailwind config.
|
||||||
TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js")
|
TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js")
|
||||||
# Default Tailwind content paths
|
# Default Tailwind content paths
|
||||||
TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
|
TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
|
||||||
|
# Relative tailwind style path to root stylesheet in STYLES_DIR.
|
||||||
|
TAILWIND_ROOT_STYLE_PATH = "./tailwind.css"
|
||||||
# The NextJS config file
|
# The NextJS config file
|
||||||
NEXT_CONFIG_FILE = "next.config.js"
|
NEXT_CONFIG_FILE = "next.config.js"
|
||||||
# The sitemap config file.
|
# The sitemap config file.
|
||||||
@ -148,6 +152,8 @@ ENV_JSON = os.path.join(WEB_DIR, "env.json")
|
|||||||
JS_EXT = ".js"
|
JS_EXT = ".js"
|
||||||
# The extension for python files.
|
# The extension for python files.
|
||||||
PY_EXT = ".py"
|
PY_EXT = ".py"
|
||||||
|
# The extension for css files.
|
||||||
|
CSS_EXT = ".css"
|
||||||
# The expected variable name where the rx.App is stored.
|
# The expected variable name where the rx.App is stored.
|
||||||
APP_VAR = "app"
|
APP_VAR = "app"
|
||||||
# The expected variable name where the API object is stored for deployment.
|
# The expected variable name where the API object is stored for deployment.
|
||||||
@ -172,6 +178,10 @@ HYDRATE = "hydrate"
|
|||||||
IS_HYDRATED = "is_hydrated"
|
IS_HYDRATED = "is_hydrated"
|
||||||
# The name of the index page.
|
# The name of the index page.
|
||||||
INDEX_ROUTE = "index"
|
INDEX_ROUTE = "index"
|
||||||
|
# The name of the app root page.
|
||||||
|
APP_ROOT = "_app"
|
||||||
|
# The root stylesheet filename.
|
||||||
|
STYLESHEET_ROOT = "styles"
|
||||||
# The name of the document root page.
|
# The name of the document root page.
|
||||||
DOCUMENT_ROOT = "_document"
|
DOCUMENT_ROOT = "_document"
|
||||||
# The name of the theme page.
|
# The name of the theme page.
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import os
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from reflex.compiler import utils
|
from reflex.compiler import compiler, utils
|
||||||
from reflex.utils import imports
|
from reflex.utils import imports
|
||||||
from reflex.vars import ImportVar
|
from reflex.vars import ImportVar
|
||||||
|
|
||||||
@ -106,22 +107,56 @@ def test_compile_imports(import_dict: imports.ImportDict, test_dicts: List[dict]
|
|||||||
assert import_dict["rest"] == test_dict["rest"]
|
assert import_dict["rest"] == test_dict["rest"]
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.parametrize(
|
def test_compile_stylesheets(tmp_path, mocker):
|
||||||
# "name,value,output",
|
"""Test that stylesheets compile correctly.
|
||||||
# [
|
|
||||||
# ("foo", "bar", 'const foo = "bar"'),
|
|
||||||
# ("num", 1, "const num = 1"),
|
|
||||||
# ("check", False, "const check = false"),
|
|
||||||
# ("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
|
|
||||||
# ("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
|
|
||||||
# ],
|
|
||||||
# )
|
|
||||||
# def test_compile_constant_declaration(name: str, value: str, output: str):
|
|
||||||
# """Test the compile_constant_declaration function.
|
|
||||||
|
|
||||||
# Args:
|
Args:
|
||||||
# name: The name of the constant.
|
tmp_path: The test directory.
|
||||||
# value: The value of the constant.
|
mocker: Pytest mocker object.
|
||||||
# output: The expected output.
|
"""
|
||||||
# """
|
project = tmp_path / "test_project"
|
||||||
# assert utils.compile_constant_declaration(name, value) == output
|
project.mkdir()
|
||||||
|
|
||||||
|
assets_dir = project / "assets"
|
||||||
|
assets_dir.mkdir()
|
||||||
|
|
||||||
|
(assets_dir / "styles.css").touch()
|
||||||
|
|
||||||
|
mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
|
||||||
|
|
||||||
|
stylesheets = [
|
||||||
|
"https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple",
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css",
|
||||||
|
"/styles.css",
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert compiler.compile_root_stylesheet(stylesheets) == (
|
||||||
|
os.path.join(".web", "styles", "styles.css"),
|
||||||
|
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('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css'); \n",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compile_nonexistent_stylesheet(tmp_path, mocker):
|
||||||
|
"""Test that an error is thrown for non-existent stylesheets.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path: The test directory.
|
||||||
|
mocker: Pytest mocker object.
|
||||||
|
"""
|
||||||
|
project = tmp_path / "test_project"
|
||||||
|
project.mkdir()
|
||||||
|
|
||||||
|
assets_dir = project / "assets"
|
||||||
|
assets_dir.mkdir()
|
||||||
|
|
||||||
|
mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
|
||||||
|
|
||||||
|
stylesheets = ["/styles.css"]
|
||||||
|
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
compiler.compile_root_stylesheet(stylesheets)
|
||||||
|
Loading…
Reference in New Issue
Block a user