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

nbb at London Clojurians, 22-3-2022

nbb at London Clojurians, 22-3-2022

Nbb (Node.js Babashka) is a scripting platform for Clojure on Node.js.

Michiel Borkent

March 22, 2022

More Decks by Michiel Borkent

Other Decks in Programming


  1. • ClojureScript scripting tool for Node.js (14.x+) • Interpreted via

    SCI, similar to babashka • Developed since September 2021 • Can be used to script against any library in Node.js ecosystem, including ES6 • Included CLJS libraries: promesa, js-interop, reagent, ... $ npx nbb user=> (require '["child_process" :as cp]) nil user=> (str (cp/execSync "echo dude")) "dude\n" user=> (require '[applied-science.js-interop :as j]) 
 user=> (j/get-in (clj->js {:a {:b {:c :foo}}}) [:a :b :c]) "foo"
  2. Node.js: the good parts • Startup time Large ecosystem: many

    useful libraries • Quickly write small web apps • Browser testing: Playwright, Puppeteer • CLI/TUI apps • Cron jobs (bots), cloud functions (AWS, Google), gulp ClojureScript...
  3. CLJS scripting options • Planck • Self-hosted CLJS on JSCore

    • time planck -e '(+ 1 2 3)' => ~350-500ms • No NPM / Node.js integration
  4. CLJS scripting options • Lumo • Self-hosted CLJS on Node.js

    • time lumo -e '(+ 1 2 3)' => ~150-270ms • Uses custom Node.js version/image (v11.13.0) • Does have NPM / Node.js integration • No ES6 modules support • Unmaintained
  5. CLJS scripting options • Nbb • Interpreted CLJS (SCI) on

    Node.js • time nbb -e '(+ 1 2 3)' => ~120-180ms 
 (could be reduced by eliding docstrings, etc.) • Uses Node.js version from system (14+ recommended) • ES6 modules support • Currently actively developed and maintained
  6. Nbb use cases • Command line apps / scripts •

    TUI apps (text ui) • Small web applications (sitefox) • UI testing (Playwright, Puppeteer) • AWS Lambda / Google Cloud Functions • Learning Clojure! • Basically anything Node.js but using Clojure
  7. Babashka vs nbb • Does nbb replace bb? 

    No, complementary. • Does nbb mean bb is less actively developed? 
 > No. Both based on SCI, bene fi t from shared code-base. • Use case for nbb > bb:
 "Does bb have a library / pod for ...?" • Parsing excel sheets? • Parsing iCal fi les? • > Not currently, but nbbjs + a Node.js library might do the trick!
  8. Projects using SCI • Babashka (JVM / GraalVM) • Nbb

    (Node.js) • Scittle (browser) • Obb (macOS) • ...
  9. Promises: promesa • Ubiquitous in JavaScript and Node.js • Trend:

    APIs tend to become more promise-based than callback based • Doable via raw JS interop • But there's an awesome library with convenient macros/ functions which makes it easier: promesa • Built-in to nbb: "the way" to deal with promises
  10. nREPL • Built-in nREPL implementation, initial implementation by viesti •

    $ nbb nrepl-server :port 1337 • cider-connect <RET> localhost <RET> 1337
 + use clojure-mode (see issue) • Calva has a built-in nbb jack-in!
  11. Console REPL $ npx nbb 
 Welcome to nbb v0.2.4!

 user=> (range) 
 ^C"Error: Script execution was interrupted by `SIGINT`" • Uses Node VM which can be killed with SIGINT • Technique could also be applied to nREPL server
  12. Require JS libs: ES modules • Trend: CJS modules ->

    ES module s • Not supported in CLJS Compiler with Node target since that compiles into js/require • Not supported in lumo
  13. Require JS libs • Always use string to denote npm

    library (shadow-cljs convention adopted by nbb):
 (require '["fs" :as fs]) 
 (ns example (:require ["asciidoctor$default" :as asciidoctor])) • Implemented using dynamic import: (js/import ...) to support ES module s • Which means that ns and require must be asyn c • Which means all (top level) eval must be async
  14. Async eval • SCI's eval-string and other APIs fns are

    synchronou s • Solution: nbb reads every top level expression + custom eva l • If expression matches (ns ...): 
 - eval every :require clause manually
 - if string lib name, use async js/import
 - built-in namespaces (reagent.core, promesa.core, ...) are loaded lazily, async for better startup tim e • SCI's clojure.core/require is patched to use an async variant, wrapped in nbb.core/await • Every other expression is evaluated synchronously and wrapped in promis e • All top level expressions are now evaluated as chained promise
  15. Require JS libs: ES6 • (require '["fs" :as fs]) evaluated

    as (js/ import "fs") + alias def 
 user=> (def fs (nbb.core/await (js/import "fs"))) 
 user=> (js/console.log fs) 
 { Dir: [class Dir], Dirent: [class Dirent], F_OK: 0, 
  16. Require JS libs: ES6 • JS: export default ... •

    nbb: (require '["library$default" :as lib]) • JS: export { foo }; • nbb: (require '["library$foo" :as foo])
  17. To use ES6 is to be ES6... • Regular ClojureScript

    compiler only emits CJS for Node.js • Shadow-cljs to the rescue: :target :esm • nbb is available as library from JS as ESM: import { loadFile } from nbb
  18. Misc • Publish your nbb project on npm: npx nbb-project

    • Self-contained executable with caxa
  19. Future • Many improvements to be made: • Load .cljs

    fi les from npm modules? (hammock) • Performance improvements in SCI • Your contributions? • If deno catches on: dbb
  20. Nbb + Node.js! • Startup time • Large ecosystem: many

    useful libraries • Quickly write small web apps • Browser testing: Playwright, Puppeteer • CLI/TUI apps • Cron jobs (bots), cloud functions (AWS, Google) • ClojureScript 💖
  21. Thanks! • Thomas Heller (shadow-cljs) • Mauricio Szabo (advice, promesa)

    • Kimmo Koskinen (initial nREPL) • Chris McCormick (early adopter, sitefox) • Jay Zawrotny (eccentric-j) (early adopter, contributor) • Andrey Antukh (promesa) • Valtteri Harmainen (AWS Lambda) • Michaël Salihi (cool projects) • Peter Strömberg (Calva) • Benjamin Scherdtner, Bobby Towers (contributions) • Nextjournal (Martin, Jack, ...) (improvements while developing UI tests, google cloud) • Many others!