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
:output-wrapper false}
: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 likecljs.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.