Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ClojureScript Reimagined - Dutch Clojure Days 2022

ClojureScript Reimagined - Dutch Clojure Days 2022

Michiel Borkent

October 30, 2022
Tweet

More Decks by Michiel Borkent

Other Decks in Programming

Transcript

  1. 🪶 Light-weight (memory) ⚡ Fast to start 
 👩🔧 Easy

    to install & operate (single binary) 🔋 Batteries included
  2. Can we fulfil new Clojure use cases with similar approaches?

    What about (CL)JS targets? JavaScript as universal glue language
  3. ClojureScript Is awesome 🙏 Targets JavaScript ecosystem with Clojure Google

    Closure: advanced whole program optimizer Mostly used from JVM Self-hosted exists (8mb of JS, non-advanced) Are there are opportunities?
  4. Support for ES6 modules Async/await Compiling ad-hoc small snippets (scripting,

    on the fl y compilation) (Also) run without JVM, fast startup for small scripts JSX Distributing compiled CLJS libraries on NPM for usage from JS Interop improvements: JS destructuring CLJS Opportunities
  5. Small Clojure Interpreter $ clj 
 Clojure 1.11.0 user =

    > (require '[sci.core :as sci]) nil user = > (sci/eval - string "(+ 1 2 3)") 
 6 $ clj -M:cljs - m cljs.main - re node 
 ClojureScript 1.11.54 cljs.user = > (require '[sci.core :as sci]) nil cljs.user = > (sci/eval - string "(+ 1 2 3)") 
 6
  6. Nbb (Node.js) Scittle (browser) Joyride (VSCode) 4ever-clojure Clerk viewers Maria.cloud

    (soon) More... Small Clojure Interpreter NEW USAGES OF CLJS THROUGH SCI
  7. CLJS Scripting on Node.js $ npx nbb user = >

    (require '["child_process" :as cp]) nil user = > (str (cp/execSync "echo dude")) "dude\n" user = > (require '[clojure.math :as math]) 
 nil 
 user = > (math/IEEE-remainder 10 math/PI) 0.5752220392306207
  8. 🪶 Light-weight (memory) ⚡ Fast to start 
 👩🔧 Easy

    to install & operate 🔋 Batteries included What about a small(er) CLJS compiler?
  9. Cherry 🍒 • ClojureScript (immutable data structures, cljs.core lib) •

    Bundle size starts at 300kb / 56kb gzipped Squint 🤤 • CLJS syntax but compiles directly to JS: • No immutability, just JS objects all the way down • Bundle size starts at 5 bytes? :) Both projects share common compiler code (fork of scriptjure + cherry-picked code from CLJS)
  10. Cherry • Compile .cljs fi les on the fl y

    into ES6-compatible .js fi les. • npm install cherry-cljs + available on JVM (.cljc) • CLJS core functions shared across builds in common lib • Enables distributing compiled CLJS on NPM • No Google Closure (light weight, compatible with everyday JS tools) • Macro support • REPL support • Async/await • Native JS destructuring • Native JSX support 🍒
  11. $ npm install cherry - cljs 
 $ cat example.cljs

    (ns example) (defn foo [x] (assoc x :foo true)) (prn (foo {})) 
 
 $ npx cherry run example.cljs [cherry] Running example.cljs {:foo true} 
 
 $ cat example.mjs import { assoc, keyword, prn, arrayMap } from 'cherry - cljs/cljs.core.js' var foo = function (x) { return assoc.call(null, x, keyword("foo"), true); }; prn.call(null, foo.call(null, arrayMap())); Cherry .cljs → .mjs
  12. $ npm install esbuild 
 $ npx esbuild example.mjs -

    - bundle - - minify - - platform=node - - outf i le=foo.mjs - - format=esm foo.mjs 296.3kb ⚡ Done in 36ms 
 $ time node foo.mjs {:foo true} node foo.mjs 0.05s user 0.01s system 94% cpu 0.065 total Cherry + JS Toolchain
  13. $ cat example.cljs 
 (ns example) (defn ^:async fetch -

    clojure [] (let [response (js/await (js/fetch "https: / / clojure.org"))] (.text response))) (prn (.substring (js/await (fetch - clojure)) 0 150)) $ node - v v18.6.0 
 
 $ npx cherry run example.cljs [cherry] Running example.cljs "<!DOCTYPE html>\n < ! - - This site was created in Web f l ow. http: / / w w w .web f l ow.com - - > \n < ! - - Last Published: Fri Nov 13 2015 01 : 48 : 45 GMT+0000 (UTC) - - > \n<htm" Cherry + async/await
  14. Next.js, Vite, SolidJS, … 
 Hot-reloading, bundling: driven by JS

    tooling Smallest bundle: 300kb (56kb zipped)
  15. Bundling cherry code Why do we have a baseline of

    300kb / 56kb gzipped? • cherry-cljs/core.cljs is produced using Google Closure advanced compilation • All of ClojureScript is included • Google Closure does not emit code that can be optimized by ES6 treeshakers like esbuild, webpack • Everything is wrapped in one global object, instead of ES6-idiomatic code • Open issue: https://github.com/squint-cljs/cherry/issues/24
  16. Vanilla and ES6-friendly JS output Bene fi ts: • Smaller

    outputs • More direct JS interop (back and forth) Downsides: • Giving up on persistent/immutable CLJS data structures 🤤 Squint
  17. $ npm install squint - cljs 
 $ cat example.cljs

    (ns example) (defn foo [x] (assoc x :foo true)) (prn (foo {})) 
 
 $ npx squint run example.cljs - - extension mjs [squint] Running example.cljs {"foo":true} 
 
 $ cat example.mjs import { assoc, prn } from 'squint - cljs/core.js' var foo = function (x) { return assoc(x, "foo", true); } ; prn(foo(({ }))); export { foo } Squint .cljs → .mjs
  18. $ npm install esbuild 
 $ npx esbuild example.mjs -

    - bundle - - minify - - platform=node - - outf i le=foo.mjs - - format=esm foo.mjs 1.4kb ⚡ Done in 4ms 
 $ time node foo.mjs {"foo":true} node foo.mjs 0.04s user 0.01s system 90% cpu 0.057 total Squint + JS Toolchain
  19. REPL + ES6 REPL = compile + eval Eval in

    ES6 context = dynamic import Dynamic import returns module, not a value - what to print? Special REPL mode in compiler • De fi nes global namespace state • De fi nes global return value
  20. Future work • Unify common compiler code into one repo

    • Better (n)REPL support • Better macro support • Optimizations • Contribute back to CLJS?
  21. CLJS reimagined? • Support for ES6 modules • Async/await •

    Compiling ad-hoc / small snippets (scripting, on the fl y compilation) • (Also) run without JVM • JSX • Distributing compiled CLJS libraries on NPM for usage from JS • Interop improvements: JS destructuring