squint 2024-09-02

I'm trying to learn squint by porting a ClojureScript library. I have a question about rendering HTML. I have a macro helpers in a cljc that look like this:

(ns fizz.impl
  (:require [borkdude.html :refer [html]])

(defn make-component*
  ([args body]
   (make-component* {} args body))
  ([{:keys [observed-attributes]}
    args
    body]
   `{:fizz.element/body-html (fn ~args (html ~body))
     :fizz.element/observed-attributes ~observed-attributes}))
This works in Clojure and the normal clojurescript compiler. It's part of a library that can SSR web components. I'd prefer not to use the #html reader tag on the backend. However, when trying to make this work in squint, I get the error:
the "path" argument must be of type string or an instance of Buffer or URL
My first thought was that I need to use require-macros :
(ns fizz.impl
  #?(:squint (:require-macros [borkdude.html :refer [html]])) ;; also tried with :cljs
  #?(:clj (:require [borkdude.html :refer [html]])))
However, this results in the same error. What's the idiomatic squint way of solving this problem?

I went in a different direction, but I'd circle back around to this if it were available

Where did you put borkdude/html?

squint doesn't support CLJ(S) libraries from deps.edn etc yet, unfortunately, but if you (for now) copy borkdude.html into src/borkdude/html.cljc then it might work, I haven't tested this

I'll give that a try. I'm using deps.edn, and html is added there as a dependency

yes, this won't do anything at the moment

you still will need :require-macros

Got it. I can compile now, but this test:

(ns fizz.element-test
  (:require-macros [borkdude.html :refer [html]])
  (:require ["vitest" :refer [test expect]]))

(test "vendor borkdude html"
      (.toEqual (expect (html [:p]))
                "<p></p>"))
Produces the screenshotted error. I'm trying to rule out an interaction with vite

not sure what this is, perhaps you can show the compiled JS ?

Compiled cljs:

import * as squint_core from 'squint-cljs/core.js';
import { test, expect } from 'vitest';
test("vendor borkdude html", expect(squint_core.str(borkdude.html.Html@1a)).toEqual("<p></p>"));

It seems isn't calling the macro

oh yeah I see

it is calling the macro, but the macro is returning a deftype instance that is serialized the wrong way

This wasn't really written with squint in mind, in squint you're expected to use the #html reader but ... perhaps I do have a workaround..

In squint there is a special form called squint-compiler-html which you can use for this. Note that this is currently only an implementation detail which might change, but this should work:

(#?(:squint squint-compiler-html :default borkdude.html/html) [:a ...])
or so

I could probably make this a little programmer-friendly

by exposing this in a stable manner

but you could try to test it right now

I'll give it a shot. What I'm after isn't so much using the html library, but being able to render a web component on the server and on the client (using squint) in the same way. I may need to back up and re-think that a bit. I'll try the special form and see if that does the trick.

why wouldn't #html work both on the server and client?

or is the issue that your library should be usable from regular CLJS?

Regular cljs isn't really a primary objective

so both server and client are running squint?

Server is running regular jvm clojure; client is using squint. Previously, I was calling html inside of a macro. I think I can lift it. That changes my main API function from:

(define test-element-1
   {:observed-attributes #{:a :b}}
   [{:keys [a b]}]
   [:div
     [:p a]
     [:p b]]))
to
(define test-element-1
   {:observed-attributes #{:a :b}}
   [{:keys [a b]}]
   #html [:div
           [:p a]
           [:p b]]))
Trying that right now.

ah JVM Clojure, that's useful information

#?(:clj (:require [borkdude.html :refer [html] :rename {html squint-compiler-html}]))
This hack may work to use just (squint-compiler-html [:div [:p a] ....) in both places If this works, I can probably rename this to be more API friendly or so

Ah, nice, I'll try that.

I have it working on the Clojure side, but not the squint side yet. Here's the macro helper:

(ns fizz.impl
  #?(:clj (:require [borkdude.html :refer [html]
                     :rename {html squint-compiler-html}])))

(defn make-component*
  ([args body]
   (make-component* {} args body))
  ([{:keys [observed-attributes]}
    args
    body]
   `{:fizz.element/body-html (fn ~args (squint-compiler-html  ~body))
     :fizz.element/observed-attributes ~observed-attributes}))
This macro-expands as:
(macroexpand-1
   '(define test-element-1
      {:observed-attributes #{:a :b}}
      [{:keys [a b]}]
      [:div
       [:p a]
       [:p b]])))

==> (def test-element-1
        #:fizz.element{:observed-attributes #{:b :a},
                       :body-html
                       (clojure.core/fn
                         [{:keys [a b]}]
                         (borkdude.html/html [:div [:p a] [:p b]]))})
However, it compiles as:
return fizz.impl.squint_compiler_html(["p", ["span", squint_core.str("a is ", a3)]]);
}), "fizz.element/observed-attributes": ["a"] });
This gives an undefined error. I'm assuming this is because the compiler is https://github.com/squint-cljs/squint/blob/1c3e1b034c4d055a1be85b78499f698daca4f9f9/src/squint/compiler.cljc#L45 squint-compiler-html to be a symbol

you need to unqualify it like this:

~'squint-compiler-html

else it gets qualified as fizz.impl/squint-compiler-html as you can see

but this might break the JVM Clojure behavior. I'll check back here tomorrow

No problem, I'm much further than I was before. Thanks! I did try that, but it breaks on the JVM side. I'll keep brainstorming.

it's probably worth logging an issue about this

👍 1

ok, let me now take a look at this...

if this is still useful to you