Squint
Light-weight ClojureScript dialect
Install / Use
/learn @squint-cljs/SquintREADME
Squint is a light-weight dialect of ClojureScript with a compiler and standard library.
Squint is not intended as a full replacement for ClojureScript but as a tool to target JS when you need something more light-weight in terms of interop and bundle size. The most significant difference with CLJS is that squint uses only built-in JS data structures. Squint's output is designed to work well with ES modules.
If you want to use squint, but with the normal ClojureScript standard library and data structures, check out cherry.
:warning: This project is a work in progress and may still undergo breaking changes.
Quickstart
Although it's early days, you're welcome to try out squint and submit issues.
$ mkdir squint-test && cd squint-test
$ npm init -y
$ npm install squint-cljs@latest
Create a .cljs file, e.g. example.cljs:
(ns example
(:require ["fs" :as fs]
["url" :refer [fileURLToPath]]))
(println (fs/existsSync (fileURLToPath js/import.meta.url)))
(defn foo [{:keys [a b c]}]
(+ a b c))
(println (foo {:a 1 :b 2 :c 3}))
Then compile and run (run does both):
$ npx squint run example.cljs
true
6
Run npx squint --help to see all command line options.
Why Squint
Squint lets you write CLJS syntax but emits small JS output, while still having parts of the CLJS standard library available (ported to mutable data structures, so with caveats). This may work especially well for projects e.g. that you'd like to deploy on CloudFlare workers, node scripts, Github actions, etc. that need the extra performance, startup time and/or small bundle size.
Talk
(slides)
Differences with ClojureScript
- The CLJS standard library is replaced with
"squint-cljs/core.js", a smaller re-implemented subset - Keywords are translated into strings
- Maps, sequences and vectors are represented as mutable objects and arrays
- Standard library functions never mutate arguments if the CLJS counterpart do
not do so. Instead, shallow cloning is used to produce new values, a pattern that JS developers
nowadays use all the time:
const x = [...y]; - Most functions return arrays, objects or
Symbol.iterator, not custom data structures - Functions like
map,filter, etc. produce lazy iterable values but their results are not cached. If side effects are used in combination with laziness, it's recommended to realize the lazy value usingvecon function boundaries. You can detect re-usage of lazy values by calling warn-on-lazy-reusage!. - Supports async/await:
(def x (js-await y)). Async functions must be marked with^:async:(defn ^:async foo []). assoc!,dissoc!,conj!, etc. perform in place mutation on objectsassoc,dissoc,conj, etc. return a new shallow copy of objectsprintlnis a synonym forconsole.logpr-strandprnprint EDN with the idea that you can paste the output back into your programs- JavaScript
Maps are printed like maps with a#js/Mapprefix
- JavaScript
- Since JavaScript only supports strings for keys in maps, any data structures used as keys will be stringified
If you are looking for closer ClojureScript semantics, take a look at Cherry 🍒.
Articles
Projects using squint
- @nextjournal/clojure-mode
- cljdoc (source)
- Eucalypt: a Reagent clone in squint
- static search index for Tumblr
- wordle
- Zenith: a game developed for the Lisp Game Jame 2024 by Trevor
- hiccupad (source)
Advent of Code
Solve Advent of Code puzzles with squint here.
Seqs
Squint does not implement Clojure seqs. Instead it uses the JavaScript iteration protocols to work with collections. What this means in practice is the following:
seqtakes a collection and returns an Iterable of that collection, or nil if it's emptyiterabletakes a collection and returns an Iterable of that collection, even if it's emptyseqable?can be used to check if you can call either one
Most collections are iterable already, so seq and iterable will simply
return them; an exception are objects created via {:a 1}, where seq and
iterable will return the result of Object.entries.
first, rest, map, reduce et al. call iterable on the collection before
processing, and functions that typically return seqs instead return an array of
the results.
Memory usage
With respect to memory usage:
- Lazy seqs in squint are built on generators. They do not cache their results, so every time they are consumed, they are re-calculated from scratch.
- Lazy seq function results hold on to their input, so if the input contains resources that should be garbage collected, it is recommended to limit their scope and convert their results to arrays when leaving the scope:
(js/global.gc)
(println (js/process.memoryUsage))
(defn doit []
(let [x [(-> (new Array 10000000)
(.fill 0)) :foo :bar]
;; Big array `x` is still being held on to by `y`:
y (rest x)]
(println (js/process.memoryUsage))
(vec y)))
(println (doit))
(js/global.gc)
;; Note that big array is garbage collected now:
(println (js/process.memoryUsage))
Run the above program with node --expose-gc ./node_cli mem.cljs
Warn on Lazy Reusage
Squint can be asked to log warnings when it detects reusage of lazy values by calling warn-on-lazy-reusage!:
lazy.cljs
(ns lazy)
(warn-on-lazy-reusage!)
(defn lazy-reuser []
(let [a (rest [0 1 2])]
(concat a a)))
(println (mapv inc (lazy-reuser)))
When you compile lazy.cljs, you'll see no warnings:
$ npx squint compile lazy.cljs
[squint] Compiling CLJS file: lazy.cljs
[squint] Wrote file: /tmp/squint-lazy-test/lazy.mjs
You'll see the warnings at runtime:
$ node lazy.mjs
Re-use of lazy value Error
at [Symbol.iterator] (file:///tmp/squint-lazy-test/node_modules/squint-cljs/src/squint/core.js:696:15)
at LazyIterable.gen (file:///tmp/squint-lazy-test/node_modules/squint-cljs/src/squint/core.js:1153:14)
at Generator.next (<anonymous>)
at Module.mapv (file:///tmp/squint-lazy-test/node_modules/squint-cljs/src/squint/core.js:1076:18)
at file:///tmp/squint-lazy-test/lazy.mjs:7:33
at ModuleJob.run (node:internal/modules/esm/module_job:343:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
[ 2, 3, 2, 3 ]
[!TIP] It is sufficient to call
(warn-on-lazy-reusage!)only from your main or entrypoint namespace.
[!NOTE] If you are running code in a browser, look for the warnings in the browser console.
[!TIP] Lazy reusage isn't necessarily incorrect; it's just slower.
Calling warn-on-lazy-reusage! incurs little to no overhead, so, if you wish, you can leave this check in production code.
JSX
You can produce JSX syntax using the #jsx tag:
#jsx [:div "Hello"]
produces:
<div>Hello</div>
and outputs the .jsx extension automatically.
You can use Clojure expressions within #jsx expressions:
(let [x 1] #jsx [:div (inc x)])
Note that when using a Clojure expression, you escape the JSX context so when you need to return more JSX, use the #jsx once again:
(let [x 1]
#jsx [:div
(if (odd? x)
#jsx [:span "Odd"]
#jsx [:span "Even"])])
To pass props, you can use :&:
(let [props {:a 1}]
#jsx [App {:& props}])
See an example of an application using JSX here (source).
[Play with JSX non the playground](https:/
Related Skills
node-connect
340.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
340.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.1kCommit, push, and open a PR

