Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bundle chakra in window for CSR #4042

Merged
merged 5 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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."""
Loading