cljs-dev

dnolen 2025-11-10T17:53:36.716569Z

@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?

borkdude 2025-11-10T18:20:48.226519Z

@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.

borkdude 2025-11-10T18:48:23.893989Z

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]);

dnolen 2025-11-10T18:57:07.267279Z

Was just playing around with it, pretty cool - I guess there are some functionality gaps that you have to be mindful of.

dnolen 2025-11-10T18:58:04.014269Z

like collections as fns, complex keys, hash / eq - stuff like that

borkdude 2025-11-10T19:01:21.082059Z

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 though

dnolen 2025-11-10T19:02:24.952599Z

right so manual transform - also no protocols?

borkdude 2025-11-10T19:03:04.496199Z

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

borkdude 2025-11-10T19:03:54.367159Z

e.g. this code works:

(defprotocol IDude
  (dude [_]))

(deftype Dude []
  IDude (dude [_] :dude))

(dude (->Dude.))

borkdude 2025-11-10T19:04:01.558209Z

but assoc etc aren't protocolized yet

dnolen 2025-11-10T19:04:55.668649Z

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

borkdude 2025-11-10T19:06:10.216169Z

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

dnolen 2025-11-10T19:07:11.665089Z

oh yeah, wasn't implying otherwise - re: only code size

borkdude 2025-11-10T19:08:07.697689Z

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)

borkdude 2025-11-10T19:08:46.595909Z

playground is here: https://squint-cljs.github.io/cherry/ (takes a while to load due to CDN stuff)

dnolen 2025-11-10T19:09:04.612959Z

the transparent bit w/ JS stuff is neat - but the fidelity gap makes it hard to dual publish Node & Maven

dnolen 2025-11-10T19:09:25.904989Z

not saying the fidelity gap needs closing

borkdude 2025-11-10T19:10:00.049469Z

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

borkdude 2025-11-10T19:11:36.474059Z

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

borkdude 2025-11-10T19:12:03.613309Z

(it's a plugin for code-mirror)

dnolen 2025-11-10T19:13:16.238049Z

I do like the async / generator support - that's a bit of work

borkdude 2025-11-10T19:13:26.551739Z

people requested to use clojure-mode from JS. for a few years there wasn't an answer to this. but squint closed the gap

borkdude 2025-11-10T19:13:51.045619Z

squint also supports async/await

borkdude 2025-11-10T19:13:59.212499Z

but that won't be compatible with CLJS

borkdude 2025-11-10T19:14:09.960119Z

unless with some macros, perhaps

borkdude 2025-11-10T19:14:36.895829Z

(defn ^:async foo []
  (try (js-await ...) (catch ...))

dnolen 2025-11-10T19:15:00.769769Z

oh that was my other question - so you punt on the expression problem w/ async/await?

borkdude 2025-11-10T19:15:18.190499Z

lol, what?

borkdude 2025-11-10T19:15:35.743089Z

people can use it out of convenience, they don't have to

dnolen 2025-11-10T19:15:46.429619Z

you need to generate closures to preserve the semantics

borkdude 2025-11-10T19:15:53.401479Z

yes, squint does that

dnolen 2025-11-10T19:16:03.264899Z

so your await might be in the wrong place

borkdude 2025-11-10T19:16:12.840839Z

(unless I'm missing something). can you give an example?

dnolen 2025-11-10T19:16:47.900859Z

I mean any time you introduce a closure and the await is inside the wrong fn

borkdude 2025-11-10T19:17:12.955629Z

give me an example and I'll see whether squint does it correctly or not

dnolen 2025-11-10T19:18:04.188989Z

do you close over loop variables to fix loop/recur when you use JS iteration underneath?

dnolen 2025-11-10T19:18:37.746289Z

what would be a trivial example

borkdude 2025-11-10T19:18:49.110899Z

ask chatgpt for an example ;)

dnolen 2025-11-10T19:19:14.387189Z

loop/recur is an easy one - you have to make a fn to capture the loop var

borkdude 2025-11-10T19:19:38.153069Z

ok, but not sure what you mean with JS iteration. what does it have to do with async/await?

dnolen 2025-11-10T19:21:33.737819Z

stuff like this (or (let [a (+ 1 (await c))] a))

dnolen 2025-11-10T19:21:50.588049Z

I mean once you come w/ one, there's an infinite number of these 🙂

dnolen 2025-11-10T19:23:42.392469Z

in the the case of (or ...) it will introduce let and the temp bindings

dnolen 2025-11-10T19:24:05.235569Z

so probably just (or ... (and ...)) where you await is enough

dnolen 2025-11-10T19:25:32.786159Z

oh you just make all closures async in an async context?

borkdude 2025-11-10T19:25:36.163809Z

yep

borkdude 2025-11-10T19:25:50.055949Z

well, only the closures that serve as wrappers for let, etc

borkdude 2025-11-10T19:28:59.233549Z

I notice the closure in the return statement shouldn't have to be async so could optimize that

borkdude 2025-11-10T19:29:08.471689Z

but currently it's like this

dnolen 2025-11-10T19:40:22.655909Z

right you're combinding w/ the :context parameter when doing this

👍 1
dnolen 2025-11-10T19:41:04.326159Z

but I guess this approach doesn't for work yield? or maybe that would create more steps than the user would expect?

dnolen 2025-11-10T19:45:45.819319Z

I mean I see squint supports it in theory, but I'm confused as to how that could work

borkdude 2025-11-10T19:47:28.003279Z

let's see. do you have an example? :)

borkdude 2025-11-10T19:47:37.847719Z

(brb, rehearing my talk for the last time...)

dnolen 2025-11-10T19:48:09.798699Z

exactly the same the same as await, no difference

borkdude 2025-11-10T19:54:58.022089Z

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;
}

borkdude 2025-11-10T19:59:37.419829Z

yeah, that's not valid in JS either

dnolen 2025-11-10T20:03:08.230449Z

yeah, the thing I was missing was yield* - I stopped paying attention to generators a long time ago.

dnolen 2025-11-10T20:03:30.132269Z

so yield* doesn't need to be in statement position?

dnolen 2025-11-10T20:09:50.647899Z

hrm ...

borkdude 2025-11-10T20:27:28.402489Z

oh that's interesting

borkdude 2025-11-10T20:29:14.594539Z

oh I think I got it. I just have to wrap yield in some parens to fix this

borkdude 2025-11-10T20:30:54.163319Z

yep, that fixes it

dnolen 2025-11-10T20:34:58.740649Z

Nice

dnolen 2025-11-10T01:58:16.177809Z

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.

🎉 4