This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-07-26
Channels
- # babashka (16)
- # beginners (38)
- # calva (11)
- # cider (35)
- # clj-kondo (3)
- # clj-otel (3)
- # clojure (28)
- # clojure-europe (11)
- # clojure-gamedev (14)
- # clojure-norway (42)
- # clojure-spec (4)
- # datalevin (10)
- # datomic (2)
- # emacs (8)
- # events (2)
- # fulcro (3)
- # gratitude (5)
- # hyperfiddle (3)
- # kaocha (1)
- # nbb (14)
- # nrepl (12)
- # portal (1)
- # re-frame (5)
- # releases (1)
- # shadow-cljs (36)
- # squint (167)
This works:
(defprotocol IFoo (foo [_]) (bar [_] [_ a b]))
(defclass Foo
(field x)
(constructor [this]
)
IFoo
(foo [_] :foo)
(bar [_] [:bar])
(bar [_ a b] [:bar a b]))
(prn (bar (new Foo) 1 2))
but there are some edge cases I'm trying to fix and those are the hard parts, e.g. extending methods on Object is what doesn't work right now
If someone would help me test drive some examples, it would help make progress I think
Do you have specific use cases you want tested, or should I just go ahead and use it for the one I had in mind (making a simple web component)?
I wonder if I can use (p)npm to install your branch, or if I have to clone it manually. Hm.
Here is a simple lit hello world component if this helps. I got it to work with shadow.
(ns demo.index
(:require [shadow.cljs.modern :refer (defclass js-template)]
[goog.dom :as gdom]
["lit" :as lit]))
(defclass HelloWorld
(extends lit/LitElement)
(field name "Username")
(constructor
[this]
(super))
Object
(render
[this]
(js/console.log "render is called" this)
(js-template lit/html "<h1> Hello " name "!<h1>")))
(defn -main
[]
(js/console.log "Hello, world!")
(.define js/customElements "hello-world" HelloWorld)
(let [app (.getElementById js/document "app")
custom-el (gdom/createElement "hello-world")]
(gdom/appendChild app custom-el))
)
I think you can (for now) just call lit/html as:
(lit/html ["<h1> Hello", "!</h1>"], name)
That line doesn't work in shadow, let me figure out a proper way to call the template function from shadow and then I'll try to take squint to run that same code.
Yes, I tried it with JS, didn't do the trick. Even without variables. I'm googling it now: Error: invalid template strings array
> function foo(array, ...args) { return [ array, ...args]; }
undefined
> foo`dude`
[ [ 'dude' ] ]
> x = 1
1
> foo`dude ${x}`
[ [ 'dude ', '' ], 1 ]
Yes, it seems that lit/html doesn't like what I give to it.
it would be useful if someone could make a sample project with squint + lit, I have no lit experience but even a getting started project looks complicated
I took on lit this morning, so that's why I'm struggling to figure out what am I doing wrong. Thanks for the tips. I have this in code
(.html lit #js [ "<h1> Hello" "" "!</h1>"] name)
And that's what shadow generates.
return module$node_modules$lit$index.html(["<h1> Hello","","!</h1>"],self__.name);
Error is the following:
lit-html.js:56 Uncaught (in promise) Error: invalid template strings array
at P (lit-html.js:56:60)
at V (lit-html.js:80:11)
at new N (lit-html.js:94:20)
at R._$AC (lit-html.js:280:49)
at R.g (lit-html.js:270:43)
at R._$AI (lit-html.js:249:189)
at exports.render (lit-html.js:431:12)
at appspark$index$HelloWorld.update (lit-element.js:66:106)
at appspark$index$HelloWorld.performUpdate (reactive-element.js:300:16)
at appspark$index$HelloWorld.scheduleUpdate (reactive-element.js:286:17)
So I guess the problem isn't with how I call it, but the lit itself.Same thing.
if I'm not mistaking, the template strings array should always have one more element than the amount of variable argument
Didn't help. I think the problem is it expects https://lit.dev/docs/v1/api/lit-html/templates/ TemplateStringsArray and I give it just a js array which is missing raw property.
This function did the thing, but that's a big hack 😄
(defn template-array
[string-seq]
(let [arr (js/Object.assign #js [] (clj->js string-seq))]
(set! (.-raw arr) (clj->js string-seq))
arr))
ok, ready to test squint with the same component. What's the easiest way to set it up?
but like I explained to @zane in the #cherry channel, the experimental branch is defclass-2
and you need to compile it locally
Sorry for stealing it 🙂 do I just run bb build
to compile locally on a defclass-2 branch?
ok, thanks for the help!
> I think that “TemplateStringsArray” is just a typescript thing @U4EFBUCUE This seems right to me. From what I can tell the actual runtime value is just a JavaScript array. What error are you getting exactly?
I "emulated" the TemplateStringsArray and lit ate it. But I don't know a proper workaround )
(defn template-array
[string-seq]
(let [arr (js/Object.assign #js [] (clj->js string-seq))]
(set! (.-raw arr) (clj->js string-seq))
arr))
added a dumb raw prop
Did that work? You may want the array value to be the same as the value of the raw property.
Yes, it worked within shadow and I'm still setting up squint to test it there
So, I tried the code and it crashed because super() isn't the first call in constructor. Web Components enforce it. The code and simple dev setup is here: https://github.com/shvets-sergey/web-components-squint super() must be called before const self__ in line 12 on screenshot: Browser error for reference:
Also static property (field) in shadow isn't in compiled file (line 18 on screenshot above). Not sure if that is suppose to work yet.
you mean the default value: no I haven't made that to work yet, but I think you can assign it in the constructor, I'll fix it tomorrow
I didn't get to work on it a whole lot today, but only for a few hours. Still wrestling with passing "this" arguments etc
This example now works:
(defprotocol IFoo (foo [_]) (bar [_] [_ a b]))
(defclass Foo
(field x) ;; TODO: support defaults;
(constructor [this]
(set! x "this-is-x"))
Object
(toString [this] (str x :dude))
IFoo
(foo [_] :foo)
(bar [_] [:bar])
(bar [this a b] [(str this) :bar a b]))
(prn (bar (new Foo) 1 2))
(prn (str (new Foo)))
so I may have solved the this trouble. the only thing left is maybe the field default and perhaps some other stuff
I found an extra problem with super yesterday as I was diving more into lit. Shadow doesn't have a syntax support for it and there is a pretty ugly workaround. More details are here: https://github.com/thheller/shadow-cljs/issues/1137
TLDR: sometimes you need to call super.parentMethod()
. For example, lit uses it to extend standard lifecycle methods of web components and if you want to extend them in your component you have to call the lit's method or otherwise your render will never be called. CLJS compiler doesn't have a way to do it and even spitting it with (js* "super.parentCallback()")
doesn't work because JavaScript requires super to be the first call in a class method and cljs compiler ads that this.self assignment as on the super problem above.
But the good side that I made all the lit tutorial stuff to work on shadow and even use normal cljs structures, so I can test this part on squint as soon as super problem is solved 🙂
The super call doesn't support any other args, it's just super()
right now. now afk, to be continued tomorrow.
Yeap, that works.
Btw, another thing to consider that isn't supported by shadow syntax and has to be work around with (set! ClassName ...)
is static arguments for class. It seems like some libraries rely on them to deliver some functionality (at least lit does). Screenshot for explanation (see static styles and static properties).
Cool, could you please post this stuff in a Github issue in squint (I think there's one about defclass)
I released a new version of squint, but note that defclass isn't in there yet. I'm getting back to defclass now for the coming few hours
This example works now in the latest commit:
$ ./node_cli.js -e '(defclass Class1 (field _x) (constructor [this x] (set! _x x))) (defclass Class2 (extends Class1) (constructor [this x y] (super (+ x y)))) (prn (new Class2 1 2))'
{"_x":3}
pushed field defaults, please test. I'll add a unit test for this all soon based on what I tested here:
$ ./node_cli.js --show -e '(defclass Class1 (field _x) (field __secret :dude) (constructor [this x] (set! _x x))) (defclass Class2 (extends Class1) (field _y 1) (constructor [this x y] (super (+ x y))) Object (dude [this] _y)) (def c (new Class2 1 2)) (prn [c (.dude c)])'
Tested and default fields now working. Is super in Object's override method suppose to work?
(connectedCallback
[this]
(super (connectedCallback))
(js/console.log "Hello from Dom" this))
Right now it generates this and breaks:
(HelloWorld2["prototype"]["set-checked"] = f__26357__auto__5);
let f__26357__auto__6 = (function (this$) {
this$ = this; const self__ = this;
super$(connectedCallback());
return console.log("Hello from Dom", this$);
})
Uncaught ReferenceError: super$ is not defined
at HelloWorld2.f__26357__auto__6 [as connectedCallback] (index.mjs:52:1)
at add_component_to_app (index.mjs:61:13)
at start (index.mjs:67:8)
at index.mjs:70:1
Also, it seems like my hack with template string doesn't work when template-array contains code variables.
I pushed updated code here: https://github.com/shvets-sergey/web-components-squint
No, it doesn't. I talked with Thomas about it and he created an issue: https://github.com/thheller/shadow-cljs/issues/1137. There is a workaround I described in the github issue, but it's very hacky.
is there anything else not working that does work with shadow? my initial goal is to support whatever shadow supports in squint
js-template literal
The rest seem to work.
Right:
> Should most likely lift Object
method declarations out of the extend-type
and directly into the class
body. Will think about it for a bit.
This is what I would do too
@U4EFBUCUE This example now works in the branch:
(ns scratch
(:require [squint.core :refer [defclass]]))
(defclass Class1
(field _x)
(field __secret :dude)
(constructor [this x] (set! _x x))
Object
(getNameSeparator [_] "-"))
(defclass Class2
(extends Class1)
(field _y 1)
(constructor [this x y] (super (+ x y)))
Object
(dude [this] (str _y (.getNameSeparator (js* "super")))))
(def c (new Class2 1 2)) (prn [c (.dude c)])
Thanks, I'll test more later today.
pushed some minor fixes. I've got this example working: https://github.com/squint-cljs/squint-lit-example/blob/main/my_element.cljs almost... When I click the button, the counter increases, but a re-render isn't triggered
you need to set static properties to the class. add this (probably without #js in squint case) after class definition before registering component.
(set! (.-properties MyElement) #js {"count" #js {}})
And it should do the trick.
I'm going to test lit-tutorial components on your branch now.Seems like methods with dash in name don't work.
(handle-click
[this event]
(js/console.log "click" this)
(js/console.log "event" event)
(set! fname (.. event -target -value))
nil)
This one throws error.(.connectedCallback (js* "super")) worked and js template too!
ah yes, I'll add handle-click
to my list, I also bumped into it. try naming it handleClick
Yeah, I renamed and it worked. Testing further. Am I supposed to get error for doall? Here is what I'm trying to do:
(defn js-map
[f js-seq]
(let [jsArr #js []]
(doall
(for [item js-seq]
(let [processed (f item)]
(.push jsArr processed))))
jsArr))
And getting the error:That worked. aclone
also not supported, right? I'll rewrite it with js clone function.
It's a different case where I need to add an item to js array and "replace" it, so the lit reactivity kicks in.
ok, everything seems to work. Atom's add-watch aren't supported by squint, right?
I pushed components I tested here: https://github.com/shvets-sergey/web-components-squint These are all from lit basic tutorial.
https://github.com/shvets-sergey/web-components-squint/blob/main/src/index.cljs
I had exactly the same thought in mind. I'm thinking about it, but haven't tried to do anything yet. Probably in a few days. I think hiccup will struggle with non-standard html tags that are component names? So, somehow needs to support something like ["hello-word" {:prop "name} & children]
Oh ok, then it's not an issue at all.
@U4EFBUCUE can you remind me of the remaining things in this thread that weren't done yet? one being atoms + add-watch
I only faced doall and add-watch, but they're kind of unrelated to the defclass/web-components. I've seen the issue on doall somewhere already, not sure about add-watch. There is nothing related to web-components that is left out.
I made a proof of concept working for this: https://twitter.com/MikeMargerum/status/1686122808064032769 but it uses cljs.analyzer. Can I use cljs.analyzer within squint/cherry macros? I think this can be done without cljs-analyzer but cljs analyzer does tons of heavy-lifting and I'd like to keep it if possible.
Simply copying hiccup's implementation doesn't work, as we need to mix-and-match string templates with clojure code and then merge consecutive strings, so we get the max performance out of browser's template implementation. It seems that browsers have some optimizations for diffing template literals that allowing lit to have almost 50% faster performance then React's VDOM for common use cases.
Well cljs analyzer probably isn’t going to be in squint because of size but where’s the code? I also did an attempt in a branch but ran into some edge cases
yeah, let me work a bit more on this and I'll start a new thread when I have a code to present. It's now a long spaghetti one exploring what's possible. It also has a bunch of other stuff to generate web components, beyond a js template literal, which for sure should be separate.
I only use cljs analyzer during macroexpansion to write the cljs-code. Easier to walk forms and handle some edge cases. I don't need that as runtime dependency. But I'm not sure how squint/cherry macros is different from regular cljs one and if I can do the same there. It works on shadow right now.
Regular vectors is relatively easy to do without analyzer, but if/for is more challenging.
Do you think adding some template literal like #jst template-fn? [:tag props? & children]
Or reader tags can only affect the next argument? I don't know anything about their internals.
I have played around with this, with a generic #html [:a ...]
reader conditional which returns a vector of strings and expressions, much like a template function would receive, such that you can do:
(js-template lit/html #html [:a ...])
I like your approach, but in lit's case you need to have some conditionals for properties. E.g. if you want to pass javascript argument you need to generate a property name with some chars like this <div .yourProperty={js object} ?checked=true @click=(fn [e])
. So it's not just plain generic html. Also they have directives that are just an expression instead of argument=value pair.... That's another headache.
I was thinking about hicada approach where people can create a macro and pass some template-specific things when calling a compiler function. But ideally this should be avoided if possible.
Their idea that they have a compiler code, but user has to create a macro and pass things like js/createElement, custom tags etc. Macro is quite simple, example is
(defmacro html
[body]
(hicada.compiler/compile body {:create-element 'js/React.createElement
:transform-fn (comp)
:array-children? false}
{} &env))
Here is the blog post about it: https://medium.com/@rauh/a-new-hiccup-compiler-for-clojurescript-8a7b63dc5128
Their compiler is very react specific, so I'm thinking how to generalize it for typical js-templates. Ideally to capture css`` templates too.