@borkdude re: squint, is the idea that you just compile yr stuff, squint.js gets prepended and that's it? You can pass that through minifier if you want? Or are you just gzipping w/o minificiation?
@dnolen almost. here's an example of what happens when you compile squint: https://squint-cljs.github.io/squint/?src=gzip%3AH4sIAAAAAAAAE9NISU3LU0jLz1eITozlUlDQSCwuzk9WSFSwSlKwStbUBAAoQM30IAAAAA%3D%3D&repl=false so:
(defn foo [a]
(assoc a :b :c))
compiles to :
import * as squint_core from "squint-cljs/core.js";
var foo = function (a) {
return squint_core.assoc(a, "b", "c");
};
export { foo };
And :require also compiles to import etc. So it emits ES6. You can then process that ES6 with a bundler like esbuild to minify it (like most JS projects do). With vite you get some more UX around that but that's the essence.there's also clojure.string and clojure.set in the standard library
(require '[clojure.string :as str])
(str/join "," [1,2,3])
=>
import * as squint_core from "squint-cljs/core.js";
import * as str from "squint-cljs/src/squint/string.js";
str.join(",", [1, 2, 3]);Was just playing around with it, pretty cool - I guess there are some functionality gaps that you have to be mindful of.
like collections as fns, complex keys, hash / eq - stuff like that
yes. {} are just plain objects, so all keys are stringified. js/Map is supported with assoc etc too though which preserves keys, but JS not supporting hash/eq stuff is kind of a mismatch with that
collections as fns is currently supported in callsites, like:
(#{:a :b :c} :c)
works. it's statically detected that this should be a lookup.
also (keep #{:a :b :c} [:a :d :e :b]) works since many HOFs turn their argument into a function if it isn't already one.
Similar for keywords (which are just strings in squint):
(map :foo [{:foo 1}])
The collection as function call could benefit from some more inference thoughright so manual transform - also no protocols?
protocols do exist but the language itself isn't extensible yet. I'm thinking about doing that, so you can plug in immutable js and make it first class for example
e.g. this code works:
(defprotocol IDude
(dude [_]))
(deftype Dude []
IDude (dude [_] :dude))
(dude (->Dude.))but assoc etc aren't protocolized yet
I'm not being critical, just thinking about code size stuff - re: :lite-mode everything does actually work - could get rid of more things but undesirable
code size is one aspect of squint. being first class JS is another. you can write libs in squint, publish them to NPM without re-publishing the core library every time
oh yeah, wasn't implying otherwise - re: only code size
btw, there is also cherry, which is basically the same as squint, but it uses the regular CLJS data structures. you can get some scripts going very fast, but the output size won't be optimal due to how GC + shadow produced the ESM core lib. The baseline is 44kb gzip for (prn :foo)
playground is here: https://squint-cljs.github.io/cherry/ (takes a while to load due to CDN stuff)
the transparent bit w/ JS stuff is neat - but the fidelity gap makes it hard to dual publish Node & Maven
not saying the fidelity gap needs closing
that's where reader conditionals come in. e.g. reagami can be directly used from JS or Squint, but also directly from CLJS (from source). https://github.com/nextjournal/clojure-mode/ is another library that is published in this way. Compiled squint for JS consumers, CLJS from source for CLJS consumers
you do need to put in the effort to make it compatible. clojure-mode was first written in CLJS. It took a day to make it also work in squint
(it's a plugin for code-mirror)
I do like the async / generator support - that's a bit of work
people requested to use clojure-mode from JS. for a few years there wasn't an answer to this. but squint closed the gap
squint also supports async/await
but that won't be compatible with CLJS
unless with some macros, perhaps
(defn ^:async foo []
(try (js-await ...) (catch ...))oh that was my other question - so you punt on the expression problem w/ async/await?
lol, what?
people can use it out of convenience, they don't have to
you need to generate closures to preserve the semantics
yes, squint does that
so your await might be in the wrong place
(unless I'm missing something). can you give an example?
I mean any time you introduce a closure and the await is inside the wrong fn
give me an example and I'll see whether squint does it correctly or not
do you close over loop variables to fix loop/recur when you use JS iteration underneath?
what would be a trivial example
ask chatgpt for an example ;)
loop/recur is an easy one - you have to make a fn to capture the loop var
ok, but not sure what you mean with JS iteration. what does it have to do with async/await?
stuff like this (or (let [a (+ 1 (await c))] a))
I mean once you come w/ one, there's an infinite number of these 🙂
in the the case of (or ...) it will introduce let and the temp bindings
so probably just (or ... (and ...)) where you await is enough
oh you just make all closures async in an async context?
yep
well, only the closures that serve as wrappers for let, etc
I notice the closure in the return statement shouldn't have to be async so could optimize that
but currently it's like this
right you're combinding w/ the :context parameter when doing this
but I guess this approach doesn't for work yield? or maybe that would create more steps than the user would expect?
I mean I see squint supports it in theory, but I'm confused as to how that could work
let's see. do you have an example? :)
(brb, rehearing my talk for the last time...)
exactly the same the same as await, no difference
welll, yield can only be in statement position so that's a bit easier I guess? this isn't valid for example.
function* foo() {
return 1 + yield 2;
}yeah, that's not valid in JS either
yeah, the thing I was missing was yield* - I stopped paying attention to generators a long time ago.
so yield* doesn't need to be in statement position?
hrm ...
@borkdude https://stackoverflow.com/questions/61428510/why-is-yield-classed-as-an-operator-and-not-a-statement ?
oh that's interesting
oh I think I got it. I just have to wrap yield in some parens to fix this
yep, that fixes it
@dnolen try this: https://squint-cljs.github.io/squint/?repl=true&src=gzip%3AH4sIAAAAAAAAEy3LMQqAMBAEwD6v2PIWsdDSr4QIwVxECTlQEfN7EeymGUmaK%2BZp1YpsBh8cIEUv%2BAdix%2B8I6TBA9rNvm5aEkQyIROMXgId0Tm5dINmMfAGKPjWgWAAAAA%3D%3D might have to hard-refresh to clear cache
Nice
a few tweaks on master, trivial reagami program is looking more like 13.5K brotli - that's pretty good, 7.4% of that is reagami.