Bootstrapped ClojureScript Support

shadow-cljs (since about 2.0.15) supports compiling builds that make use of the self-hosted ClojureScript Compiler. Previously this was not supported since the build needs to be modified in a few places to ensure that all the required files are available. shadow-cljs itself continues to use the JVM compiler.

This feature is split into two parts. First you need a “host” build which will be your “app” (currently limited to :browser builds). This build will host the compiler and then use the support files generated by the second :bootstrap build. These support files include the CLJS analyzer cache, macro JS files, .cljs files, etc.

The :bootstrap build itself will generate an “index” with useful information for the compiler. When the “host” build starts up it calls the provided shadow.cljs.bootstrap.browser/init function to load the index and fetch all resources necessary to start using the compiler (which usually involves the analyzer data for cljs.core and the cljs.core$macros JS file).

Once init completes the compiler can be used. The provided shadow.cljs.bootstrap.browser/load function takes care of properly loading dependencies. The CLJS analyzer will call this function whenever a dependency is required and it will load the analyzer data, macro and JS files. Only namespaces pre-compiled by the :bootstrap build will be available.

The “host” can look something like this:

(ns demo.selfhost.simple
  (:require [cljs.js :as cljs]
            

[cljs.env :as env]

[shadow.cljs.bootstrap.browser :as boot])) (defn print-result [{:keys [error value] :as result}] (js/console.log “result” result) (set! (.-innerHTML (js/document.getElementById “dump”)) value)) (def code ” (ns simpleexample.core (:require [clojure.string :as str] [reagent.core :as r])) (defonce timer (r/atom (js/Date.))) (defonce time-color (r/atom \”#f34\”)) (defonce time-updater (js/setInterval #(reset! timer (js/Date.)) 1000)) (defn greeting [message] [:h1 message]) (defn clock [] (let [time-str (-> @timer .toTimeString (str/split \” \”) first)] [:div.example-clock {:style {:color @time-color}} time-str])) (defn color-input [] [:div.color-input \”Time color: \” [:input {:type \”text\” :value @time-color :on-change #(reset! time-color (-> % .-target .-value))}]]) (defn simple-example [] [:div

[greeting \”Hello world, it is now\”]

[clock]

[color-input]]) (r/render [simple-example] (js/document.getElementById \”app\”))” ) (defonce compile-state-ref (env/default-compiler-env)) (defn compile-it [] (cljs/eval-str compile-state-ref code “[test]” {:eval cljs/js-eval :load (partial boot/load compile-state-ref)} print-result)) (defn start [] (boot/init compile-state-ref {:path “/bootstrap”} compile-it)) (defn stop [])

The shadow-cljs.edn config

{:dependencies
 [[reagent "0.8.0-alpha1" :exclusions [cljsjs/create-react-class]]

 :source-paths
 ["src"]
 
 :builds
 {:bootstrap-host
  {:target :browser

   :output-dir "out/demo-selfhost/public/simple/js"
   :asset-path "/simple/js"

   :compiler-options
   {:optimizations :simple}

   :modules
   {:base
    {:entries [demo.selfhost.simple]}}

   :devtools
   {:http-root "out/demo-selfhost/public"
    :http-port 8700
    :before-load demo.selfhost.simple/stop
    :after-load demo.selfhost.simple/start}}
 
  :bootstrap-support
  {:target :bootstrap
   :output-dir "out/demo-selfhost/public/bootstrap"
   :exclude #{cljs.js}
   :entries [cljs.js demo.macro reagent.core]
   :macros []}}}

The config option for the :bootstrap build are

  • :entries a sequence of namespaces you want to have available for the self-hosted compiler
  • :exclude for macro namespaces that are not self-host compatible as they would otherwise break the build. This would include things like cljs.core.async.macros, cljs.js, etc.
  • :macros will usually be optional since all macros used by the :entries will already be included.

Everything is written to :output-dir. The path where those files are available must be passed to the boot/init function.

@mhuebert created a standalone example and the shadow-cljs repo itself contains the example above.

You can try it by running

npm install -g shadow-cljs
git clone https://github.com/thheller/shadow-cljs.git
cd shadow-cljs
shadow-cljs watch bootstrap-host bootstrap-support
open http://localhost:8700

Usually shadow-cljs does not require lein but it is required in this case since I’m using lein to build the shadow-cljs project. The standalone example is probably better suited for testing.

I didn’t cover a whole lot of technical details. Come by #shadow-cljs if you have questions. Please report issues here.

Add a Comment