Skip to content

Commit

Permalink
Move custom styles to root App file(_app.js) (#1764)
Browse files Browse the repository at this point in the history
  • Loading branch information
ElijahAhianyo authored Sep 15, 2023
1 parent 63ae96e commit 74d227d
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 40 deletions.
5 changes: 5 additions & 0 deletions reflex/.templates/jinja/web/styles/styles.css.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{%- block imports_styles %}
{% for sheet_name in stylesheets %}
{{- "@import url('" + sheet_name + "'); " }}
{% endfor %}
{% endblock %}
7 changes: 5 additions & 2 deletions reflex/.templates/web/jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"compilerOptions": {
"baseUrl": "."
"baseUrl": ".",
"paths": {
"@/*": ["public/*"]
}
}
}
}
2 changes: 1 addition & 1 deletion reflex/.templates/web/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import theme from "/utils/theme";
import { clientStorage, initialEvents, initialState, StateContext, EventLoopContext } from "/utils/context.js";
import { useEventLoop } from "utils/state";

import '../styles/tailwind.css'
import '/styles/styles.css'

const GlobalStyles = css`
/* Hide the blue border around Chakra components. */
Expand Down
7 changes: 5 additions & 2 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,11 @@ def compile(self):
for component in custom_components:
all_imports.update(component.get_imports())

# Compile the root document with base styles and fonts
compile_results.append(compiler.compile_document_root(self.stylesheets))
# Compile the root stylesheet with base styles.
compile_results.append(compiler.compile_root_stylesheet(self.stylesheets))

# Compile the root document.
compile_results.append(compiler.compile_document_root())

# Compile the theme.
compile_results.append(compiler.compile_theme(self.style))
Expand Down
57 changes: 49 additions & 8 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Compiler for the reflex apps."""
from __future__ import annotations

import os
from pathlib import Path
from typing import List, Set, Tuple, Type

from reflex import constants
Expand Down Expand Up @@ -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:
"""Compile the components.
Expand Down Expand Up @@ -162,21 +208,17 @@ def _compile_tailwind(
)


def compile_document_root(stylesheets: List[str]) -> Tuple[str, str]:
def compile_document_root() -> Tuple[str, str]:
"""Compile the document root.
Args:
stylesheets: The stylesheets to include in the document root.
Returns:
The path and code of the compiled document root.
"""
# Get the path for the output file.
output_path = utils.get_page_path(constants.DOCUMENT_ROOT)

# Create the document root.
document_root = utils.create_document_root(stylesheets)

document_root = utils.create_document_root()
# Compile the document root.
code = _compile_document_root(document_root)
return output_path, code
Expand Down Expand Up @@ -279,5 +321,4 @@ def compile_tailwind(

def purge_web_pages_dir():
"""Empty out .web directory."""
template_files = ["_app.js"]
utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=template_files)
utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=["_app.js"])
3 changes: 3 additions & 0 deletions reflex/compiler/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ def get_template(name: str) -> Template:

# Sitemap config file.
SITEMAP_CONFIG = "module.exports = {config}".format

# Code to render the root stylesheet.
STYLE = get_template("web/styles/styles.css.jinja2")
34 changes: 27 additions & 7 deletions reflex/compiler/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import os
from typing import Any, Dict, List, Optional, Set, Tuple, Type
from urllib.parse import urlparse

from pydantic.fields import ModelField

Expand All @@ -18,7 +19,6 @@
Main,
Meta,
NextScript,
RawLink,
Title,
)
from reflex.components.component import Component, ComponentStyle, CustomComponent
Expand Down Expand Up @@ -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.
Args:
stylesheets: The list of stylesheets to include in the document root.
Returns:
The document root.
"""
sheets = [RawLink.create(rel="stylesheet", href=href) for href in stylesheets]
return Html.create(
DocumentHead.create(*sheets),
DocumentHead.create(),
Body.create(
ColorModeScript.create(),
Main.create(),
Expand Down Expand Up @@ -324,6 +320,17 @@ def get_theme_path() -> str:
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:
"""Get the path of the context / initial state file.
Expand Down Expand Up @@ -415,3 +422,16 @@ def empty_dir(path: str, keep_files: Optional[List[str]] = None):
for element in directory_contents:
if element not in keep_files:
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])
10 changes: 10 additions & 0 deletions reflex/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,14 @@ def get_fnm_name() -> str | None:
WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR)
# The directory where the assets are located.
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.
TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js")
# Default Tailwind content paths
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
NEXT_CONFIG_FILE = "next.config.js"
# The sitemap config file.
Expand All @@ -148,6 +152,8 @@ def get_fnm_name() -> str | None:
JS_EXT = ".js"
# The extension for python files.
PY_EXT = ".py"
# The extension for css files.
CSS_EXT = ".css"
# The expected variable name where the rx.App is stored.
APP_VAR = "app"
# The expected variable name where the API object is stored for deployment.
Expand All @@ -172,6 +178,10 @@ def get_fnm_name() -> str | None:
IS_HYDRATED = "is_hydrated"
# The name of the index page.
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.
DOCUMENT_ROOT = "_document"
# The name of the theme page.
Expand Down
75 changes: 55 additions & 20 deletions tests/compiler/test_compiler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
from typing import List, Set

import pytest

from reflex.compiler import utils
from reflex.compiler import compiler, utils
from reflex.utils import imports
from reflex.vars import ImportVar

Expand Down Expand Up @@ -106,22 +107,56 @@ def test_compile_imports(import_dict: imports.ImportDict, test_dicts: List[dict]
assert import_dict["rest"] == test_dict["rest"]


# @pytest.mark.parametrize(
# "name,value,output",
# [
# ("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:
# name: The name of the constant.
# value: The value of the constant.
# output: The expected output.
# """
# assert utils.compile_constant_declaration(name, value) == output
def test_compile_stylesheets(tmp_path, mocker):
"""Test that stylesheets compile correctly.
Args:
tmp_path: The test directory.
mocker: Pytest mocker object.
"""
project = tmp_path / "test_project"
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)

0 comments on commit 74d227d

Please sign in to comment.