浏览代码

bundle chakra in window for CSR (#4042)

* bundle chakra in window for CSR

* remove repeated chakra ui reference

* use dynamically generated libraries

* remove js from it
Khaleel Al-Adhami 8 月之前
父节点
当前提交
ad0827c59c

+ 6 - 8
reflex/.templates/jinja/web/pages/_app.js.jinja2

@@ -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}}
@@ -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;
   }, []);

+ 24 - 0
reflex/compiler/compiler.py

@@ -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.
 
@@ -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(),
     )
 

+ 25 - 8
reflex/components/dynamic.py

@@ -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.
@@ -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.
@@ -58,10 +81,7 @@ def load_dynamic_serializer():
             )
         ] = None
 
-        libs_in_window = [
-            "react",
-            "@radix-ui/themes",
-        ]
+        libs_in_window = bundled_libraries
 
         imports = {}
         for lib, names in component._get_all_imports().items():
@@ -69,10 +89,7 @@ def load_dynamic_serializer():
             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:

+ 4 - 0
reflex/utils/exceptions.py

@@ -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."""