diff --git a/dev/workshop/core.cljs b/dev/workshop/core.cljs index a873618..0708e2d 100644 --- a/dev/workshop/core.cljs +++ b/dev/workshop/core.cljs @@ -358,11 +358,11 @@ (simple-benchmark [] (rds/renderToString - (r/createElement - react-children-benchmark - #js {:foo "bar"} - (r/createElement "div" #js {:style #js {:backgroundColor "green"}} "foo") - (r/createElement "div" nil "bar"))) + (helix/jsxs react-children-benchmark + #js {:foo "bar" + :children #js [(helix/jsx "div" #js {:style #js {:backgroundColor "green"} + :children "foo"}) + (helix/jsx "div" #js {:children "bar"})]})) iterations))) helix-time (hooks/use-memo diff --git a/src/helix/core.clj b/src/helix/core.clj index dee6a00..e91729e 100644 --- a/src/helix/core.clj +++ b/src/helix/core.clj @@ -2,8 +2,14 @@ (:require [helix.impl.analyzer :as hana] [helix.impl.props :as impl.props] + [cljs.tagged-literals :as tl] [clojure.string :as string])) +(defn- jsx-children [coll] + (let [s (seq coll)] + (if (and s (next s)) + (tl/->JSValue coll) + (first coll)))) (defmacro $ "Create a new React element from a valid React type. @@ -44,19 +50,29 @@ (:native (meta type))) type (if (keyword? type) (name type) - type)] - (cond - (map? (first args)) - `^js/React.Element (.createElement - (get-react) - ~type - ~(if native? - `(impl.props/dom-props ~(first args)) - `(impl.props/props ~(first args))) - ~@(rest args)) - - :else `^js/React.Element (.createElement (get-react) ~type nil ~@args)))) - + type) + has-props? (or (map? (first args)) + (nil? (first args))) + children (if has-props? + (rest args) + args) + props (if (map? (first args)) + (if native? + `(impl.props/dom-props ~(first args) ~(jsx-children children)) + `(impl.props/props ~(first args) ~(jsx-children children))) + (tl/->JSValue (cond-> {} + (not-empty children) + (assoc :children (jsx-children children))))) + has-key? (when has-props? + (contains? (first args) :key)) + the-key (when has-key? + (:key (first args))) + emit-fn (if (next children) + `jsxs + `jsx)] + (if has-key? + `^js/React.Element (~emit-fn ~type ~props ~the-key) + `^js/React.Element (~emit-fn ~type ~props)))) (defmacro <> "Creates a new React Fragment Element" diff --git a/src/helix/core.cljs b/src/helix/core.cljs index 7fd23c0..07b7ade 100644 --- a/src/helix/core.cljs +++ b/src/helix/core.cljs @@ -4,7 +4,8 @@ [helix.impl.props :as impl.props] [helix.impl.classes :as helix.class] [cljs-bean.core :as bean] - ["react" :as react]) + ["react" :as react] + ["react/jsx-runtime" :as jsx-runtime]) (:require-macros [helix.core])) @@ -37,6 +38,8 @@ ;; a dynamic arity dispatch. See https://github.com/Lokeh/helix/issues/20 (defn ^js/React get-react [] react) +(def jsx jsx-runtime/jsx) +(def jsxs jsx-runtime/jsxs) (defn $ "Create a new React element from a valid React type. @@ -55,20 +58,30 @@ native? (or (keyword? type) (string? type) (:native (meta type))) + has-props? ^boolean (or (map? ?p) + (nil? ?p)) + children* ^seq (if has-props? + ?c + args) + children (if (next children*) + (into-array children*) + (first children*)) + props* (cond-> {} + has-props? (conj ?p) + (some? children) (assoc :children children)) + props (if native? + (impl.props/-dom-props props*) + (impl.props/-props props*)) + key (:key props*) + emit-fn (if (next children*) + jsxs + jsx) type' (if (keyword? type) (name type) type)] - (if (map? ?p) - (apply create-element - type' - (if native? - (impl.props/-dom-props ?p) - (impl.props/-props ?p)) - ?c) - (apply create-element - type' - nil - args)))) + (if (some? key) + (emit-fn type' props key) + (emit-fn type' props)))) (def ^:deprecated $$ diff --git a/src/helix/dom.cljc b/src/helix/dom.cljc index ff564af..0ace6b4 100644 --- a/src/helix/dom.cljc +++ b/src/helix/dom.cljc @@ -1,6 +1,7 @@ (ns helix.dom (:refer-clojure :exclude [map meta time]) (:require + [cljs.tagged-literals :as tl] [helix.core :as hx] [helix.impl.props :as impl.props]) #?(:cljs (:require-macros [helix.dom]))) @@ -161,18 +162,23 @@ Use the special & or :& prop to merge dynamic props in." [type & args] - (if (map? (first args)) - `^js/React.Element (.createElement - (hx/get-react) - ~type - (impl.props/dom-props ~(first args)) - ~@(rest args)) - `^js/React.Element (.createElement - (hx/get-react) - ~type - nil - ~@args))) - + (let [?p (first args) + has-props? (map? ?p) + children* (if has-props? + (rest args) + args) + multiple-children (next children*) + children (if multiple-children + (tl/->JSValue children*) + (first children*)) + props* (when has-props? ?p) + key (:key props*) + emit-fn (if multiple-children + `hx/jsxs + `hx/jsx)] + (if (some? key) + `^js/React.Element (~emit-fn ~type (impl.props/dom-props ~props* ~children) ~key) + `^js/React.Element (~emit-fn ~type (impl.props/dom-props ~props* ~children))))) #?(:clj (defn gen-tag [tag] diff --git a/src/helix/impl/props.cljc b/src/helix/impl/props.cljc index 4366bf0..7666680 100644 --- a/src/helix/impl/props.cljc +++ b/src/helix/impl/props.cljc @@ -172,8 +172,11 @@ (-dom-props {:style ["fs1"]}) ) -(defmacro dom-props [m] - (-dom-props m)) +(defmacro dom-props + ([m] `(dom-props ~m nil)) + ([m c] (-dom-props (cond-> m + c (assoc :children c) + true (dissoc :key))))) (defn -props @@ -205,5 +208,8 @@ (-props {:foo-bar "baz"}) ) -(defmacro props [m] - (-props m)) +(defmacro props + ([m] `(props ~m nil)) + ([m c] (-props (cond-> m + (some? c) (assoc :children c) + true (dissoc :key))))) diff --git a/test/helix/core_test.cljs b/test/helix/core_test.cljs index e61fa6e..8626fc6 100644 --- a/test/helix/core_test.cljs +++ b/test/helix/core_test.cljs @@ -90,3 +90,60 @@ (t/testing "can be used with IReset" (reset! ref "well done") (t/is (= "well done" @ref)))))) + +(t/deftest jsx-test + (t/testing "jsx transform" + ;; In this test, you will see comments of the equivalent JSX. You can + ;; compare the output of the JSX transform by copy-pasting the provided JSX + ;; and running it through @babel/plugin-transform-react-jsx + (t/testing "with no props or children" + (let [component-1 (macroexpand '($ :div)) ;
+ component-2 (macroexpand '($ "div")) + component-3 (macroexpand '($ "div" nil)) + component-4 (macroexpand '($ "div" nil nil)) ;