Procházet zdrojové kódy

use $ syntax (#4237)

* use $ syntax

* missing test case change

* try being a little smart

* improve merge imports logic

* add public as well

* oops missed that one

* last one there
Khaleel Al-Adhami před 6 měsíci
rodič
revize
c3848d0db4

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

@@ -1,11 +1,11 @@
 {% extends "web/pages/base_page.js.jinja2" %}
 
 {% block early_imports %}
-import '/styles/styles.css'
+import '$/styles/styles.css'
 {% endblock %}
 
 {% block declaration %}
-import { EventLoopProvider, StateProvider, defaultColorMode } from "/utils/context.js";
+import { EventLoopProvider, StateProvider, defaultColorMode } from "$/utils/context.js";
 import { ThemeProvider } from 'next-themes'
 {% for library_alias, library_path in  window_libraries %}
 import * as {{library_alias}} from "{{library_path}}";

+ 3 - 1
reflex/.templates/jinja/web/utils/context.js.jinja2

@@ -1,5 +1,5 @@
 import { createContext, useContext, useMemo, useReducer, useState } from "react"
-import { applyDelta, Event, hydrateClientStorage, useEventLoop, refs } from "/utils/state.js"
+import { applyDelta, Event, hydrateClientStorage, useEventLoop, refs } from "$/utils/state.js"
 
 {% if initial_state %}
 export const initialState = {{ initial_state|json_dumps }}
@@ -59,6 +59,8 @@ export const initialEvents = () => [
 {% else %}
 export const state_name = undefined
 
+export const exception_state_name = undefined
+
 export const onLoadInternalEvent = () => []
 
 export const initialEvents = () => []

+ 3 - 3
reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js

@@ -4,8 +4,8 @@ import {
   ColorModeContext,
   defaultColorMode,
   isDevMode,
-  lastCompiledTimeStamp
-} from "/utils/context.js";
+  lastCompiledTimeStamp,
+} from "$/utils/context.js";
 
 export default function RadixThemesColorModeProvider({ children }) {
   const { theme, resolvedTheme, setTheme } = useTheme();
@@ -37,7 +37,7 @@ export default function RadixThemesColorModeProvider({ children }) {
     const allowedModes = ["light", "dark", "system"];
     if (!allowedModes.includes(mode)) {
       console.error(
-        `Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`,
+        `Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`
       );
       mode = defaultColorMode;
     }

+ 2 - 1
reflex/.templates/web/jsconfig.json

@@ -2,7 +2,8 @@
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
+      "$/*": ["*"],
       "@/*": ["public/*"]
     }
   }
-}
+}

+ 4 - 4
reflex/.templates/web/utils/state.js

@@ -2,7 +2,7 @@
 import axios from "axios";
 import io from "socket.io-client";
 import JSON5 from "json5";
-import env from "/env.json";
+import env from "$/env.json";
 import Cookies from "universal-cookie";
 import { useEffect, useRef, useState } from "react";
 import Router, { useRouter } from "next/router";
@@ -12,9 +12,9 @@ import {
   onLoadInternalEvent,
   state_name,
   exception_state_name,
-} from "utils/context.js";
-import debounce from "/utils/helpers/debounce";
-import throttle from "/utils/helpers/throttle";
+} from "$/utils/context.js";
+import debounce from "$/utils/helpers/debounce";
+import throttle from "$/utils/helpers/throttle";
 import * as Babel from "@babel/standalone";
 
 // Endpoint URLs.

+ 1 - 1
reflex/app.py

@@ -679,7 +679,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
             for i, tags in imports.items()
             if i not in constants.PackageJson.DEPENDENCIES
             and i not in constants.PackageJson.DEV_DEPENDENCIES
-            and not any(i.startswith(prefix) for prefix in ["/", ".", "next/"])
+            and not any(i.startswith(prefix) for prefix in ["/", "$/", ".", "next/"])
             and i != ""
             and any(tag.install for tag in tags)
         }

+ 4 - 4
reflex/compiler/compiler.py

@@ -67,8 +67,8 @@ def _compile_app(app_root: Component) -> str:
     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"),
