Skip to content

Commit

Permalink
bundle chakra in window for CSR (#4042)
Browse files Browse the repository at this point in the history
* bundle chakra in window for CSR

* remove repeated chakra ui reference

* use dynamically generated libraries

* remove js from it
  • Loading branch information
adhami3310 authored and simon committed Oct 23, 2024
1 parent b9852a2 commit 321a2ae
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 16 deletions.
14 changes: 6 additions & 8 deletions reflex/.templates/jinja/web/pages/_app.js.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import '/styles/styles.css'
{% block declaration %}
import { EventLoopProvider, StateProvider, defaultColorMode } from "/utils/context.js";
import { ThemeProvider } from 'next-themes'
import * as React from "react";
import * as utils_context from "/utils/context.js";
import * as utils_state from "/utils/state.js";
import * as radix from "@radix-ui/themes";
{% for library_alias, library_path in window_libraries %}
import * as {{library_alias}} from "{{library_path}}";
{% endfor %}

{% for custom_code in custom_codes %}
{{custom_code}}
Expand All @@ -33,10 +32,9 @@ export default function MyApp({ Component, pageProps }) {
React.useEffect(() => {
// Make contexts and state objects available globally for dynamic eval'd components
let windowImports = {
"react": React,
"@radix-ui/themes": radix,
"/utils/context": utils_context,
"/utils/state": utils_state,
{% for library_alias, library_path in window_libraries %}
"{{library_path}}": {{library_alias}},
{% endfor %}
};
window["__reflex"] = windowImports;
}, []);
Expand Down
24 changes: 24 additions & 0 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ def _compile_document_root(root: Component) -> str:
)


def _normalize_library_name(lib: str) -> str:
"""Normalize the library name.
Args:
lib: The library name to normalize.
Returns:
The normalized library name.
"""
if lib == "react":
return "React"
return lib.replace("@", "").replace("/", "_").replace("-", "_")


def _compile_app(app_root: Component) -> str:
"""Compile the app template component.
Expand All @@ -49,10 +63,20 @@ def _compile_app(app_root: Component) -> str:
Returns:
The compiled app.
"""
from reflex.components.dynamic import bundled_libraries

window_libraries = [
(_normalize_library_name(name), name) for name in bundled_libraries
] + [
("utils_context", f"/{constants.Dirs.UTILS}/context"),
("utils_state", f"/{constants.Dirs.UTILS}/state"),
]

return templates.APP_ROOT.render(
imports=utils.compile_imports(app_root._get_all_imports()),
custom_codes=app_root._get_all_custom_code(),
hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()},
window_libraries=window_libraries,
render=app_root.render(),
)

Expand Down
33 changes: 25 additions & 8 deletions reflex/components/dynamic.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"""Components that are dynamically generated on the backend."""

from typing import TYPE_CHECKING

from reflex import constants
from reflex.utils import imports
from reflex.utils.exceptions import DynamicComponentMissingLibrary
from reflex.utils.format import format_library_name
from reflex.utils.serializers import serializer
from reflex.vars import Var, get_unique_variable_name
from reflex.vars.base import VarData, transform

if TYPE_CHECKING:
from reflex.components.component import Component


def get_cdn_url(lib: str) -> str:
"""Get the CDN URL for a library.
Expand All @@ -20,6 +26,23 @@ def get_cdn_url(lib: str) -> str:
return f"https://cdn.jsdelivr.net/npm/{lib}" + "/+esm"


bundled_libraries = {"react", "@radix-ui/themes"}


def bundle_library(component: "Component"):
"""Bundle a library with the component.
Args:
component: The component to bundle the library with.
Raises:
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
"""
if component.library is None:
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
bundled_libraries.add(format_library_name(component.library))


def load_dynamic_serializer():
"""Load the serializer for dynamic components."""
# Causes a circular import, so we import here.
Expand Down Expand Up @@ -58,21 +81,15 @@ def make_component(component: Component) -> str:
)
] = None

libs_in_window = [
"react",
"@radix-ui/themes",
]
libs_in_window = bundled_libraries

imports = {}
for lib, names in component._get_all_imports().items():
formatted_lib_name = format_library_name(lib)
if (
not lib.startswith((".", "/"))
and not lib.startswith("http")
and all(
formatted_lib_name != lib_in_window
for lib_in_window in libs_in_window
)
and formatted_lib_name not in libs_in_window
):
imports[get_cdn_url(lib)] = names
else:
Expand Down
4 changes: 4 additions & 0 deletions reflex/utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,9 @@ class InvalidLifespanTaskType(ReflexError, TypeError):
"""Raised when an invalid task type is registered as a lifespan task."""


class DynamicComponentMissingLibrary(ReflexError, ValueError):
"""Raised when a dynamic component is missing a library."""


class SetUndefinedStateVarError(ReflexError, AttributeError):
"""Raised when setting the value of a var without first declaring it."""

0 comments on commit 321a2ae

Please sign in to comment.