Skip to content
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
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
cheshire/cheshire {:mvn/version "6.1.0"}

;; Client-side component compilation (CLJS-dialect -> JS)
io.github.squint-cljs/squint {:mvn/version "0.14.209"}
io.github.squint-cljs/squint {:mvn/version "0.14.210"}

;; Logging
com.taoensso/telemere {:mvn/version "1.2.1"}}
Expand Down
86 changes: 46 additions & 40 deletions src/hyper/component.clj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
:core-alias "$sc"
:context :statement})

(defn- compile-squint*
[source]
(try
(squint/compile* source squint-opts)
(catch Exception e
(throw (ex-info (str "Squint compilation failed: " (ex-message e))
{:source source}
e)))))

(defn compile-squint
"Compile a string of Squint (CLJS-dialect) source to JavaScript.

Expand All @@ -63,12 +72,7 @@

Throws ex-info with the source attached when compilation fails."
[source]
(try
(:body (squint/compile-string* source squint-opts))
(catch Exception e
(throw (ex-info (str "Squint compilation failed: " (ex-message e))
{:source source}
e)))))
(:body (compile-squint* source)))

(defn- ns-form-source
"Build a Squint ns form declaring ES module requires, so alias-qualified
Expand Down Expand Up @@ -170,11 +174,11 @@
Returns the component name."
[name spec]
{:pre [(string? name) (str/includes? name "-") (:render spec)]}
(let [requires (parse-requires (:require spec))
_ (check-alias-conflicts! name requires)
spec (-> spec (dissoc :require) (assoc :requires requires))
js (compile-squint (registration-source name spec))]
(swap! registry* assoc name (assoc spec :js js))
(let [requires (parse-requires (:require spec))
_ (check-alias-conflicts! name requires)
spec (-> spec (dissoc :require) (assoc :requires requires))
{:keys [body used-core-vars]} (compile-squint* (registration-source name spec))]
(swap! registry* assoc name (assoc spec :js body :core-vars used-core-vars))
name))

;; ---------------------------------------------------------------------------
Expand Down Expand Up @@ -450,42 +454,44 @@
{:style/indent 1
:style.cljfmt/indent [[:block 1] [:inner 1]]}
[cname & body]
(let [[docstring body] (if (string? (first body))
[(first body) (rest body)]
[nil body])
[opts body] (if (map? (first body))
[(first body) (rest body)]
[{} body])
requires (parse-requires (:require opts))
[binding-vec & segments] body
_ (assert (vector? binding-vec)
(str "defc " cname ": expected attribute binding vector, got "
(pr-str binding-vec)))
attr-keys (extract-attrs binding-vec)
_ (assert (seq attr-keys)
(str "defc " cname ": binding vector must use {:keys [...]} form "
"so attribute names are statically visible"))
tag-name (sym->tag cname)
_ (assert (str/includes? tag-name "-")
(str "defc " cname ": tag name \"" tag-name
"\" must contain a hyphen (Custom Elements spec)"))
parsed (parse-segments cname segments)
_ (assert (or (:render parsed) (:mount parsed))
(str "defc " cname ": a (render ...) or (mount ...) segment is required"))
ns-str (str *ns*)
src (squint-source tag-name attr-keys parsed ns-str requires)
tag-kw (keyword tag-name)
(let [[docstring body] (if (string? (first body))
[(first body) (rest body)]
[nil body])
[opts body] (if (map? (first body))
[(first body) (rest body)]
[{} body])
requires (parse-requires (:require opts))
[binding-vec & segments] body
_ (assert (vector? binding-vec)
(str "defc " cname ": expected attribute binding vector, got "
(pr-str binding-vec)))
attr-keys (extract-attrs binding-vec)
_ (assert (seq attr-keys)
(str "defc " cname ": binding vector must use {:keys [...]} form "
"so attribute names are statically visible"))
tag-name (sym->tag cname)
_ (assert (str/includes? tag-name "-")
(str "defc " cname ": tag name \"" tag-name
"\" must contain a hyphen (Custom Elements spec)"))
parsed (parse-segments cname segments)
_ (assert (or (:render parsed) (:mount parsed))
(str "defc " cname ": a (render ...) or (mount ...) segment is required"))
ns-str (str *ns*)
src (squint-source tag-name attr-keys parsed ns-str requires)
tag-kw (keyword tag-name)
;; Compile at macro-expansion time so Squint errors surface at compile
;; time (with the source attached), but emit the registration into the
;; expansion so it executes at runtime. This keeps defc AOT-safe
;; (expansion-time side effects don't survive AOT compilation) and
;; testable (with-redefs of registry* works).
compiled-js (compile-squint src)]
{compiled-js :body
core-vars :used-core-vars} (compile-squint* src)]
`(do
(check-alias-conflicts! ~tag-name '~requires)
(swap! registry* assoc ~tag-name {:attrs ~attr-keys
:requires '~requires
:js ~compiled-js})
(swap! registry* assoc ~tag-name {:attrs ~attr-keys
:requires '~requires
:js ~compiled-js
:core-vars ~core-vars})
(defn ~cname
~@(when docstring [docstring])
[attr-map# & children#]
Expand Down
13 changes: 2 additions & 11 deletions src/hyper/component/bundle.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
REPL redefinition hot-swap the bundle over SSE."
(:require [clojure.java.io :as io]
[clojure.string :as str]
[hyper.component :as component]
[hyper.expr :as expr]))
[hyper.component :as component]))

(defn- runtime-js []
(slurp (io/resource "hyper/component-runtime.js")))
Expand Down Expand Up @@ -77,18 +76,10 @@
(reset! bundle-cache* {:key registry :bundle b})
b))))))

(defonce ^:private core-vars-cache* (atom nil))

(defn core-vars
"Returns the munged squint core names that the registered components use."
[]
(let [registry @component/registry*
[cached-registry names] @core-vars-cache*]
(if (identical? registry cached-registry)
names
(let [names (into #{} (mapcat #(expr/core-vars-of "$sc" (:js %))) (vals registry))]
(reset! core-vars-cache* [registry names])
names))))
(into #{} (mapcat :core-vars) (vals @component/registry*)))

(defn head-script-tag
"Hiccup script tag for the components bundle, or nil when no components
Expand Down
50 changes: 26 additions & 24 deletions src/hyper/expr.clj
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,6 @@
core-vars*
(atom (sorted-set)))

;; A future squint release adds the used core names to the compiler output, which replaces this regex.
(defn core-vars-of
"Returns the squint core names that js references through alias."
[alias js]
(into (sorted-set) (map second)
(re-seq (re-pattern (str (java.util.regex.Pattern/quote alias) "\\.([A-Za-z_$][\\w$]*)")) js)))

(defn use-core-vars!
"Adds names to core-vars*.
names is a set of munged squint core names.
Expand All @@ -253,25 +246,33 @@
(swap! core-vars* into names))
js)

(defn- compile-form*
"Returns {:js ... :used-core-vars ...} for a single (boundary-inferred) form."
[form]
(let [processed (pre-process form)
;; pr-str, not str, because embedded lazy seqs from the boundary walk would
;; stringify as "clojure.lang.LazySeq@hash" under str.
{:keys [body used-core-vars]}
(squint/compile* (pr-str processed)
{:elide-imports true
:elide-exports true
:top-level false
:context :expr
:core-alias "hyper_sc"
:macros compiler-macros})]
{:js (-> body
replace-deref
(restore-signal-casing processed)
(str/replace #"\n" " ")
str/trim
(str/replace #";$" ""))
:used-core-vars used-core-vars}))

(defn compile-form
"Compile a single (boundary-inferred) form to a Datastar expression
string. Public for testing."
[form]
(let [processed (pre-process form)]
;; pr-str, not str — embedded lazy seqs from the boundary walk would
;; stringify as "clojure.lang.LazySeq@hash" under str.
(-> (squint/compile-string (pr-str processed)
{:elide-imports true
:elide-exports true
:top-level false
:context :expr
:core-alias "hyper_sc"
:macros compiler-macros})
replace-deref
(restore-signal-casing processed)
(str/replace #"\n" " ")
str/trim
(str/replace #";$" ""))))
(:js (compile-form* form)))

;; ---------------------------------------------------------------------------
;; Runtime splicing
Expand Down Expand Up @@ -335,8 +336,9 @@
(let [env-locals (set (keys &env))
pairs* (atom [])
forms* (mapv #(infer-boundary % env-locals pairs*) forms)
template (join-statements (map compile-form forms*))
names (core-vars-of "hyper_sc" template)
compiled (map compile-form* forms*)
template (join-statements (map :js compiled))
names (into #{} (mapcat :used-core-vars) compiled)
pairs @pairs*
js (if (empty? pairs)
template
Expand Down
Loading