Browse Source

in theory, one can get rid of jsx, but that would be craaazy

Khaleel Al-Adhami 2 months ago
parent
commit
7d26ba751a

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

@@ -40,25 +40,19 @@ export function Layout({children}) {
     window["__reflex"] = windowImports;
   }, []);
 
-  return (
-    <AppLayout>
-      <ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
-        <StateProvider>
-          <EventLoopProvider>
-            <AppWrap>
-              {children}
-            </AppWrap>
-          </EventLoopProvider>
-        </StateProvider>
-      </ThemeProvider>
-    </AppLayout>
-  )
+  return jsx(AppLayout, {},
+    jsx(ThemeProvider, {defaultTheme: defaultColorMode, attribute: "class"},
+      jsx(StateProvider, {},
+        jsx(EventLoopProvider, {},
+          jsx(AppWrap, {}, children)
+        )
+      )
+    )
+  );
 }
 
 export default function App() {
-  return (
-    <Outlet />
-  );
+  return jsx(Outlet, {});
 }
 
 {% endblock %}

+ 33 - 29
reflex/.templates/jinja/web/pages/utils.js.jinja2

@@ -25,7 +25,11 @@
 {#     component: component dictionary #}
 {% macro render_self_close_tag(component) %}
 {%- if component.name|length %}
-<{{ component.name }} {{- render_props(component.props) }}{% if component.autofocus %} ref={focusRef} {% endif %}/>
+jsx(
+  {{ component.name }},
+  {{- render_props(component.props) }},
+  {{- component.contents }}
+)
 {%- else %}
   {{- component.contents }}
 {%- endif %}
@@ -35,12 +39,16 @@
 {# Args: #}
 {#     component: component dictionary #}
 {% macro render_tag(component) %}
-<{{component.name}} {{- render_props(component.props) }}>
-{{ component.contents }}
-{% for child in component.children %}
-{{ render(child) }}
-{% endfor %}
-</{{component.name}}>
+jsx({{ component.name }}, {{- render_props(component.props) -}},
+  {%- if component.contents|length -%}
+    {{- component.contents -}},
+  {%- endif -%}
+  {%- for child in component.children -%}
+    {%- if child is mapping or child|length %}
+      {{- render(child) -}},
+    {%- endif -%}
+  {%- endfor -%}
+)
 {%- endmacro %}
 
 
@@ -48,11 +56,11 @@
 {# Args: #}
 {#     component: component dictionary #}
 {% macro render_condition_tag(component) %}
-{ {{- component.cond_state }} ? (
+{{- component.cond_state }} ? (
   {{ render(component.true_value) }}
 ) : (
   {{ render(component.false_value) }}
-)}
+)
 {%- endmacro %}
 
 
@@ -60,41 +68,37 @@
 {# Args: #}
 {#     component: component dictionary #}
 {% macro render_iterable_tag(component) %}
-<>{ {{ component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
+{{ component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
   {% for child in component.children %}
   {{ render(child) }}
   {% endfor %}
-))}</>
+))
 {%- endmacro %}
 
 
 {# Rendering props of a component. #}
 {# Args: #}
 {#     component: component dictionary #}
-{% macro render_props(props) %}
-{% if props|length %} {{ props|join(" ") }}{% endif %}
-{% endmacro %}
+{% macro render_props(props) %}{ {%- if props|length -%} {{- props|join(", ") -}}{%- endif -%} }{% endmacro %}
 
 {# Rendering Match component. #}
 {# Args: #}
 {#     component: component dictionary #}
 {% macro render_match_tag(component) %}
-{
-    (() => {
-        switch (JSON.stringify({{ component.cond._js_expr }})) {
-        {% for case in component.match_cases %}
-            {% for condition in case[:-1] %}
-                case JSON.stringify({{ condition._js_expr }}):
-            {% endfor %}
-                return {{ render(case[-1]) }};
-                break;
-        {% endfor %}
-            default:
-                return {{ render(component.default) }};
-                break;
-        }
-    })()
+(() => {
+  switch (JSON.stringify({{ component.cond._js_expr }})) {
+  {% for case in component.match_cases %}
+    {% for condition in case[:-1] %}
+      case JSON.stringify({{ condition._js_expr }}):
+    {% endfor %}
+      return {{ render(case[-1]) }};
+      break;
+  {% endfor %}
+    default:
+      return {{ render(component.default) }};
+      break;
   }
+})()
 {%- endmacro %}
 
 

+ 2 - 2
reflex/.templates/web/app/routes.js

@@ -2,8 +2,8 @@ import { route } from "@react-router/dev/routes";
 import { flatRoutes } from "@react-router/fs-routes";
 
 export default [
-  route("*", "routes/404.jsx"),
+  route("*", "routes/404.js"),
   ...(await flatRoutes({
-    ignoredRouteFiles: ["routes/404.jsx"],
+    ignoredRouteFiles: ["routes/404.js"],
   })),
 ];

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

@@ -229,8 +229,8 @@ export const applyEvent = async (event, socket, navigate) => {
       a.href = eval?.(
         event.payload.url.replace(
           "getBackendURL(env.UPLOAD)",
-          `"${getBackendURL(env.UPLOAD)}"`
-        )
+          `"${getBackendURL(env.UPLOAD)}"`,
+        ),
       );
     }
     a.download = event.payload.filename;
@@ -344,7 +344,7 @@ export const applyRestEvent = async (event, socket) => {
       event.payload.files,
       event.payload.upload_id,
       event.payload.on_upload_progress,
-      socket
+      socket,
     );
     return false;
   }
@@ -363,7 +363,7 @@ export const queueEvents = async (events, socket, prepend) => {
     events = [
       ...events,
       ...Array.from({ length: event_queue.length }).map(() =>
-        event_queue.shift()
+        event_queue.shift(),
       ),
     ];
   }
@@ -422,7 +422,7 @@ export const connect = async (
   dispatch,
   transports,
   setConnectErrors,
-  client_storage = {}
+  client_storage = {},
 ) => {
   // Get backend URL object from the endpoint.
   const endpoint = getBackendURL(EVENTURL);
@@ -524,7 +524,7 @@ export const uploadFiles = async (
   files,
   upload_id,
   on_upload_progress,
-  socket
+  socket,
 ) => {
   // return if there's no file to upload
   if (files === undefined || files.length === 0) {
@@ -629,7 +629,7 @@ export const Event = (
   name,
   payload = {},
   event_actions = {},
-  handler = null
+  handler = null,
 ) => {
   return { name, payload, handler, event_actions };
 };
@@ -656,7 +656,7 @@ export const hydrateClientStorage = (client_storage) => {
     for (const state_key in client_storage.local_storage) {
       const options = client_storage.local_storage[state_key];
       const local_storage_value = localStorage.getItem(
-        options.name || state_key
+        options.name || state_key,
       );
       if (local_storage_value !== null) {
         client_storage_values[state_key] = local_storage_value;
@@ -667,7 +667,7 @@ export const hydrateClientStorage = (client_storage) => {
     for (const state_key in client_storage.session_storage) {
       const session_options = client_storage.session_storage[state_key];
       const session_storage_value = sessionStorage.getItem(
-        session_options.name || state_key
+        session_options.name || state_key,
       );
       if (session_storage_value != null) {
         client_storage_values[state_key] = session_storage_value;
@@ -692,7 +692,7 @@ export const hydrateClientStorage = (client_storage) => {
 const applyClientStorageDelta = (client_storage, delta) => {
   // find the main state and check for is_hydrated
   const unqualified_states = Object.keys(delta).filter(
-    (key) => key.split(".").length === 1
+    (key) => key.split(".").length === 1,
   );
   if (unqualified_states.length === 1) {
     const main_state = delta[unqualified_states[0]];
@@ -726,7 +726,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
         const session_options = client_storage.session_storage[state_key];
         sessionStorage.setItem(
           session_options.name || state_key,
-          delta[substate][key]
+          delta[substate][key],
         );
       }
     }
@@ -746,7 +746,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
 export const useEventLoop = (
   dispatch,
   initial_events = () => [],
-  client_storage = {}
+  client_storage = {},
 ) => {
   const socket = useRef(null);
   const location = useLocation();
@@ -763,7 +763,7 @@ export const useEventLoop = (
 
     event_actions = events.reduce(
       (acc, e) => ({ ...acc, ...e.event_actions }),
-      event_actions ?? {}
+      event_actions ?? {},
     );
 
     const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
@@ -791,7 +791,7 @@ export const useEventLoop = (
       debounce(
         combined_name,
         () => queueEvents(events, socket),
-        event_actions.debounce
+        event_actions.debounce,
       );
     } else {
       queueEvents(events, socket);
@@ -811,7 +811,7 @@ export const useEventLoop = (
           },
         })),
         socket,
-        true
+        true,
       );
       sentHydrate.current = true;
     }
@@ -857,7 +857,7 @@ export const useEventLoop = (
           dispatch,
           ["websocket"],
           setConnectErrors,
-          client_storage
+          client_storage,
         );
       }
     }
@@ -905,7 +905,7 @@ export const useEventLoop = (
         vars[storage_to_state_map[e.key]] = e.newValue;
         const event = Event(
           `${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
-          { vars: vars }
+          { vars: vars },
         );
         addEvents([event], e);
       }
@@ -994,7 +994,7 @@ export const getRefValues = (refs) => {
   return refs.map((ref) =>
     ref.current
       ? ref.current.value || ref.current.getAttribute("aria-valuenow")
-      : null
+      : null,
   );
 };
 

+ 5 - 5
reflex/compiler/utils.py

@@ -13,11 +13,11 @@ from urllib.parse import urlparse
 from pydantic.v1.fields import ModelField
 
 from reflex import constants
-from reflex.components.base import Description, Image, Meta, Scripts, Title
+from reflex.components.base import Description, Image, Scripts
 from reflex.components.base.document import Links, ScrollRestoration
 from reflex.components.base.document import Meta as ReactMeta
 from reflex.components.component import Component, ComponentStyle, CustomComponent
-from reflex.components.el.elements.metadata import Head
+from reflex.components.el.elements.metadata import Head, Meta, Title
 from reflex.components.el.elements.other import Html
 from reflex.components.el.elements.sectioning import Body
 from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
@@ -401,7 +401,7 @@ def get_page_path(path: str) -> str:
         get_web_dir()
         / constants.Dirs.PAGES
         / constants.Dirs.ROUTES
-        / (path + constants.Ext.JSX)
+        / (path + constants.Ext.JS)
     )
 
 
@@ -449,7 +449,7 @@ def get_components_path() -> str:
     return str(
         get_web_dir()
         / constants.Dirs.UTILS
-        / (constants.PageNames.COMPONENTS + constants.Ext.JSX),
+        / (constants.PageNames.COMPONENTS + constants.Ext.JS),
     )
 
 
@@ -462,7 +462,7 @@ def get_stateful_components_path() -> str:
     return str(
         get_web_dir()
         / constants.Dirs.UTILS
-        / (constants.PageNames.STATEFUL_COMPONENTS + constants.Ext.JSX)
+        / (constants.PageNames.STATEFUL_COMPONENTS + constants.Ext.JS)
     )
 
 

+ 4 - 3
reflex/components/base/bare.py

@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import json
 from typing import Any, Iterator, Sequence
 
 from reflex.components.component import BaseComponent, Component, ComponentStyle
@@ -170,9 +171,9 @@ class Bare(Component):
     def _render(self) -> Tag:
         if isinstance(self.contents, Var):
             if isinstance(self.contents, (BooleanVar, ObjectVar)):
-                return Tagless(contents=f"{{{self.contents.to_string()!s}}}")
-            return Tagless(contents=f"{{{self.contents!s}}}")
-        return Tagless(contents=str(self.contents))
+                return Tagless(contents=f"{self.contents.to_string()!s}")
+            return Tagless(contents=f"{self.contents!s}")
+        return Tagless(contents=f'"{json.dumps(self.contents)}"')
 
     def _add_style_recursive(
         self, style: ComponentStyle, theme: Component | None = None

+ 17 - 16
reflex/components/component.py

@@ -276,6 +276,9 @@ class Component(BaseComponent, ABC):
     # The alias for the tag.
     alias: str | None = pydantic.v1.Field(default_factory=lambda: None)
 
+    # Whether the component is a global scope tag. True for tags like `html`, `head`, `body`.
+    _is_tag_in_global_scope: bool = pydantic.PrivateAttr(default_factory=lambda: False)
+
     # Whether the import is default or named.
     is_default: bool | None = pydantic.v1.Field(default_factory=lambda: False)
 
@@ -668,9 +671,13 @@ class Component(BaseComponent, ABC):
         Returns:
             The tag to render.
         """
+        name = (self.tag if not self.alias else self.alias) or ""
+        if self._is_tag_in_global_scope and self.library is None:
+            name = '"' + name + '"'
+
         # Create the base tag.
         tag = Tag(
-            name=(self.tag if not self.alias else self.alias) or "",
+            name=name,
             special_props=self.special_props,
         )
 
@@ -1390,11 +1397,13 @@ class Component(BaseComponent, ABC):
         Returns:
             The imports needed by the component.
         """
-        _imports = {}
+        _imports = {
+            "@emotion/react": ImportVar(tag="jsx"),
+        }
 
         # Import this component's tag from the main library.
         if self.library is not None and self.tag is not None:
-            _imports[self.library] = {self.import_var}
+            _imports[self.library] = self.import_var
 
         # Get static imports required for event processing.
         event_imports = Imports.EVENTS if self.event_triggers else {}
@@ -1420,7 +1429,7 @@ class Component(BaseComponent, ABC):
         return imports.merge_imports(
             self._get_dependencies_imports(),
             self._get_hooks_imports(),
-            _imports,
+            {**_imports},
             event_imports,
             *var_imports,
             *added_import_dicts,
@@ -2534,12 +2543,12 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
     special_props = []
 
     for prop_str in tag["props"]:
-        if "=" not in prop_str:
+        if ":" not in prop_str:
             special_props.append(Var(prop_str).to(ObjectVar))
             continue
-        prop = prop_str.index("=")
+        prop = prop_str.index(":")
         key = prop_str[:prop]
-        value = prop_str[prop + 2 : -1]
+        value = prop_str[prop + 1 :]
         props[key] = value
 
     props = Var.create({Var.create(k): Var(v) for k, v in props.items()})
@@ -2547,19 +2556,11 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
     for prop in special_props:
         props = props.merge(prop)
 
-    contents = tag["contents"][1:-1] if tag["contents"] else None
+    contents = tag["contents"] if tag["contents"] else None
 
     raw_tag_name = tag.get("name")
     tag_name = Var(raw_tag_name or "Fragment")
 
-    tag_name = (
-        Var.create(raw_tag_name)
-        if raw_tag_name
-        and raw_tag_name.split(".")[0] not in imported_names
-        and raw_tag_name.lower() == raw_tag_name
-        else tag_name
-    )
-
     return FunctionStringVar.create(
         "jsx",
     ).call(

+ 4 - 0
reflex/components/el/element.py

@@ -1,11 +1,15 @@
 """Base class definition for raw HTML elements."""
 
+import pydantic
+
 from reflex.components.component import Component
 
 
 class Element(Component):
     """The base class for all raw HTML elements."""
 
+    _is_tag_in_global_scope = pydantic.PrivateAttr(default_factory=lambda: True)
+
     def __eq__(self, other: object):
         """Two elements are equal if they have the same tag.
 

+ 1 - 1
reflex/utils/format.py

@@ -437,7 +437,7 @@ def format_props(*single_props, **key_value_props) -> list[str]:
 
     return [
         (
-            f"{name}={{{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}}}"
+            f"{name}:{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}"
         )
         for name, prop in sorted(key_value_props.items())
         if prop is not None

+ 1 - 0
reflex/utils/pyi_generator.py

@@ -47,6 +47,7 @@ EXCLUDED_PROPS = [
     "tag",
     "is_default",
     "special_props",
+    "_is_tag_in_global_scope",
     "_invalid_children",
     "_memoization_mode",
     "_rename_props",