+        ("utils_context", f"$/{constants.Dirs.UTILS}/context"),
+        ("utils_state", f"$/{constants.Dirs.UTILS}/state"),
     ]
 
     return templates.APP_ROOT.render(
@@ -228,7 +228,7 @@ def _compile_components(
     """
     imports = {
         "react": [ImportVar(tag="memo")],
-        f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="E"), ImportVar(tag="isTrue")],
+        f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="E"), ImportVar(tag="isTrue")],
     }
     component_renders = []
 
@@ -315,7 +315,7 @@ def _compile_stateful_components(
     # Don't import from the file that we're about to create.
     all_imports = utils.merge_imports(*all_import_dicts)
     all_imports.pop(
-        f"/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None
+        f"$/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None
     )
 
     return templates.STATEFUL_COMPONENTS.render(

+ 6 - 0
reflex/compiler/utils.py

@@ -83,6 +83,12 @@ def validate_imports(import_dict: ParsedImportDict):
                 f"{_import.tag}/{_import.alias}" if _import.alias else _import.tag
             )
             if import_name in used_tags:
+                already_imported = used_tags[import_name]
+                if (already_imported[0] == "$" and already_imported[1:] == lib) or (
+                    lib[0] == "$" and lib[1:] == already_imported
+                ):
+                    used_tags[import_name] = lib if lib[0] == "$" else already_imported
+                    continue
                 raise ValueError(
                     f"Can not compile, the tag {import_name} is used multiple time from {lib} and {used_tags[import_name]}"
                 )

+ 5 - 3
reflex/components/component.py

@@ -1308,7 +1308,9 @@ class Component(BaseComponent, ABC):
         if self._get_ref_hook():
             # Handle hooks needed for attaching react refs to DOM nodes.
             _imports.setdefault("react", set()).add(ImportVar(tag="useRef"))
-            _imports.setdefault(f"/{Dirs.STATE_PATH}", set()).add(ImportVar(tag="refs"))
+            _imports.setdefault(f"$/{Dirs.STATE_PATH}", set()).add(
+                ImportVar(tag="refs")
+            )
 
         if self._get_mount_lifecycle_hook():
             # Handle hooks for `on_mount` / `on_unmount`.
@@ -1665,7 +1667,7 @@ class CustomComponent(Component):
     """A custom user-defined component."""
 
     # Use the components library.
-    library = f"/{Dirs.COMPONENTS_PATH}"
+    library = f"$/{Dirs.COMPONENTS_PATH}"
 
     # The function that creates the component.
     component_fn: Callable[..., Component] = Component.create
@@ -2233,7 +2235,7 @@ class StatefulComponent(BaseComponent):
         """
         if self.rendered_as_shared:
             return {
-                f"/{Dirs.UTILS}/{PageNames.STATEFUL_COMPONENTS}": [
+                f"$/{Dirs.UTILS}/{PageNames.STATEFUL_COMPONENTS}": [
                     ImportVar(tag=self.tag)
                 ]
             }

+ 2 - 2
reflex/components/core/banner.py

@@ -66,8 +66,8 @@ class WebsocketTargetURL(Var):
             _js_expr="getBackendURL(env.EVENT).href",
             _var_data=VarData(
                 imports={
-                    "/env.json": [ImportVar(tag="env", is_default=True)],
-                    f"/{Dirs.STATE_PATH}": [ImportVar(tag="getBackendURL")],
+                    "$/env.json": [ImportVar(tag="env", is_default=True)],
+                    f"$/{Dirs.STATE_PATH}": [ImportVar(tag="getBackendURL")],
                 },
             ),
             _var_type=WebsocketTargetURL,

+ 1 - 1
reflex/components/core/client_side_routing.py

@@ -21,7 +21,7 @@ route_not_found: Var = Var(_js_expr=constants.ROUTE_NOT_FOUND)
 class ClientSideRouting(Component):
     """The client-side routing component."""
 
-    library = "/utils/client_side_routing"
+    library = "$/utils/client_side_routing"
     tag = "useClientSideRouting"
 
     def add_hooks(self) -> list[str]:

+ 1 - 1
reflex/components/core/clipboard.py

@@ -67,7 +67,7 @@ class Clipboard(Fragment):
             The import dict for the component.
         """
         return {
-            "/utils/helpers/paste.js": ImportVar(
+            "$/utils/helpers/paste.js": ImportVar(
                 tag="usePasteHandler", is_default=True
             ),
         }

+ 1 - 1
reflex/components/core/cond.py

@@ -15,7 +15,7 @@ from reflex.vars.base import LiteralVar, Var
 from reflex.vars.number import ternary_operation
 
 _IS_TRUE_IMPORT: ImportDict = {
-    f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
+    f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
 }
 
 

+ 4 - 4
reflex/components/core/upload.py

@@ -29,7 +29,7 @@ DEFAULT_UPLOAD_ID: str = "default"
 upload_files_context_var_data: VarData = VarData(
     imports={
         "react": "useContext",
-        f"/{Dirs.CONTEXTS_PATH}": "UploadFilesContext",
+        f"$/{Dirs.CONTEXTS_PATH}": "UploadFilesContext",
     },
     hooks={
         "const [filesById, setFilesById] = useContext(UploadFilesContext);": None,
@@ -134,8 +134,8 @@ uploaded_files_url_prefix = Var(
     _js_expr="getBackendURL(env.UPLOAD)",
     _var_data=VarData(
         imports={
-            f"/{Dirs.STATE_PATH}": "getBackendURL",
-            "/env.json": ImportVar(tag="env", is_default=True),
+            f"$/{Dirs.STATE_PATH}": "getBackendURL",
+            "$/env.json": ImportVar(tag="env", is_default=True),
         }
     ),
 ).to(str)
@@ -170,7 +170,7 @@ def _on_drop_spec(files: Var) -> Tuple[Var[Any]]:
 class UploadFilesProvider(Component):
     """AppWrap component that provides a dict of selected files by ID via useContext."""
 
-    library = f"/{Dirs.CONTEXTS_PATH}"
+    library = f"$/{Dirs.CONTEXTS_PATH}"
     tag = "UploadFilesProvider"
 
 

+ 2 - 2
reflex/components/core/upload.pyi

@@ -34,8 +34,8 @@ uploaded_files_url_prefix = Var(
     _js_expr="getBackendURL(env.UPLOAD)",
     _var_data=VarData(
         imports={
-            f"/{Dirs.STATE_PATH}": "getBackendURL",
-            "/env.json": ImportVar(tag="env", is_default=True),
+            f"$/{Dirs.STATE_PATH}": "getBackendURL",
+            "$/env.json": ImportVar(tag="env", is_default=True),
         }
     ),
 ).to(str)

+ 1 - 1
reflex/components/datadisplay/dataeditor.py

@@ -344,7 +344,7 @@ class DataEditor(NoSSRComponent):
         return {
             "": f"{format.format_library_name(self.library)}/dist/index.css",
             self.library: "GridCellKind",
-            "/utils/helpers/dataeditor.js": ImportVar(
+            "$/utils/helpers/dataeditor.js": ImportVar(
                 tag="formatDataEditorCells", is_default=False, install=False
             ),
         }

+ 3 - 3
reflex/components/dynamic.py

@@ -90,7 +90,7 @@ def load_dynamic_serializer():
         for lib, names in component._get_all_imports().items():
             formatted_lib_name = format_library_name(lib)
             if (
-                not lib.startswith((".", "/"))
+                not lib.startswith((".", "/", "$/"))
                 and not lib.startswith("http")
                 and formatted_lib_name not in libs_in_window
             ):
@@ -106,7 +106,7 @@ def load_dynamic_serializer():
         # Rewrite imports from `/` to destructure from window
         for ix, line in enumerate(module_code_lines[:]):
             if line.startswith("import "):
-                if 'from "/' in line:
+                if 'from "$/' in line or 'from "/' in line:
                     module_code_lines[ix] = (
                         line.replace("import ", "const ", 1).replace(
                             " from ", " = window['__reflex'][", 1
@@ -157,7 +157,7 @@ def load_dynamic_serializer():
             merge_var_data=VarData.merge(
                 VarData(
                     imports={
-                        f"/{constants.Dirs.STATE_PATH}": [
+                        f"$/{constants.Dirs.STATE_PATH}": [
                             imports.ImportVar(tag="evalReactComponent"),
                         ],
                         "react": [

+ 1 - 1
reflex/components/el/elements/forms.py

@@ -187,7 +187,7 @@ class Form(BaseHTML):
         """
         return {
             "react": "useCallback",
-            f"/{Dirs.STATE_PATH}": ["getRefValue", "getRefValues"],
+            f"$/{Dirs.STATE_PATH}": ["getRefValue", "getRefValues"],
         }
 
     def add_hooks(self) -> list[str]:

+ 2 - 2
reflex/components/radix/themes/base.py

@@ -221,7 +221,7 @@ class Theme(RadixThemesComponent):
             The import dict.
         """
         _imports: ImportDict = {
-            "/utils/theme.js": [ImportVar(tag="theme", is_default=True)],
+            "$/utils/theme.js": [ImportVar(tag="theme", is_default=True)],
         }
         if get_config().tailwind is None:
             # When tailwind is disabled, import the radix-ui styles directly because they will
@@ -265,7 +265,7 @@ class ThemePanel(RadixThemesComponent):
 class RadixThemesColorModeProvider(Component):
     """Next-themes integration for radix themes components."""
 
-    library = "/components/reflex/radix_themes_color_mode_provider.js"
+    library = "$/components/reflex/radix_themes_color_mode_provider.js"
     tag = "RadixThemesColorModeProvider"
     is_default = True
 

+ 1 - 1
reflex/components/sonner/toast.py

@@ -251,7 +251,7 @@ class Toaster(Component):
             _js_expr=f"{toast_ref} = toast",
             _var_data=VarData(
                 imports={
-                    "/utils/state": [ImportVar(tag="refs")],
+                    "$/utils/state": [ImportVar(tag="refs")],
                     self.library: [ImportVar(tag="toast", install=False)],
                 }
             ),

+ 2 - 2
reflex/constants/compiler.py

@@ -114,8 +114,8 @@ class Imports(SimpleNamespace):
 
     EVENTS = {
         "react": [ImportVar(tag="useContext")],
-        f"/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
-        f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
+        f"$/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
+        f"$/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
     }
 
 

+ 1 - 1
reflex/experimental/client_state.py

@@ -21,7 +21,7 @@ NoValue = object()
 
 
 _refs_import = {
-    f"/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
+    f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="refs")],
 }
 
 

+ 1 - 1
reflex/style.py

@@ -23,7 +23,7 @@ LiteralColorMode = Literal["system", "light", "dark"]
 
 # Reference the global ColorModeContext
 color_mode_imports = {
-    f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
+    f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
     "react": [ImportVar(tag="useContext")],
 }
 

+ 6 - 0
reflex/utils/imports.py

@@ -23,6 +23,12 @@ def merge_imports(
         for lib, fields in (
             import_dict if isinstance(import_dict, tuple) else import_dict.items()
         ):
+            # If the lib is an absolute path, we need to prefix it with a $
+            lib = (
+                "$" + lib
+                if lib.startswith(("/utils/", "/components/", "/styles/", "/public/"))
+                else lib
+            )
             if isinstance(fields, (list, tuple, set)):
                 all_imports[lib].extend(
                     (

+ 3 - 3
reflex/vars/base.py

@@ -217,7 +217,7 @@ class VarData:
                 ): None
             },
             imports={
-                f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
+                f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
                 "react": [ImportVar(tag="useContext")],
             },
         )
@@ -956,7 +956,7 @@ class Var(Generic[VAR_TYPE]):
             _js_expr="refs",
             _var_data=VarData(
                 imports={
-                    f"/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
+                    f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
                 }
             ),
         ).to(ObjectVar, Dict[str, str])
@@ -2530,7 +2530,7 @@ def get_uuid_string_var() -> Var:
     unique_uuid_var = get_unique_variable_name()
     unique_uuid_var_data = VarData(
         imports={
-            f"/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")},  # type: ignore
+            f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")},  # type: ignore
             "react": "useMemo",
         },
         hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None},

+ 1 - 1
reflex/vars/number.py

@@ -1090,7 +1090,7 @@ boolean_types = Union[BooleanVar, bool]
 
 
 _IS_TRUE_IMPORT: ImportDict = {
-    f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
+    f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
 }
 
 

+ 7 - 7
tests/units/components/core/test_banner.py

@@ -12,7 +12,7 @@ def test_websocket_target_url():
     var_data = url._get_all_var_data()
     assert var_data is not None
     assert sorted(tuple((key for key, _ in var_data.imports))) == sorted(
-        ("/utils/state", "/env.json")
+        ("$/utils/state", "$/env.json")
     )
 
 
@@ -22,10 +22,10 @@ def test_connection_banner():
     assert sorted(tuple(_imports)) == sorted(
         (
             "react",
-            "/utils/context",
-            "/utils/state",
+            "$/utils/context",
+            "$/utils/state",
             "@radix-ui/themes@^3.0.0",
-            "/env.json",
+            "$/env.json",
         )
     )
 
@@ -40,10 +40,10 @@ def test_connection_modal():
     assert sorted(tuple(_imports)) == sorted(
         (
             "react",
-            "/utils/context",
-            "/utils/state",
+            "$/utils/context",
+            "$/utils/state",
             "@radix-ui/themes@^3.0.0",
-            "/env.json",
+            "$/env.json",
         )
     )