1. Introduction

The shadow-cljs compiler provides everything you need to compile your ClojureScript code with a focus on simplicity and ease of use. It augments the official ClojureScript compiler with a number of additional features and enhancements:

  • Less configuration

  • Deterministic build order

  • More reliable NPM integration

    • More likely your favorite JS library will work

  • No need for build-specific source directories

  • Better externs checker

  • Better warning tracker

  • Easier use with js projects

  • Includes hot code reload

  • Smart integration with cljs.test for the browser and CI

  • The built-in development server aggressively fixes browser caching issues

1.1. About this Book

1.1.1. Work in Progress

This is a work in progress. If you find an error, please submit a PR to fix it, or an issue with details of the problem.

1.1.2. Contributing

This source for this book is hosted on Github.

1.1.3. Conventions Used

There are many examples in this book. Most things used in these should be obvious from their context, but to prevent misunderstanding it is important to know the author’s intentions.

When command-line examples are given we may include BASH comments (starting with #), and will usually include the standard user UNIX prompt of $ to indicate separation of the command from its output.

# A comment. This command lists files:
$ ls -l
shadow-cljs.edn
project.clj
...

Many of the examples are of the configuration file for the compiler. This file contains an EDN map. Where we have already discussed required options we will often elide them for clarity. In this case we’ll usually include an ellipsis to indicate "content that is required but isn’t in our current focus":

Example 1. Specify dependencies
{:dependencies [[lib "1.0"]]}
Example 2. Add source paths
{...
 :source-paths ["src"]
 ...}

This allows us to concisely include enough context to understand the nesting of the configuration of interest:

Example 3. Nested option
{...
 :builds {:build-id {...
                     :output-dir "resources/public/js"}}}

Code examples may be similarly shortened.

2. Installation

2.1. Standalone via npm

You will need:

NPM
$ npm install --save-dev shadow-cljs
$ npm install -g shadow-cljs
Yarn
$ yarn add --dev shadow-cljs
$ yarn global add shadow-cljs

2.2. Library

Although it is recommended to run the standalone version via npm you can also embed shadow-cljs into any other Clojure JVM tool (eg. lein, boot, …​).

The artifact can be found at:

shadow cljs
shadow cljs

3. Usage

shadow-cljs can be used in many different ways but the general workflow stays the same.

During development you have the option to compile a build once or run a watch worker which watches your source files for changes and re-compiles them automatically. When enabled the watch will also hot-reload your code and provide a REPL. During development the focus is on developer experience with fast feedback cycles. Development code should never be shipped to the public.

When it is time to get serious you create a release build which creates an optimized build suitable for production. For this the Closure Compiler is used which applies some seriously :advanced optimizations to your code to create the most optimal output available. This may require some tuning to work properly when using lots of interop with native JavaScript but works flawlessly for ClojureScript (and the code from the Closure Library).

3.1. Command Line

If installed globally, you can use the shadow-cljs command directly.

$ shadow-cljs help

If you prefer to only use the local npm install you can invoke it via npx or yarn.

# npm
$ npx shadow-cljs help

# yarn
$ yarn shadow-cljs help

# manually
$ ./node_modules/.bin/shadow-cljs help
Commonly used shadow-cljs commands during development
# compile a build once and exit
$ shadow-cljs compile app

# compile and watch
$ shadow-cljs watch app

# connect to REPL for the build (available while watch is running)
$ shadow-cljs cljs-repl app

# connect to standalone node repl
$ shadow-cljs node-repl
Running a release build optimized for production use.
$ shadow-cljs release app

Sometimes you may run into some release issues due to :advanced compilation. These commands can help track down the causes.

Release debugging commands.
$ shadow-cljs check app
$ shadow-cljs release app --debug

3.1.1. Server Mode

A shadow-cljs command can be fairly slow to start. To improve this shadow-cljs can run in "server mode" which means that a dedicated process is started which all other commands can use to execute a lot faster since they won’t have to start a new JVM/Clojure instance.

Commands that do long-running things implicitly start a server instance (eg. watch) but it is often advisable to have a dedicated server process running.

You can run the process in the foreground in a dedicated terminal. Use CTRL+C to terminate the server.

$ shadow-cljs server

# or (if you'd like REPL to control the server process)
$ shadow-cljs clj-repl

You can also run the server in the background controlled via the common start|stop|restart functions.

$ shadow-cljs start
$ shadow-cljs stop
$ shadow-cljs restart

Once any server is running every other command will use that and run much faster.

3.2. Build Tool Integration

shadow-cljs can integrate with other Clojure tools since the primary distribution is just a .jar file available via Clojars.

Caution
It is strongly recommended to use the standalone shadow-cljs version. The command does a lot of things to optimize the user experience (e.g. faster startup) which are not done by other tools. You’ll also save yourself a lot of headaches dealing with dependency conflicts and other related errors.

3.2.1. Leiningen

If you’d like to use Leiningen for your build system (particularly if you use Cursive), the shadow-cljs command-line utility can be configured to use lein to run your commands. Enable this by adding a :lein entry to your shadow-cljs.edn config. With this setting, the shadow-cljs command will use lein to launch the JVM, ignoring any :source-paths and :dependencies in shadow-cljs.edn; relying instead on lein to set them from project.clj.

{:lein true
 ; :source-paths and :dependencies are now ignored in this file
 ; configure them via project.clj
 :builds { ... }

When using :lein you must include the thheller/shadow-cljs artifact in your :dependencies manually.

Using a dedicated lein profile
{:lein {:profile "+cljs"}
 :builds {...}}
The lein command used by shadow-cljs as configured above
lein with-profile +cljs run -m shadow.cljs.devtools.cli ...
Running Tasks Directly From Leiningen

You may also directly execute shadow-cljs commands via lein if you prefer to not use the shadow-cljs command itself. Most commands should work if you just replace shadow-cljs …​ with lein run -m shadow.cljs.devtools.cli …​.

Some sample commands are listed below:

Listing Options
$ shadow-cljs help
# or
$ lein run -m shadow.cljs.devtools.cli help
Start a dev mode build with a REPL and live-reload
$ shadow-cljs watch build-id
# or
$ lein run -m shadow.cljs.devtools.cli watch build-id
Just compile :dev mode once, no REPL or live-reload:
$ lein run -m shadow.cljs.devtools.cli compile build-id
Create a :release mode optimized build:
$ lein run -m shadow.cljs.devtools.cli release build-id

3.2.2. tools.deps / deps.edn

The new deps.edn can also be used to manage your :dependencies and :source-paths instead of using the built-in methods or lein. All shadow-cljs commands will then be launched via the new clojure utility instead.

Important
tools.deps is still changing quite frequently. Make sure you are using the latest version.

To use this set the :deps true property in your config. It is also possible to configure which deps.edn aliases should be used.

Example with :cljs alias
{:deps {:aliases [:cljs]}
 :builds ...}

You may also specify shadow-cljs -A:cljs …​ in the command line instead.

Important
Aliases are only applied when a new instance/server is started. They do not apply when connecting to a running server.

3.2.3. Boot

The authors have little Boot experience, so this chapter is in need of contributions. We understand that Boot allows you to build your tool chain out of functions. Since shadow-cljs is a normal JVM library, you can call functions within it to invoke tasks.

Some boot tasks are available here: https://github.com/jgdavey/boot-shadow-cljs

3.3. Clojure REPL

It is possible to use shadow-cljs entirely via a Clojure REPL. You can start a Clojure REPL via shadow-cljs itself or by any of the usual ways to get one (eg. lein repl, clj). If the thheller/shadow-cljs artifact is on the classpath you are good to go.

Lets start with the "easy" way.
$ shadow-cljs clj-repl
...
shadow-cljs - REPL - see (help), :repl/quit to exit
[1:0]~shadow.user=>

The shadow.cljs.devtools.api namespace has functions that map more or less 1:1 to the CLI counterparts.

Example commands
;; shadow-cljs watch foo
(shadow.cljs.devtools.api/watch :foo)
;; the shadow.user ns already has an alias for shadow.cljs.devtools.api
(shadow/watch :foo)
;; shadow-cljs watch foo --verbose
(shadow/watch :foo {:verbose true})
;; shadow-cljs compile foo
(shadow/compile :foo)
;; shadow-cljs release foo
(shadow/release :foo)

3.3.1. Embedded

When you are not using the shadow-cljs clj-repl but instead a REPL started by any other means you need to start the embedded server.

Example using lein repl
$ lein repl
nREPL server started on port 57098 on host 127.0.0.1 - nrepl://127.0.0.1:57098
REPL-y 0.3.7, nREPL 0.2.13
Clojure 1.9.0
...

user=> (require '[shadow.cljs.devtools.server :as server])
nil
user=> (server/start!)
...
:shadow.cljs.devtools.server/started
user=> (require '[shadow.cljs.devtools.api :as shadow])
nil
user=> (shadow/compile :foo)
...

You can stop the embedded server by running (shadow.cljs.devtools.server/stop!). This will also stop all running build processes.

3.4. ClojureScript REPL

Most :target configurations automatically inject the necessary code for a ClojureScript REPL. It should not require any additional configuration. For the CLJS REPL to work you need 2 things

  1. a running watch for your build

  2. connect the JS runtime of the :target. Meaning if you are using the :browser target you need to open a Browser that has the generated JS loaded. For node.js builds that means running the node process.

Once you have both you can connect to the CLJS REPL via the command line or from the Clojure REPL.

CLI
$ shadow-cljs watch build-id
...

# different terminal
$ shadow-cljs cljs-repl build-id
shadow-cljs - connected to server
[3:1]~cljs.user=>
REPL
$ shadow-cljs clj-repl
...
[2:0]~shadow.user=> (shadow/watch :browser)
[:browser] Configuring build.
[:browser] Compiling ...
[:browser] Build completed. (341 files, 1 compiled, 0 warnings, 3,19s)
:watching
[2:0]~shadow.user=> (shadow/repl :browser)
[2:1]~cljs.user=>
Tip
Type :repl/quit to exit the REPL. This will only exit the REPL, the watch will remain running.
Tip
You may run multiple watch "workers" in parallel and connect/disconnect to their REPLs at any given time.
No connected runtime error.
[3:1]~cljs.user=> (js/alert "foo")
There is no connected JS runtime.

If you see this you need to open your App in the Browser or start the node process.

3.4.1. Node REPL

The above REPLs were all coupled to a specific build where you are responsible for running the given :target. You may also launch an embedded node REPL where a process is started for you.

$ shadow-cljs node-repl

This starts a blank CLJS REPL with an already connected node process.

Important
If you exit the Node REPL the node process is also killed!

node-repl lets you get started without any additional configuration. It has access to all your code via the usual means. Since it is not connected to any build it does not do any live-reloading.

3.5. Running Clojure Code

You can use the shadow-cljs CLI to call specific Clojure functions from the command line. This is useful when you want run some code before/after certain tasks. Suppose you wanted to rsync the output of your release build to a remote server.

Example Clojure Namespace in src/my/build.clj
(ns my.build
  (:require
    [shadow.cljs.devtools.api :as shadow]
    [clojure.java.shell :refer (sh)]))

(defn release []
  (shadow/release :my-build)
  (sh "rsync" "-arzt" "path/to/output-dir" "my@server.com:some/path"))
Running the release function
$ shadow-cljs clj-run my.build/release

You can pass arguments to the invoked functions via the command line.

Using arguments via normal Clojure fn args
...
(defn release [server]
  (shadow/release :my-build)
  (sh "rsync" "-arzt" "path/to/output-dir" server)
Passing the server from the command line
$ shadow-cljs clj-run my.build/release my@server.com:some/path
Tip
The usual (defn release [& args]) structure also works if you want to parse the args with something like tools.cli.

You have access to the full power of Clojure here. You can build entire tools on top of this if you like. As a bonus everything you write this way is also directly available via the Clojure REPL.

Important
When the server is running the namespace will not be reloaded automatically, it will only be loaded once. It is recommended to do the development using a REPL and reload the file as usual (eg. (require 'my.build :reload)). You may also run shadow-cljs clj-eval "(require 'my.build :reload)" to reload manually from the command line.

3.5.1. Calling watch via clj-run

By default the functions called by clj-run only have access to a minimal shadow-cljs runtime which is enough to run compile, release and any other Clojure functionality. The JVM will terminate when your function completes.

If you want to start a watch for a given build you need to declare that the function you are calling requires a full server. This will cause the process to stay alive until you explicitly call (shadow.cljs.devtools.server/stop!) or CTRL+C the process.

(ns demo.run
  (:require [shadow.cljs.devtools.api :as shadow))

;; this fails because a full server instance is missing
(defn foo
  [& args]
  (shadow/watch :my-build))

;; this metadata will ensure that the server is started so watch works
(defn foo
  {:shadow/requires-server true}
  [& args]
  (shadow/watch :my-build))

4. Configuration

shadow-cljs is configured by a shadow-cljs.edn file in your project root directory. You can create a default one by running shadow-cljs init. It should contain a map with some global configuration and a :builds entry for all your builds.

{:source-paths [...]
 :dependencies [...]
 :builds {...}}

An example config could look like this:

{:dependencies
 [[reagent "0.8.0-alpha2"]]

 :source-paths
 ["src"]

 :builds
 {:app {:target :browser
        :output-dir "public/js"
        :asset-path "/js"
        :modules {:main {:entries [my.app]}}}}}

The file structure for this example should look like this:

.
├── package.json
├── shadow-cljs.edn
└── src
    └── my
        └── app.cljs

4.1. Source Paths

:source-paths configures your JVM classpath. The compiler will use this config to find Clojure(Script) source files (eg. .cljs).

It is fine to put everything into one source path but you can use multiple if you want to "group" source files in certain ways. It is useful if you want to keep your tests separate for example.

Using multiple source paths
{:source-paths ["src/main" "src/test"]
 ...}
File Structure
.
├── package.json
├── shadow-cljs.edn
└── src
    └── main
        └── my
            └── app.cljs
    └── test
        └── my
            └── app_test.cljs

It is not recommended to separate source files by extension (eg. src/clj, src/cljs, src/cljc). For some reason this is widely used in CLJS project templates but it just makes things harder to use.

4.2. Dependencies

4.2.1. Clojure(Script)

Your dependencies are managed via the :dependencies key at the root of the shadow-cljs.edn config file. They are declared in the same notation that other Clojure tools like lein or boot use.

Notice that the source path is only specified once in the entire configuration. The system will use namespace dependency graphs to determine what code is needed in the final output of any given build.

4.2.2. JavaScript

shadow-cljs integrates fully with the npm ecosystem to manage JavaScript dependencies.

You can use npm or yarn to manage your dependencies, please refer to their respective documentation.

Both manage your dependencies via a package.json file in your project directory. Almost every package available via npm will explain how to install it. Those instructions now apply to shadow-cljs as well.

Installing a JavaScript package
# npm
$ npm install the-thing

# yarn
$ yarn add the-thing

Nothing more is required. Dependencies will be added to the package.json file and this will be used to manage them.

Tip
If you don’t have a package.json yet run npm init from a command line.
Missing JS Dependency?

You might run into errors related to missing JS dependencies. Most ClojureScript libraries do not yet declare the npm packages they use since they still expect to use CLJSJS. We want to use npm directly which means you must manually install the npm packages until libraries properly declare the :npm-deps themselves.

The required JS dependency "react" is not available, it was required by ...

This means that you should npm install react.

Tip
In the case of react you probably need these 3 packages: npm install react react-dom create-react-class.

4.3. Server Options

This section is for other options that configure the shadow-cljs server instance. They are optional.

4.3.1. nREPL

The shadow-cljs server provides a nREPL server via TCP. If you look at the startup message you’ll see the port of nREPL, and the port will also be stored in target/shadow-cljs/nrepl.port:

$ shadow-cljs watch dev
shadow-cljs - HTTP server for ":app" available at http://localhost:8020
shadow-cljs - server running at http://0.0.0.0:9630
shadow-cljs - nrepl running at /0.0.0.0:9462
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...

You can configure the port and additional middleware with shadow-cljs.edn:

{...
 :nrepl {:port 9000
         :middleware []} ; list of namespace-qualified symbols
}

If the popular cider-nrepl is found on the classpath it will be added automatically. No additional configuration required.

nREPL Usage

When connecting to the nREPL server the connection always starts out as a Clojure REPL. Switching to a CLJS REPL works similarly to the non-nREPL version. First the watch for the given build needs to be started and then we need to select this build to switch the current nREPL session to that build. After selecting the build everything will be eval’d in ClojureScript instead of Clojure.

(require '[shadow.cljs.devtools.api :as shadow])
(shadow/watch :the-build)
(shadow/nrepl-select :the-build)
Tip
Use :repl/quit to return to Clojure.
Embedded nREPL Server

When you use shadow-cljs embedded in other tools that provide their own nREPL server (eg. lein) you need to configure the shadow-cljs middleware. Otherwise you won’t be able to switch between CLJ and CLJS REPLs.

Example Leiningen project.clj
(defproject my-amazing-project "1.0.0"
  ...
  :repl-options
  {:init-ns shadow.user ;; or any of your choosing
   :nrepl-middleware
   [shadow.cljs.devtools.server.nrepl/cljs-load-file
    shadow.cljs.devtools.server.nrepl/cljs-eval
    shadow.cljs.devtools.server.nrepl/cljs-select
    ;; required by some tools, not by shadow-cljs.
    cemerick.piggieback/wrap-cljs-repl]}
  ...)
Tip
You still need to start the embedded server manually before using the CLJS REPL.

4.3.2. SSL

The shadow-cljs HTTP servers support SSL. It requires a Java Keystore that provides a matching private key and certificate.

shadow-cljs.edn with SSL configured
{...
 :ssl {:keystore "ssl/keystore.jks"
       :password "shadow-cljs"}
 ...}

The above are the defaults so if you want to use those it is fine to just set :ssl {}.

You can create a Keystore using the java keytool command. Creating a trusted self-signed certificate is also possible but somewhat complicated.

  • OpenSSL instructions for Linux and Windows (via WSL)

  • macOS instructions

The created Certificates.p12 (macOS) or localhost.pfx (Linux, Windows) file can be turned into the required keystore.jks via the keytool utility.

$ keytool -importkeystore -destkeystore keystore.jks -srcstoretype PKCS12 -srckeystore localhost.pfx
Important
You must generate the Certificate with a SAN (Subject Alternative Name) for "localhost" (or whichever host you want to use). SAN is required to get Chrome to trust the Certificate and now show warnings. The password used when exporting must match the password assigned to the Keystore.

4.3.3. HTTP(S)

The shadow-cljs server starts one primary HTTP server. It is used by the builds to connect the Hot Reload and REPL clients via websocket. By default it listens on Port 9630. If that Port is in use it will increment by one and attempt again until an open Port is found.

Startup message indicating the Port used
shadow-cljs - server running at http://0.0.0.0:9630

When :ssl is configured the server will be available via https:// instead.

Tip
The server automatically supports HTTP/2 when using :ssl.

The UI served by this server is still in development so the main purpose of this HTTP server currently is serving the websockets.

If you prefer to set your own port instead you can do this via the :http config.

shadow-cljs.edn with :http config
{...
 :http {:port 12345
        :host "my.machine.local"}
 ...}

:ssl switches the server to server https:// only. If you want to keep the http:// version you can configure a separate :ssl-port as well.

{...
 :http {:port 12345
        :ssl-port 23456
        :host "localhost"}
 ...}

5. Build Configuration

shadow-cljs.edn will also need a :builds section. Builds should be a map of builds keyed by build ID:

A configuration file with a build map.
{:dependencies [[some-library "1.2.1"] ...]
 :source-paths ["src"]
 :builds
 {:app   {:target     :browser
          ... browser-specific options ...}
  :tests {:target :karma
          ... karma-specific options ...}}}

Each build describes artifacts that the compiler will build. The build target is an extensible feature of shadow-cljs, and the compiler comes with quite a few of them already.

5.1. Build Target

Each build in shadow-cljs must define a :target which defines where you intend your code to be executed. There are default built-ins for the browser and node.js. They all share the basic concept of having :dev and :release modes. :dev mode provides all the usual development goodies like fast compilation, live code reloading and a REPL. :release mode will produce optimized output intended for production.

Targets are covered in separate chapters.

Here are some of them:

:browser

Output code suitable for running in a web browser.

:bootstrap

Output code suitable for running in bootstrapped cljs environment.

:browser-test

Scan for tests to determine required files, and output tests suitable for running in the browser.

:karma

Scan for tests to determine required files, and output karma-runner compatible tests. See Karma.

:node-library

Output code suitable for use as a node library.

:node-script

Output code suitable for use as a node script.

:npm-module

Output code suitable for use as an NPM module.

Each target is covered in more detail in its own chapter since the remaining build options vary on the target you select.

5.2. Development Options

Each build :target typically provides some development support. They are grouped under the :devtools key for each :build.

5.2.1. REPL

When running watch code for the REPL is injected automatically and usually does not require additional configuration. Currently there is one config option that allows configuring which namespace the REPL will start in. It defaults to cljs.user.

{...
 :builds
 {:app {...
        :devtools {:repl-init-ns my.app
                   ...}}}}

5.2.2. Preloads

As a developer most of your time is spent in development mode. You’re probably familiar with tools like figwheel, boot-reload, and devtools. It’s almost certain that you want one or more of these in your builds.

Preloads are used to force certain namespaces into the front of your generated Javascript. This is generally used to inject tools and instrumentation before the application actually loads and runs. The preloads option is simply a list of namespaces in the :devtools/:preloads section of shadow-cljs-edn:

{...
 :builds
 {:app {...
        :devtools {:preloads [fulcro.inspect.preload]
                   ...}}}}
Note
Since version 2.0.130 shadow-cljs automatically adds cljs-devtools to the preloads in watch and compile if they are on the classpath. All you need to do is make sure binaryage/devtools is in your dependencies list. (Note, not binaryage/cljs-devtools.)

5.2.3. Hot Code Reload

The React and ClojureScript ecosystems combine to make this kind of thing super useful. The shadow-cljs system includes everything you need to do your hot code reload, without needing to resort to external tools.

In order to use it you simply run:

shadow-cljs watch build-id

5.2.4. Lifecycle Hooks

You can configure the compiler to run functions just before hot code reload brings in updated code, and just after. These are useful for stopping/starting things that would otherwise close over old code.

These can be configured via the :devtools section in your build config or directly in your code via metadata tags.

Metadata

You can set certain metadata on normal CLJS defn vars to inform the compiler that these functions should be called at a certain time when live reloading.

hook config via metadata
(ns my.app)

(defn ^:dev/before-load stop []
  (js/console.log "stop"))

(defn ^:dev/after-load start []
  (js/console.log "start"))

This would call my.app/stop before loading any new code and my.app/start when all new code was loaded. You can tag multiple functions like this and they will be called in dependency order of their namespaces.

There are also async variants of these in case you need to do some async work that should complete before proceeding with the reload process.

async hooks example
(ns my.app)

(defn ^:dev/before-load-async stop [done]
  (js/console.log "stop")
  (js/setTimeout
    (fn []
      (js/console.log "stop complete")
      (done)))

(defn ^:dev/after-load-async start [done]
  (js/console.log "start")
  (js/setTimeout
    (fn []
      (js/console.log "start complete")
      (done)))
Important
The functions will receive one callback function that must be called when their work is completed. If the callback function is not called the reload process will not proceed.

It is possible to tag namespaces with metadata so they will never be reloaded even if they are recompiled.

non-reloadable ns
(ns ^:dev/once my.thing)

(js/console.warn "will only execute once")
Config

In addition to the metadata you can configure the lifecycle hooks via shadow-cljs.edn.

:before-load

A symbol (with namespace) of a function to run just before refreshing files that have been recompiled. This function must by synchronous in nature.

:before-load-async

A symbol (with namespace) of a function (fn [done]) to run just before refreshing. This function can do async processing, but must call (done) to indicate it is complete.

:after-load

A symbol (with namespace) of a function to run after hot code reload is complete.

:after-load-async

A symbol (with namespace) of a function (fn [done]) to run after hot code reload is complete. This function can do async processing, but must call (done) to indicate it is complete.

:autoload

A boolean controlling whether code should be hot loaded. Implicitly set to true if either of the callbacks is set. Always enabled for the :browser target by default, set to false to disable.

:ignore-warnings

A boolean controlling whether code with warnings should be reloaded. Defaults to false.

A sample of lifecycle hooks.
{...
 :builds
 {:app {...
        :devtools {:before-load  my.app/stop
                   :after-load   my.app/start
                   ...}}}}
Tip
If neither :after-load nor :before-load are set the compiler will only attempt to hot reload the code in the :browser target. If you still want hot reloading but don’t need any of the callbacks you can set :autoload true instead.

5.3. Compiler Cache

shadow-cljs will cache all compilation results by default. The cache is invalidated whenever anything relevant to the individual source files changes (eg. changed compiler setting, changed dependencies, etc.). This greatly improves the developer experience since incremental compilation will be much faster than starting from scratch.

Invalidating the cache however can not always be done reliably if you are using a lot of macros with side-effects (reading files, storing things outside the compiler state, etc.). In those cases you might need to disable caching entirely.

Namespaces that are known to include side-effecting macros can be blocked from caching. They won’t be cached themselves and namespaces requiring them will not be cached as well. The clara-rules library has side-effecting macros and is blocked by default. You can specify which namespaces to block globally via the :cache-blockers configuration. It expects a set of namespace symbols.

clara.rules cache blocking example (this is done by default)
{...
 :cache-blockers #{clara.rules}
 :builds {...}}

In addition you can control how much caching is done more broadly via the :build-options :cache-level entry. The supported options are:

:all

The default, all CLJS files are cached

:jars

Only caches files from libraries, ie. source files in .jar files

:off

Does not cache any CLJS compilation results (by far the slowest option)

Compiling without Cache
{...
 :builds
 {:app
  {:target :browser
   ...
   :build-options
   {:cache-level :off}}}}

The cache files are stored in a dedicated directory for each build so the cache is never shared between builds. A build with the id :app will have the :dev cache in the directory:

Cache location for cljs/core.cljs
target/shadow-cljs/builds/app/dev/ana/cljs/core.cljs.cache.transit.json

The :cache-root setting defaults to target/shadow-cljs and controls where ALL cache files will be written. It can only be configured globally, not per build.

{:source-paths [...]
 :dependencies [...]
 :cache-root ".shadow-cljs"
 :builds ...}

;; cache then goes to
;; .shadow-cljs/builds/app/dev/ana/cljs/core.cljs.cache.transit.json

The :cache-root is always resolved relative to the project directory. You can also specify absolute paths (eg. /tmp/shadow-cljs).

5.4. Closure Defines

The Closure Library & Compiler allow you to define variables that are essentially compile time constants. You can use these to configure certain features of your build. Since the Closure compiler treats these as constants when running :advanced optimizations they are fully supported in the Dead-Code-Elimination passes and can be used to remove certain parts of the code that should not be included in release builds.

You can define them in your code

(ns your.app)

(goog-define VERBOSE false)

(when VERBOSE
  (println "Hello World"))

This defines the your.app/VERBOSE variable as false by default. This will cause the println to be removed in :advanced compilation. You can toggle this to true via the :closure-defines options which will enable the println. This can either be done for development only or always.

{...
 :builds
 {:app
  {:target :browser
   ...
   :modules {:app {:entries [your.app]}}
   ;; to enable in development only
   :dev {:closure-defines {your.app/VERBOSE true}}
   ;; to enable always
   :closure-defines {your.app/VERBOSE true}
   ;; you may also enable it for release as well
   :release {:closure-defines {your.app/VERBOSE true}}
   }}
Tip
It is generally safer to use the "disabled" variant as the default since it makes things less likely to be included in a release build when they shouldn’t be. Forgetting to set a :closure-defines variable should almost always result in less code being used not more.

Closure Defines from the Closure Library

  • goog.DEBUG: The Closure Library uses this for many development features. shadow-cljs automatically sets this to false for release builds.

  • goog.LOCALE can be used to configure certain localization features like goog.i18n.DateTimeFormat. It accepts a standard locale string and defaults to en. Pretty much all locales are supported, see here and here.

5.5. Compiler Options

The CLJS compiler supports several options to influence how some code is generated. For the most part shadow-cljs will pick some good defaults for each :target but you might occasionally want to change some of them.

These are all grouped under the :compiler-options key in your build config.

{:dependencies [...]
 :builds
 {:app
  {:target :browser
   ...
   :compiler-options {:fn-invoke-direct true}}}}

Most of the standard ClojureScript Compiler Options are either enabled by default or do not apply. So very few of them actually have an effect. A lot of them are also specific to certain :target types and do not apply universally (e.g. :compiler-options {:output-wrapper true} is only relevant for :target :browser).

Currently supported options include

  • :optimizations supports :advanced, :simple or :whitespace, defaults to :advanced. :none is the default for development and cannot be set manually. release with :none won’t work.

  • :infer-externs :all, :auto, true or false, defaults to true

  • :static-fns (Boolean) defaults to true

  • :fn-invoke-direct (Boolean) defaults to false

  • :elide-asserts (Boolean) default to false in development and true in release builds

  • :pretty-print and :pseudo-names default to false. You can use shadow-cljs release app --debug to enable both temporarily without touching your config. This is very useful when running into problem with release builds

  • :source-map (Boolean) defaults to true during development, false for release.

  • :externs vector of paths, defaults to []

  • :checked-arrays (Boolean), defaults to false

  • :language-in and :language-out

  • :anon-fn-naming-policy

  • :rename-prefix and :rename-prefix-namespace

Unsupported or non-applicable Options

Options that don’t have any effect at all include

  • :verbose is controlled by running shadow-cljs compile app --verbose not in the build config.

  • :foreign-libs and :libs

  • :stable-names always enabled, cannot be disabled

  • :install-deps

  • :source-map-path, :source-asset-path and :source-map-timestamp

  • :cache-analysis always enabled, cannot be disabled.

  • :recompile-dependents

  • :preamble

  • :hashbang (the :node-script target supports this, others don’t)

  • :compiler-stats use --verbose to get detailed information instead

  • :optimize-constants always done for release builds, cannot be disabled

  • :parallel-build always enabled

  • :aot-cache

  • :package-json-resolution see :js-options :resolve instead

  • :watch-fn

  • :process-shim

5.6. Conditional Reading

Caution
This feature only works in shadow-cljs. It was officially rejected by the ClojureScript project. It will still compile fine in CLJS but only the official branches work (e.g. :cljs). It might still be supported one day but as of now it is not.

shadow-cljs lets you configure additional reader features in .cljc files. By default you can only use reader conditionals to generate separate code for :clj, :cljs or :cljr. In many CLJS builds however it is also desirable to select which code is generated based on your :target.

Example: Some npm packages only work when targeting the :browser, but you may have a ns that you also want to use in a :node-script build. This might happen frequently when trying to use Server-Side Rendering (SSR) with your React App. codemirror is one such package.

(ns my.awesome.component
  (:require
    ["react" :as react]
    ["codemirror" :as CodeMirror]))

;; suppose you create a CodeMirror instance on some React :ref
(defn init-cm [dom-node]
  (let [cm (CodeMirror/fromTextArea dom-node #js {...})]
    ...))

...

This namespace will compile fine for both builds (:node-script and :browser) but when trying to run the :node-script it will fail since the codemirror package tries to access the DOM. Since react-dom/server does not use refs the init-cm function will never be called anyways.

While you can use :closure-defines to conditionally compile away the init-cm fn you can not use it to get rid of the extra :require. Reader conditionals let you do this easily.

(ns my.awesome.component
 (:require
   ["react" :as react]
   ;; NOTE: The order here matters. Only the first applicable
   ;; branch is used. If :cljs is used first it will still be
   ;; taken by the :server build
   #?@(:node [[]]
       :cljs [["codemirror" :as CodeMirror]])))

#?(:node
   (defn init-cm [dom-node] ...))

...
:reader-features config examples
{...
 :builds
 ;; app build configured normally, no adjustments required
 {:app
  {:target :browser
   ...}
  ;; for the server we add the :node reader feature
  ;; it will then be used instead of the default :cljs
  :server
  {:target :node-script
   :compiler-options
   {:reader-features #{:node}}}}}

The :server build will then no longer have the codemirror require and the init-cm function is removed. Becoming only

(ns my.awesome.component
  (:require
    ["react" :as react]))

...
Important
This feature is only available in .cljc files and will fail in .cljs files.

6. Targeting the Browser

The :browser target produces output intended to run in a Browser environment. During development it supports live code reloading, REPL, CSS reloading. The release output will be minified by the Closure Compiler with :advanced optimizations.

A basic browser configuration looks like this:

{:dependencies [...]
 :source-paths [...]

 :builds
 {:app {:target :browser
        :output-dir "public/assets/app/js"
        :asset-path "/assets/app/js"
        :modules {:main {:entries [my.app]}}}]}

6.1. Output Settings

The browser target outputs a lot of files, and a directory is needed for them all. You’ll need to serve these assets with some kind of server, and the Javascript loading code needs to know the server-centric path to these assets. The options you need to specify are:

:output-dir

The directory to use for all compiler output.

:asset-path

The relative path from web server’s root to the resources in :output-dir.

Your entry point javascript file and all related JS files will appear in :output-dir.

Warning
Each build requires its own :output-dir, you may not put multiple builds into the same directory. This directory should also be exclusively owned by the build. There should be no other files in there. While shadow-cljs won’t delete anything it is safer to leave it alone. Compilation creates many more files than just the main entry point javascript file during development: source maps, original sources, and generated sources.

The :asset-path is a prefix that gets added to the paths of module loading code inside of the generated javascript. It allows you to output your javascript module to a particular subdirectory of your web server’s root. The dynamic loading during development (hot code reload) and production (code splitting) need this to correctly locate files.

Locating your generated files in a directory and asset path like this make it so that other assets (images, css, etc.) can easily co-exist on the same server without accidental collisions.

For example: if your web server will serve the folder public/x when asked for the URI /x, and your output-dir for a module is public/assets/app/js then your asset-path should be /assets/app/js. You are not required to use an absolute asset path, but it is highly recommended.

6.2. Modules

Modules configure how the compiled sources are bundled together and how the final .js are generated. Each Module declares a list of Entry Namespace and from that dependency graph is built. When using multiple Modules the code is split so that the maximum amount of code is moved to the outer edges of the graph. The goal is to minimize the amount of code the browser has to load initially and loading the rest on-demand.

Tip
Don’t worry too much about :modules in the beginning. Start with one and split them later.

The :modules section of the config is always a map keyed by module ID. The module ID is also used to generate the Javascript filename. Module :main will generate main.js in :output-dir.

The available options in a module are:

:entries

The namespaces that serve as the root nodes of the dependency graph for the output code of this module.

:depends-on

The names of other modules that must be loaded in order for this one to have everything it needs.

:prepend

String content that will be prepended to the js output. Useful for comments, copyright notice, etc.

:append

String content that will be appended to the js output. Useful for comments, copyright notice, etc.

:prepend-js

A string to prepend to the module output containing valid javascript that will be run through Closure optimizer.

:append-js

A string to append to the module output containing valid javascript that will be run through Closure optimizer.

The following example shows a minimum module configuration:

Example :browser config
{...
 :builds
 {:app {:target :browser
        :output-dir "public/js"
        ...
        :modules {:main {:entries [my.app.main]}}}}}

shadow-cljs will follow the dependency graph from the root set of code entry points in the :entries to find everything needed to actually compile and include in the output. Namespaces that are not required will not be included.

The above config will create a public/js/main.js file. During development there will be an additional public/js/cljs-runtime directory with lots of files. This directory is not required for release builds.

6.3. Code Splitting

Declaring more than one Module requires a tiny bit of additional static configuration so the Compiler can figure out how the Modules are related to each other and how you will be loading them later.

In addition to :entries you’ll need to declare which module depends on which (via :depends-on). How you structure this is entirely up to your needs and there is no one-size-fits-all solution unfortunately.

Say you have a traditional website with actual different pages.

  • www.acme.com - serving the homepage

  • www.acme.com/login - serving the login form

  • www.acme.com/protected - protected section that is only available once the user is logged in

One good configuration for this would be to have one common module that is shared between all the pages. Then one for each page.

Example config with multiple :modules
{...
 :output-dir "public/js"
 :modules
 {:shared
  {:entries [my.app.common]}
  :home
  {:entries [my.app.home]
   :depends-on #{:shared}}
  :login
  {:entries [my.app.login]
   :depends-on #{:shared}}
  :protected
  {:entries [my.app.protected]
   :depends-on #{:shared}}
Tip
You can leave the :entries of the :shared module empty to let the compiler figure out which namespaces are shared between the other modules.
Generated file structure
.
└── public
    └── js
        ├── shared.js
        ├── home.js
        ├── login.js
        └── protected.js

In your HTML for the Homepage you’d then always include the shared.js on each page and the others conditionally depending on which page the user is on.

HTML for the /login page
<script src="/js/shared.js"></script>
<script src="/js/login.js"></script>
Important
The .js files must be included in the correct order. The manifest.edn can help with this.

6.3.1. Loading code dynamically

These days Single-Page-Apps (SPA) are becoming more popular and they work similarly only that instead of letting the Server decide which JS to include to Client does it by itself.

Using shadow-cljs’s built-in Loader Support

The compiler supports generating the required data for using the shadow.loader utility namespace. It exposes a simple interface to let you load modules on-demand at runtime.

You only need to add :module-loader true to your build config. The loader will always be injected into the default module (the one everything else depends on).

At runtime you may use the shadow.loader namespace to load modules. You may also load a module eagerly by just using a <script> tag in your page.

{...
 :builds
   {:app
     {:target :browser
      ...
      :module-loader true
      :modules {:main  {:entries [my.app]}
                :extra {:entries [my.app.extra]
                        :depends-on #{:main}}}}}}

If you had the following for your main entry point:

(ns my.app
  (:require [shadow.loader :as loader]))

(defn fn-to-call-on-load []
  (js/console.log "extra loaded"))

(defn fn-to-call-on-error []
  (js/console.log "extra load failed"))

Then the following expressions can be used for loading code:

Loading a module
;; load returns a goog.async.Deferred, and can be used like a promise
(-> (loader/load "extra")
    (.then fn-to-call-on-load fn-to-call-on-error))
Loading many modules
;; must be a JS array, also returns goog.async.Deferred
(loader/load-many #js ["foo" "bar"])
Including a callback
(loader/with-module "extra" fn-to-call-on-load)

You can check if a module is loaded using (loaded? "module-name").

Loader Costs

Using the loader is very lightweight. It has a few dependencies which you may not be otherwise using. In practice using :module-loader true adds about 8KB gzip’d to the default module. This will vary depending on how much of goog.net and goog.events you are already using, and what level of optimization you use for your release builds.

Using the Standard ClojureScript API

The generated code is capable of using the standard ClojureScript cljs.loader API. See the documentation on the ClojureScript website for instructions.

The advantage of using the standard API is that your code will play well with others. This may be of particular importance to library authors. The disadvantage is that the dynamic module loading API in the standard distribution is currently somewhat less easy-to-use than the support in shadow-cljs.

6.4. Output Wrapper

Release builds only: The code generated by the Closure Compiler :advanced compilation will create a lot of global variables which has the potential to create conflicts with other JS running in your page. To isolate the created variables the code can be wrapped in an anonymous function to the variables only apply in that scope.

release builds for :browser with only one :modules are wrapped in (function(){<the-code>}).call(this); by default. So no global variables are created.

When using multiple :modules (a.k.a code splitting) this is not enabled by default since each module must be able to access the variables created by the modules it depends on. The Closure Compiler supports an additional option to enable the use of an output wrapper in combination with multiple :modules named :rename-prefix-namespace. This will cause the Compiler to scope all "global" variables used by the build into one actual global variable. By default this is set to :rename-prefix-namespace "$APP" when :output-wrapper is set to true.

{...
 :builds
 {:target :browser
  ...
  :compiler-options
  {:output-wrapper true
   :rename-prefix-namespace "MY_APP"}}}

This will only create the MY_APP global variable. Since every "global" variable will now be prefixed by MY_APP. (e.g. MY_APP.a instead of just a) the code size can go up substantially. It is important to keep this short. Browser compression (e.g. gzip) helps reduce the overhead of the extra code but depending on the amount of global variables in your build this can still produce a noticeable increase.

Important
Note that the created variable isn’t actually useful directly. It will contain a lot of munged/minified properties. All exported (eg. ^:export) variables will still be exported into the global scope and are not affect by this setting. The setting only serves to limit the amount of global variables created, nothing else. Do not use it directly.

6.5. Web Workers

The :modules configuration may also be used to generate files intended to be used as a Web Workers. You may declare any module (except the default) as a Web Worker by setting :web-worker true. The generated file will contain some additional bootstrap code which will load its dependencies automatically. The way :modules work also ensures that code used only by the worker will also only be in the final file for the worker. Each worker should have a dedicated CLJS namespace.

An example of generating a web worker script
{...
 :builds
   {:app
    {:target :browser
     :output-dir "public/js"
     :asset-path "/js"
     ...
     :modules {:main   {:entries [my.app]}
               :extra  {:entries [my.app.extra]
                        :depends-on #{:main}}
               :worker {:entries [my.app.worker]
                        :depends-on #{:main}
                        :web-worker true}}}}}

The above configuration will generate worker.js which you can use to start the Web Worker. It will have all code from the :main module available (but not :extra). The code in the my.app.worker namespace will only ever execute in the worker. Worker generation happens in both development and release modes.

Sample echo worker
(ns my.app.worker)

(js/self.addEventListener "message"
  (fn [^js e]
    (js/postMessage (.. e -data))
Sample using the worker
(ns my.app)

(let [worker (js/Worker. "/js/worker.js"))]
  (.. worker (addEventListener (fn [e] (js/console.log e))))
  (.. worker (postMessage "hello world")))
Note
Hot code reload does not support reloading code in the worker while it is running. I suggest shutting down all workers using the :devtools :before-load function and restarting it in the :after-load.

6.6. Cacheable Output

In a web setting it is desirable to cache .js files for a very long time to avoid extra request. It is common practice the generate a unique name for the .js file for every released version. This changes the URL used to access it and thereby is safe to cache forever.

You can add :module-hash-names true to your build config to automatically create a MD5 signature for each generated output module file. That means that a :main module will generate a main.<md5hash>.js instead of just the default main.js.

:module-hash-names true will include the full 32-length md5 hash, if you prefer a shorter version you can specify a number between 1-32 instead (eg. :module-hash-names 8). Be aware that shortening the hash may increase the chances of generating conflicts. I recommend using the full hash.

Example :module-hash-names config
{...
 :builds
   {:app
     {:target :browser
      ...
      :output-dir "public/js"
      :asset-path "/js"
      :module-hash-names true
      :modules {:main  {:entries [my.app]}
                :extra {:entries [my.app.extra]
                        :depends-on #{:main}}}}}}

Instead of generating main.js it will now generate main.<hash>.js in the :output-dir.

Since the filename can change with every release it gets a little bit more complicated to include them in your HTML. The Output Manifest can help with that.

6.7. Output Manifest

shadow-cljs generates a manifest.edn file in the configured :output-dir. This file contains a description of the module config together with an extra :output-name property which maps the original module name to actual filename (important when using the :module-hash-names feature).

Sample output of manifest.edn when using hashed filenames.
[{:module-id :common,
  :name :common,
  :output-name "common.15D142F7841E2838B46283EA558634EE.js",
  :entries [...],
  :depends-on #{},
  :sources [...]}
 {:module-id :page-a,
  :name :page-a,
  :output-name "page-a.D8844E305644135CBD5CBCF7E359168A.js",
  :entries [...],
  :depends-on #{:common},
  :sources [...]}
 ...]

The manifest contains all :modules sorted in dependency order. You can use it to map the :module-id back to the actual generated filename.

Development builds also produce this file and you may check if for modifications to know when a new build completed. :module-hash-names does not apply during development so you’ll get the usual filenames.

You can configure the name of the generated manifest file via the :build-options :manifest-name entry. It defaults to manifest.edn. If you configure a filename with .json ending the output will be JSON instead of EDN. The file will be relative to the configured :output-dir.

Example manifest.json config
{...
 :builds
   {:app
     {:target :browser
      ...
      :build-options {:manifest-name "manifest.json"}
      :modules {:main  {:entries [my.app]}
                :extra {:entries [my.app.extra]
                        :depends-on #{:main}}}}}}

6.8. Development Support

The :devtools section of the configuration for :browser supports a few additional options for configuring an optional dev-time HTTP server for a build and CSS reloading.

6.8.1. Heads-Up Display (HUD)

The :browser target now uses a HUD to display a loading indicator when a build is started. It will also display warnings and errors if there are any.

You can disable this by setting :hud false in the :devtools section.

Opening Files

Warnings include a link to source location which can be clicked to open the file in your editor. For this a little bit of config is required.

You can either configure this in your shadow-cljs.edn config for the project or globally in your home directory under ~/.shadow-cljs/config.edn.

:open-file-command configuration
{:open-file-command
 ["idea" :pwd "--line" :line :file]}

The :open-file-command expects a vector representing a very simple DSL. Strings are kept as they are and keyword are replaced by their respective values. A nested vector can be used in case you need to combine multiple params, using clojure.core/format style pattern.

The above example would execute

$ idea /path/to/project-root --line 3 /path/to/project-root/srv/main/demo/foo.cljs
emacsclient example
{:open-file-command
 ["emacsclient" "-n" ["+%s:%s" :line :column] :file]}
$ emacsclient -n +3:1 /path/to/project-root/srv/main/demo/foo.cljs

The available replacement variables are:

:pwd

Process Working Directory (aka project root)

:file

Absolute File Path

:line

Line Number of Warning/Error

:column

Column Number

:wsl-file

Translated WSL file path. Useful when running shadow-cljs via WSL Bash. Translates a /mnt/c/Users/someone/code/project/src/main/demo/foo.cljs path into C:\Users...

:wsl-pwd

Translated :pwd

6.8.2. HTTP Server

shadow-cljs can provide a basic HTTP server for your build. By default it will serve all static files from the configured :http-root directory, and fall back to index.html when a resource is not found (this is what you typically want when developing an application which uses browser push state).

The server supports the following options:

:http-root

The disk path from which to serve root filesystem requests. If not supplied, no disk files are served.

:http-port

The port to serve from.

:http-host

Optional. The hostname to listen on. Defaults to localhost.

:http-resource-root

Optional. A path relative to classpath from which resources can be served. If not supplied, no resources are served.

:http-handler

Optional. A namespace (as a symbol). A (fn [req] resp) that is used if a resource is not found for the given request. Defaults to shadow.http.push-state/handle.

The following two options only apply when using the default, built-in handler:

:push-state/headers

(optional) A map of HTTP headers to respond with. Defaults to text/html standard headers.

:push-state/index

(optional) The file to serve. Defaults to index.html.

Typical HTTP config serving the "public" directory under http://localhost:8080
{...
 {:builds
  {:app {:target :browser
         :output-dir "public/js"
         :asset-path "/js"
         :modules {:main {:entries [my.app]}}
         ...
         :devtools {:http-root "public"
                    :http-port 8080}}}}
Example public/index.html
<!doctype html>
<html>
<body>
<h1>Hello World</h1>
<script src="/js/main.js"></script>
</body>
</html>

6.8.3. CSS Reloading

The Browser devtools can also reload CSS for you. This is enabled by default and in most cases requires no additional configuration when you are using the built-in HTTP dev server.

The server will watch the configured :http-root and notify the client about file updates. If a .css file is linked with an absolute path it will be reloaded on change. Relative paths are currently not supported.

Example HTML snippet
<link rel="stylesheet" href="/css/main.css"/>
Example Hiccup since we aren’t savages
[:link {:rel "stylesheet" :href "/css/main.css"}]

This will cause an update if the file <:http-root>/css/main.css is changed.

shadow-cljs currently provides no support for directly compiling CSS but the usual tools will work and should be run separately. Just make sure the output is generated into the correct places.

When you are not using the built-in HTTP Server you can specify :watch-dir instead which should be a path to the document root used to serve your content.

Example :watch-dir config
{...
    {:builds
      {:app {...
             :devtools {:watch-dir "public"}}}}

6.8.4. Proxy Support

By default the devtools client will attempt to connect to the shadow-cljs process via the configured HTTP server (usually localhost). If you are using a reverse proxy to serve your HTML that might not be possible. You can set :devtools-url to configure which URL to use.

{...
 :builds
 {:app {...
        :devtools {:before-load  my.app/stop
                   :after-load   my.app/start
                   :devtools-url "https://some.host/shadow-cljs"
                   ...}}}}

shadow-cljs will then use the :devtools-url as the base when making requests. It is not the final URL so you must ensure that all requests starting with the path you configured (eg. /shadow-cljs/*) are forwarded to the host shadow-cljs is running on.

Incoming Request to Proxy
https://some.host/shadow-cljs/ws/foo/bar?asdf
must forward to
http://localhost:9630/foo/bar?asdf

The client will make WebSocket request as well as normal XHR requests to load files. Ensure that your proxy properly upgrades WebSockets.

Important
The requests must be forwarded to the main HTTP server, not the one configured in the build itself.

7. Targeting node.js

There is built-in support for generating code that is intended to be used as a stand-alone script, and also for code that is intended to be used as a library. See the section on common configuration for the base settings needed in a configuration file.

Note
The optimizations in node at the time of this writing default to :simple. You can use the normal configuration options to set the optimization level.

7.1. node.js Scripts

The :target :node-script produces single-file stand-alone output that can be run using node.js. The code is just ClojureScript, and an entry point is easy to define:

(ns demo.script)

(defn main [& cli-args]
  (prn "hello world"))

7.1.1. Build Options

You will need the same basic main configuration as in other targets (like :source-paths), but you’ll need some node-specific build target options:

:main

(required). The namespace-qualified symbol of your script’s entry point function.

:output-to

(required). The path and filename for the generated script.

:output-dir

(optional). The path for supporting files in development mode. Defaults to a cache directory.

Sample node script build
{:source-paths [...]
 ...
 :builds {:script
            {:id        :script
             :target    :node-script
             :main      demo.script/main
             :output-to "out/demo-script/script.js"}}}

When compiled this results in a standalone out/demo-script/script.js file intended to be called via node script.js <command line args>. When run it will call (demo.script/main <command line args>) function on startup. This only ever produces the file specified in :output-to. Any other support files (e.g. for development mode) are written to a temporary support directory.

7.1.2. Hot Code Reload

You will often write scripts that run as servers or some other long-running process. Hot code reload can be quite useful when working with these, and it is simple to set up:

  1. Add start/stop callback functions.

  2. Configure the build use those hooks.

Here is an example http server in node:

Sample node script with start/stop hooks for hot code reload.
(ns demo.script
  (:require ["http" :as http]))

(defn request-handler [req res]
  (.end res "foo"))

; a place to hang onto the server so we can stop/start it
(defonce server-ref
  (volatile! nil))

(defn main [& args]
  (js/console.log "starting server")
  (let [server (http/createServer #(request-handler %1 %2))]

    (.listen server 3000
      (fn [err]
        (if err
          (js/console.error "server start failed")
          (js/console.info "http server running"))
        ))

    (vreset! server-ref server)))

(defn start
  "Hook to start. Also used as a hook for hot code reload."
  []
  (js/console.warn "start called")
  (main))

(defn stop
  "Hot code reload hook to shut down resources so hot code reload can work"
  [done]
  (js/console.warn "stop called")
  (when-some [srv @server-ref]
    (.close srv
      (fn [err]
        (js/console.log "stop completed" err)
        (done)))))

(js/console.log "__filename" js/__filename)

The associated configuration is (shadow-cljs.edn):

Adding hooks for hot code reload.
{...
 :builds
   { :script {... as before

              ; add in reload hooks
              :devtools {:before-load-async demo.script/stop
                         :after-load demo.script/start}}}}
Warning
Many libraries hide state or do actions that prevent hot code reloading from working well. There is nothing the compiler can do to improve this since it has no idea what those libraries are doing. Hot code reload will only work well in situations where you can cleanly "stop" and "restart" the artifacts used.

7.2. node.js Libraries

The :target :node-library emits code that can be used (via require) as a standard node library, and is useful for publishing your code for re-use as a compiled Javascript artifact.

As with other modes the main configuration options apply and must be set. The target-specific options are:

:target

Use :node-library

:output-to

(required). The path and filename for the generated library.

:exports

(required) Either a single namespace-qualified symbol or a map from keywords to namespace-qualified symbols.

:output-dir

(optional). The path for supporting files in development mode. Defaults to a cache directory.

The hot code reload story is similar to the script target, but may not work as well since it cannot as easily control all of the code that is loaded.

7.2.1. Exports

The :exports option deserves some specific examples. There are two possible ways to specify what should be exported:

  1. Export a single artifact

  2. Export any number of artifacts

In the first case, you can use a single symbol:

Build configuration with a single export
{...
 :builds {:lib {:exports demo.ns/f
                ...}}}

and the resulting require in Node will give you that artifact:

$ node
> var f = require('./lib.js');
f(); // the object I get is exactly what I exported

In the second case you supply a map from keyword names to artifacts:

Build configuration with multiple exports
{...
 :builds {:lib {:exports {:g       demo.ns/f
                          :h       other.ns/thing
                          :ns/ok?  another.ns/ok?}
                ...}}}

The keyword is used as the name of the entry in the exported object. No munging is done to this keyword name (but namespaces are dropped). So, the above example maps cljs f to g, etc.:

$ node
> var lib = require('./lib.js');
lib.g(); // call demo-ns/f
lib["ok?"](); // call another-ns/ok?
Note
The exports config automatically tracks exported symbols and passes them on to the optimization stage. This means that anything listed in :exports will not be renamed by Google Closure optimizations.

7.2.2. Full Example

The example below creates a lib.js file intended to be consumed via the normal Node require mechanism.

(ns demo.lib)

(defn hello []
  (prn "hello")
  "hello")

The build configuration would be:

{...
 :builds {:library {:target    :node-library
                    :output-to "out/demo-library/lib.js"
                    :exports   {:hello demo.lib/hello}}}}

and the runtime use is as you would expect:

$ cd out/demo-library
$ node
> var x = require('./lib');
undefined
> x.hello()
hello
'hello'

As :node-script this will only create the file specified in :output-to. The :exports map maps CLJS vars to the name they should be exported to.

Note
Development mode has the same setup as for node scripts (extra dependencies).

8. Embedding in the JS Ecosystem — The :npm-module Target

There is an additional target that is intended to help you use shadow-cljs as part of a project and provide seamless integration with existing JS tools (eg. webpack, browserify, babel, create-react-app, …​) with as little configuration as possible.

It can be configured like other targets, but since it is meant to work in the JS ecosystem it comes with a convenience mode that makes such integration easier.

8.1. Convenience Mode

This target is meant to be as easy to access as possible, and does not actually require a specific build in the config. You still need a non-empty config file, but the default one from shadow-cljs init will do.

ClojureScript organizes files into namespaces which means that a demo.foo namespace should be inside a src/demo/foo.cljs file. Where src is the default source path the shadow-cljs tool (see main configuration).

If you have this file in src/demo:

Sample source for foo.cljs
(ns demo.foo)

(defn hello [who]
  (str "Hello, " who "!"))

you can compile it (and any others) via:

$ shadow-cljs compile npm
[:npm] Build completed. (16 files, 5 compiled, 0 warnings, 7.91s)

The generated exports will be named shadow-cljs/ with the CLJS namespace.

$ node
> var x = require("shadow-cljs/demo.foo");
undefined
> x.hello("JS")
'Hello, JS!'

Watching with incremental compile is as simple as running shadow-cljs watch npm.

8.2. Explicit Configuration

You can also treat :npm-module on the same footing with your other builds: give it explicit configuration. This allows you to also customize the code generation options. Of course you will configure the main configuration as in all targets. The target-specific options are:

:target

Use :npm-module

:output-dir

The path for supporting files in development mode.

:entries

(optional) A vector of namespaces to include. Everything from these will be included. Defaults to everything it can find.

If you use an :output-dir like "node_modules/shadow-cljs" you can access them by using require("shadow-cljs/demo.foo"). When using something not in node_modules you must include them using a relative path. With :output-dir "out" that would be require("./out/demo.foo") from your project root.

If you plan to distribute code on NPM, then you may want to use the :node-library target instead since it allows for a finer level of control over exports and optimization.

8.3. Working with Optimizations

Unlike the :node-library target, the module target does not know what you want to call the symbols you’re exporting, so it just exports them as-is. If you use advanced compilation, then everything will get a minified munged name!

This is easy to remedy, simply add :export metadata on any symbols that you want to preserve:

(def ^:export my-constant 5.662)
(defn ^:export my-function [] ...)

This is a standard annotation that is understood by ClojureScript and prevents Google Closure from renaming an artifact.

9. Testing

shadow-cljs provides a few ultility targets to make building tests a little easier.

All test targets generate a test runner and automatically add all namespaces matching the configurable :ns-regexp. The default test runners were built for cljs.test but you can create custom runners if you prefer to use other test frameworks.

The default :ns-regexp is "-test$", so your first test could look like:

File: src/test/demo/app_test.cljs
(ns demo.app-test
  (:require [cljs.test :refer (deftest is)]))

(deftest a-failing-test
  (is (= 1 2))

In the Clojure world it is common to keep test files in their own source paths so the above example assumes you have configured :source-paths ["src/main" "src/test"] in your shadow-cljs.edn config. Your usual app code goes into src/main and the tests go into src/test. This however is optional and it is totally fine to keep everything in src and just use :source-paths ["src"].

9.1. Testing in node.js

This target will create a test runner including all test namespaces matching the given regular expression.

The relevant configuration options are:

:target

:node-test

:output-to

The final output file that will be used to run tests.

:ns-regexp

(optional) A regular expression matching namespaces against project files. This only scans files, and will not scan jars. Defaults to "-test$".

:autorun

(boolean, optional) Run the tests via node when a build completes

Test config matching all *-spec namespaces
{...
 :builds
 {:test
  {:target    :node-test
   :output-to "out/node-tests.js"
   :ns-regexp "-spec$"
   :autorun   true}}}

The :node-test target only generates the test file. You can run it via node.

$ shadow-cljs compile test
# or
$ shadow-cljs release test

# run tests manually, :autorun will do this automatically
$ node out/node-tests.js

The node process exit code will be set to 0 when successful and 1 on any failures.

9.2. Testing in the Browser

This target is meant for gathering up namespaces that contain tests (based on a filename pattern match), and triggering a test runner. It contains a built-in runner that will automatically scan for cljs.test tests and run them.

The relevant configuration options are:

:target

:browser-test

:test-dir

A folder in which to output files. See below.

:ns-regexp

(optional) A regular expression matching namespaces against project files. This only scans files, and will not scan jars. Defaults to "-test$".

:runner-ns

(optional) A namespace that can contain a start, stop, and init function. Defaults to shadow.test.browser.

The normal :devtools options are supported, so you will usually create an http server to serve the files. In general you will need a config that looks like this:

{...
 :builds {:test     {:target    :browser-test
                     :test-dir  "resources/public/js/test"
                     :ns-regexp "-spec$"
                     :runner-ns tests.client-test-main
                     :devtools  {:http-port          8021
                                 :http-root          "resources/public/js/test"}}

Remember that the test directory will have the index.html, and a js folder.

If you choose to supply a custom :runner-ns, it might look like this:

(ns tests.client-test-main)

(defn start []
  ... run the tests...)

(defn stop [done]
  ; tests can be async. You must call done so that the runner knows you actually finished
  (done))

(defn ^:export init []
  (start))

It just has init, start, stop methods. init will be called once on startup, stop will be called before any code is reloaded and start will be called after all code was reloaded.

Tip
:runner-ns is optional, just leave it out to use the default.

9.2.1. Generated output in :test-dir

The output includes two primary artifacts in your test-dir folder:

  • index.html - If and only if there was not already an index.html file present. By default the generated file loads the tests and runs init in the :runner-ns. You may edit or add a custom version that will not be overwritten.

  • js/test.js - The Javascript tests. The tests will always have this name. The entries for the module are auto-generated.

9.3. Targeting Tests to Karma for Continuous Integration

When you want to run your CLJS tests against a browser on some kind of CI server you’ll need to be able to run the tests from a command line and get back a status code. Karma is a well-known and supported test runner that can do this for you, and shadow-cljs includes a target that can add the appropriate wrappers around your tests so they will work in it.

9.3.1. Installing Karma

See their website for full instructions. You’ll typically need something like this is your package.json:

{
  "name": "CITests",
  "version": "1.0.0",
  "description": "Testing",
  ...
  "devDependencies": {
    "karma": "^2.0.0",
    "karma-chrome-launcher": "^2.2.0",
    "karma-cljs-test": "^0.1.0",
    ...
  },
  "author": "",
  "license": "MIT"
}

So, you need Karma, a browser launcher, and the cljs-test integration.

9.3.2. The Build

The build options are:

:target

:karma

:output-to

A path/filename for the js file.

:ns-regexp

(optional) A regex to match the test namespaces, defaults to "-test$

So you might have something like this:

{...
 :builds
 {:ci
  {:target :karma
   :output-to  "target/ci.js"
   :ns-regexp  "-spec$"}}}

You also need a karma.conf.js:

module.exports = function (config) {
    config.set({
        browsers: ['ChromeHeadless'],
        // The directory where the output file lives
        basePath: 'target',
        // The file itself
        files: ['ci.js'],
        frameworks: ['cljs-test'],
        plugins: ['karma-cljs-test', 'karma-chrome-launcher'],
        colors: true,
        logLevel: config.LOG_INFO,
        client: {
            args: ["shadow.test.karma.init"],
            singleRun: true
        }
    })
};

then you can run the tests as follows (assuming you’ve installed global executables of the tools):

$ shadow-cljs compile ci
$ karma start --single-run
12 01 2018 01:19:24.222:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
12 01 2018 01:19:24.224:INFO [launcher]: Launching browser ChromeHeadless with unlimited concurrency
12 01 2018 01:19:24.231:INFO [launcher]: Starting browser ChromeHeadless
12 01 2018 01:19:24.478:INFO [HeadlessChrome 0.0.0 (Mac OS X 10.12.6)]: Connected on socket TcfrjxVKmx7xN6enAAAA with id 85554456
LOG: 'Testing boo.sample-spec'
HeadlessChrome 0.0.0 (Mac OS X 10.12.6): Executed 1 of 1 SUCCESS (0.007 secs / 0.002 secs)

10. Custom Targets

You might have guess that with so many targets the ecosystem probably allows easy extension. Creating custom targets just requires one function. This function receives one argument (the compiler state) and must return it after doing its work. This function is called once for each stage of the compilation.

The compiler provides three special keys from the shadow.cljs.devtools.compiler namespace:

shadow.build/config

The build config map from your shadow-cljs.edn file.

shadow.build/mode

Either :dev or :release

shadow.build/stage

A keyword. See below.

To use it, you simply specify the fully qualified name of the function as the :target for your build. In this case :target build/custom. The namespace is up to you.

10.1. Example

If your :source-path is ["src"] then create a src/build.clj:

(ns build
  (:require
    [shadow.build :as build]
    [shadow.build.targets.browser :as browser]))

(defn custom [{::build/keys [stage mode config] :as state}]
  (let [state (browser/process state)]
    (when (and (= :flush stage) (= :dev mode))
      (call-rsync))
    state))

This would call a call-rsync (undefined here, can do pretty much everything you want here) after every successful dev build.

The state is a clojure map representing the full shadow-build compiler state.

10.2. Compilation Stages

The stages are:

  • :init

  • :compile-prepare

  • :compile-finish

  • :optimize-prepare (:release only)

  • :optimize-finish (:release only)

  • :flush

When mode is :dev the :init is only called once. Any of the others may be called again (in order) when autobuild is active and files are re-compiled on change.

11. JavaScript Integration

11.1. NPM

npm has come the de-facto standard package manager for JavaScript. Almost all JS libraries can be found there and shadow-cljs provides seamless integration for accessing those packages.

11.1.1. Using npm packages

Most npm packages will also include some instructions on how to use the actual code. The “old” CommonJS style just has require calls which translate directly:

var react = require("react");
(ns my.app
  (:require ["react" :as react]))

Whatever "string" parameter is used when calling require we transfer to the :require as-is. The :as alias is up to you. Once we have that we can use the code like any other CLJS namespace!

(react/createElement "div" nil "hello world")

In shadow-cljs: always use the ns form and whatever :as alias you provided. You may also use :refer and :rename. This is different than what :foreign-libs/CLJSJS does where you include the thing in the namespace but then used a global js/Thing in your code.

Some packages just export a single function which you can call directly by using (:require ["thing" :as thing]) and then (thing).

More recently some packages started using ES6 import statements in their examples. Those also translate pretty much 1:1 with one slight difference related to default exports.

The following table can be used for translation:

Table 1. ES6 Import to CLJS Require
ES6 Import CLJS Require

import defaultExport from "module-name";

(:require ["module-name" :default defaultExport])

import * as name from "module-name";

(:require ["module-name" :as name])

import { export } from "module-name";

(:require ["module-name" :refer (export)])

import { export as alias } from "module-name";

(:require ["module-name" :rename {export alias}])

import { export1 , export2 } from "module-name";

(:require ["module-name" :refer (export1 export2)])

import { export1 , export2 as alias2 , […​] } from "module-name";

(:require ["module-name" :refer (export1) :rename {export2 alias2}])

import defaultExport, { export [ , […​] ] } from "module-name";

(:require ["module-name" :refer (export) :default defaultExport])

import defaultExport, * as name from "module-name";

(:require ["module-name" :as name :default defaultExport])

import "module-name";

(:require ["module-name"])

The :default option is currently only available in shadow-cljs, you can vote here to hopefully make it standard. You can always use :as alias and then call alias/default if you prefer to stay compatible with standard CLJS in the meantime.

Notice that previously we were stuck using bundled code which included a lot of code we didn’t actually need. Now we’re in a better situation: Some libraries are also packaged in ways ways that allow you to include only the parts you need, leading to much less code in your final build.

react-virtualized is a great example:

// You can import any component you want as a named export from 'react-virtualized', eg
import { Column, Table } from 'react-virtualized'

// But if you only use a few react-virtualized components,
// And you're concerned about increasing your application's bundle size,
// You can directly import only the components you need, like so:
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'
import List from 'react-virtualized/dist/commonjs/List'

With our improved support we we can easily translate this to:

(ns my-ns
  ;; all
  (:require ["react-virtualized" :refer (Column Table)])
  ;; OR one by one
  (:require ["react-virtualized/dist/commonjs/AutoSizer" :default virtual-auto-sizer]
            ["react-virtualized/dist/commonjs/List" :default virtual-list]))

11.1.2. Package Provider

shadow-cljs supports several different ways to include npm packages into your build. They are configurable via the :js-options :js-provider setting. Each :target usually sets the one appropriate for your build most often you won’t need to touch this setting.

Currently there are 3 supported JS Providers:

:require

Maps directly to the JS require("thing") function call. It is the default for all node.js targets since it can resolve require natively at runtime. The included JS is not processed in any way.

:shadow

Resolves the JS via node_modules and includes a minified version of each referenced file in the build. It is the default for the :browser target. node_modules sources do not go through :advanced compilation.

:closure

Resolves similarly to :shadow but attempts to process all included files via the Closure Compiler CommonJS/ES6 rewrite facilities. They will also be processed via :advanced compilation.

:shadow vs :closure

Ideally we want to use :closure as our primary JS Provider since that will run the entire application through :advanced giving us the most optimized output. In practice however lots of code available via npm is not compatible with the aggressive optimizations that :advanced compilation does. They either fail to compile at all or expose subtle bugs at runtime that are very hard to identify.

:shadow is sort of a stopgap solution that only processes code via :simple and achieves much more reliable support while still getting reasonably optimized code. The output is comparable (or often better) to what other tools like webpack generate.

Until support in Closure gets more reliable :shadow is the recommend JS Provider for :browser builds.

Example config for using :closure in a :browser build.
{...
 :builds
 {:app
  {:target :browser
   ...
   :js-options {:js-provider :closure}
   }}}

11.1.3. Resolving Packages

By default shadow-cljs will resolve all (:require ["thing" :as x]) requires following the npm convention. This means it will look at <project>/node_modules/thing/package.json and follow the code from there. To customize how this works shadow-cljs exposes a :resolve config option that lets you override how things are resolved.

Using a CDN

Say you already have React included in your page via a CDN. You could just start using js/React again but we stopped doing that for a good reason. Instead you can continue to use (:require ["react" :as react]) but configure how "react" resolves!

Here is a sample shadow-cljs.edn config for such a build:

{...
 :builds
 {:app
  {:target :browser
   ...
   :js-options
   {:resolve {"react" {:target :global
                       :global "React"}}}}

  :server
  {:target :node-script
   ...}}}

The :app build will now use the global React instance while the :server build continues using the "react" npm package! No need to fiddle with the code to make this work.

Redirecting “require”

Some packages provide multiple “dist” files and sometimes the default one described doesn’t quite work in shadow-cljs. One good example for this is "d3". Their default "main" points to "build/d3.node.js" but that is not what we want when working with the browser. Their ES6 code runs into a bug in the Closure Compiler, so we can’t use that. Instead we just redirect the require to some other file:

{...
 :builds
 {:app
  {:target :browser
   ...
   :js-options
   {:resolve {"d3" {:target :npm
                    :require "d3/build/d3.js"}}}

You can also just (:require ["d3/build/d3.js" :as d3]) as well if you only care about the Browser.

Limitations

The :shadow-js and :closure have full control over :resolve and everything mentioned above works without any downsides. The :js-provider :require however is more limited. Only the initial require can be influenced since the standard require is in control after that. This means it is not possible to influence what a package might require internally. It is therefore not recommended to be used with targets that use require directly (eg. :node-script).

Redirecting "react" to "preact"
{...
 :builds
 {:app
  {:target :node-script
   ...
   :js-options
   {:resolve {"react" {:target :npm
                       :require "preact"}}}
Example use of react-table
(ns my.app
  (:require
    ["react-table" :as rt]))

The above works fine in the Browser since every "react" require will be replaced, including the "react" require "react-table" has internally. For :js-provider :require however a require("react-table") will be emitted and node will be in control how that is resolved. Meaning that it will resolve it to the standard "react" and not the "preact" we had configured.

11.2. Dealing with .js Files

DANGER: This feature is an experiment! It is currently only supported in shadow-cljs and other CLJS tools will yell at you if you attempt to use it. Use at your own risk. The feature was initially rejected from CLJS core but I think it is useful and should not have been dismissed without further discussion.

CLJS has an alternate implementation which in turn is not supported by shadow-cljs. I found this implementation to be lacking in certain aspects so I opted for the different solution. Happy to discuss the pros/cons of both approaches though.

We covered how npm packages are used but you may be working on a codebase that already has lots of plain JavaScript and you don’t want to rewrite everything in ClojureScript just yet. shadow-cljs provides 100% full interop between JavaScript and ClojureScript. Which means your JS can use your CLJS and CLJS can use your JS.

There are only a few conventions you need to follow in order for this to work reliably but chances are that you are already doing that anyways.

11.2.1. The Classpath

shadow-cljs uses the Java Virtual Machine (JVM) and its "classpath" when resolving files. This is sort of a virtual filesystem composed of either filesystem directories or .jar files (which basically are just .zip files). This is slightly different from npm where you are always dealing with files.

In ClojureScript everything is namespaced and each name is expected to resolve to a file. If you have a (ns demo.app) namespace the compiler expects to find a demo/app.cljs (or .cljc) on the classpath. The classpath will be searched in order until it is found. Suppose you configured the :source-paths ["src/main" "src/test"] the compiler will first look in src/main/demo/app.cljs and then src/test/demo/app.cljs. When the file is not found the JVM will begin looking into the .jar files on the classpath. So if there is a demo/app.cljs at the root of that "zip" file it will be used instead.

Important
You can not escape this classpath, meaning that if you configured src/main you cannot get to src.

If multiple paths contain the same file only the "first" one is used. Everything in Java and Clojure(Script) is namespaced to avoid such conflicts. Very similar to npm where each package must have a unique name.

11.2.2. Requiring JS

We already covered how npm packages are accessed by their name but on the classpath we access .js files by either a full path or relative to the current namespace.

Loading JS from the classpath
(ns demo.app
  (:require
    ["/some-library/components/foo" :as foo]
    ["./bar" :as bar :refer (myComponent)]))
Tip
For string requires the extension .js will be added automatically but you can specify the extension if you prefer. Note that currently only .js is supported though.

Absolute requires like /some-library/components/foo mean that the compiler will look for a some-library/components/foo.js on the classpath; unlike node which would attempt to load the file from the local filesystem. The same classpath rules apply so the file may either be in your :source-paths or in some third-party .jar library you are using.

Relative requires are resolved by first looking at the current namespace and then resolving a relative path from that name. In the above example we are in demo/app.cljs to the ./bar require resolves to demo/bar.js, so it is identical to (:require ["/demo/bar"]).

Important
The files must not be physically located in the same directory. The lookup for the file appears on the classpath instead. This is unlike node which expects relative requires to always resolve to physical files.
Example File Structure with Separate Paths
.
├── package.json
├── shadow-cljs.edn
└── src
    └── main
        └── demo
            └── app.cljs
    └── js
        └── demo
            └── bar.js

11.2.3. Language Support

Important
It is expected that the classpath only contains JavaScript that can be consumed without any pre-processing by the Compiler. npm has a very similar convention.

The Closure Compiler is used for processing all JavaScript found on the classpath using its ECMASCRIPT_NEXT language setting. What exactly this setting means is not well documented but it mostly represents the next generation JavaScript code which might not even be supported by most browsers yet. ES6 is very well supported as well as most ES7 features. Similarly to standard CLJS this will be compiled down to ES5 with polyfills when required.

11.2.4. JavaScript Dialects

Since there are many popular JavaScript dialects (JSX, CoffeeScript, etc) that are not directly parsable by the Closure Compiler we need to pre-process them before putting them onto the classpath. babel is commonly used in the JavaScript world so we are going to use babel to process .jsx files as an example here.

Example shadow-cljs.edn Config
{:source-paths
 ["src/main"
  "src/gen"]
 ...}
Example File Structure
.
├── package.json
├── shadow-cljs.edn
└── src
    └── main
        └── demo
            └── app.cljs
    └── js
        ├── .babelrc
        └── demo
            └── bar.jsx
Important
Notice how src/js is not added to :source-paths which means it will not be on the classpath.
src/js/demo/bar.jsx
import React from "react";

function myComponent() {
  return <h1>JSX!</h1>;
}

export { myComponent };

We run babel to convert the files and write them to the configured src/gen directory. Which directory you use it up to you. I prefer src/gen for generated files.

$ babel src/js --out-dir src/gen
# or during development
$ babel src/js --out-dir src/gen --watch

babel itself is configured via the src/js/.babelrc. See the official example for JSX.

JSX minimal .babelrc
{
  "plugins": ["transform-react-jsx"]
}

Once babel writes the src/gen/demo/bar.js it will be available to use via ClojureScript and will even be hot loaded just like your ClojureScript sources.

Important
shadow-cljs currently does not provide any support for running those transformation steps. Please use the standard tools (eg. babel, coffeescript, etc.) directly until it does.

11.2.5. Access CLJS from JS

The JS sources can access all your ClojureScript (and the Closure Library) directly by importing their namespaces with a goog: prefix which the Compiler will rewrite to expose the namespace as the default ES6 export.

import cljs, { keyword } from "goog:cljs.core";

// construct {:foo "hello world"} in JS
cljs.array_map(keyword("foo"), "hello world");
Tip
The goog: prefix currently only works for ES6 file. require("goog:cljs.core") does not work.

11.3. Migrating cljsjs.*

CLJSJS is an effort to package Javascript libraries to be able to use them from within ClojureScript.

Since shadow-cljs can access npm packages directly we do not need to rely on re-packaged CLJSJS packages.

However many CLJS libraries are still using CLJSJS packages and they would break with shadow-cljs since it doesn’t support those anymore. It is however very easy to mimick those cljsjs namespaces since they are mostly build from npm packages anyways. It just requires one shim file that maps the cljsjs.thing back to its original npm package and exposes the expected global variable.

For React this requires a file like src/cljsjs/react.cljs:

(ns cljsjs.react
  (:require ["react" :as react]
            ["create-react-class" :as crc]))
(js/goog.object.set react "createClass" crc)
(js/goog.exportSymbol "React" react)

Since this would be tedious for everyone to do manually I created the shadow-cljsjs library which provides just that. It does not include every package but I’ll keep adding them and contributions are very welcome as well.

Note
The shadow-cljsjs library only provides the shim files. You’ll still need to npm install the actual packages yourself.

11.3.1. Why not use CLJSJS?

CLJSJS packages basically just take the package from npm and put them into a .jar and re-publish them via clojars. As a bonus they often bundle Externs. The compiler otherwise does nothing with these files and only prepends them to the generated output.

This was very useful when we had no access to npm directly but has certain issues since not all packages are easily combined with others. A package might rely on react but instead of expressing this via npm they bundle their own react. If you are not careful you could end up including 2 different react versions in your build which may lead to very confusing errors or at the very least increase the build size substantially.

Apart from that not every npm package is available via CLJSJS and keeping the package versions in sync requires manual work, which means packages are often out of date.

shadow-cljs does not support CLJSJS at all to avoid conflicts in your code. One library might attempt to use the "old" cljsjs.react while another uses the newer (:require ["react"]) directly. This would again lead to 2 versions of react on your page again.

So the only thing we are missing are the bundled Externs. In many instances these are not required due to improved externs inference. Often those Externs are generated using third-party tools which means they are not totally accurate anyways.

Conclusion: Use npm directly. Use :infer-externs auto.

12. Generating Production Code — All Targets

Development mode always outputs individual files for each namespace so that they can be hot loaded in isolation. When you’re ready to deploy code to a real server you want to run the Closure Compiler on it to generate a single minified result for each module.

By default the release mode output file should just be a drop-in replacements for the development mode file: there is no difference in the way you include them in your HTML. You may use filename hashing to improve caching characteristics on browser targets.

Generating Minified Output
$ shadow-cljs release build-id

12.1. Release Configuration

Usually you won’t need to add any extra configuration to create a release version for your build. The default config already captures everything necessary and should only require extra configuration if you want to override the defaults.

Each :target already provides good defaults optimized for each platform so you’ll have less to worry about.

12.1.1. Optimizations

You can choose the optimization level using the :compiler-options section of the configuration:

Important
You do not usually need to set :optimizations since the :target already sets it to an appropriate level.
Important
:optimizations only apply when using the release command. Development builds are never optimized by the Closure Compiler. Development builds are always set to :none.
{...
 :build
   {:build-id
     {...
      :compiler-options {:optimizations :simple}}}}

See the the Closure compiler’s documentation for more information on available optimization levels.

12.1.2. Release-Specific vs. Development Configuration

If you wish to have separate configuration values in a build when running a release build then you can override settings by including a :dev and/or :release section in the build section:

Example shadow-cljs.edn build config
{:source-paths ["src"]
 :dependencies []
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :asset-path "/js"
   :modules {:base {:entries [my.app.core]}}

   ;; Here is some dev-specific config
   :dev {:compiler-options {:devcards true}}

   ;; Here is some production config
   :release {:compiler-options {:optimizations :simple}}}}}

12.2. Externs

Since we want builds to be fully optimized by the Closure Compiler :advanced compilation we need to deal with Externs. Externs represent pieces of code that are not included when doing :advanced compilation. :advanced works by doing whole program optimizations but some code we just won’t be able to include so Externs inform the Compiler about this code. Without Externs the Compiler may rename or remove some code that it shouldn’t.

Typically all JS Dependencies are foreign and won’t be passed through :advanced and thus require Externs.

Tip
Externs are only required for :advanced, they are not required in :simple mode.

12.2.1. Externs Inference

To help deal with Externs the shadow-cljs compiler provides enhanced externs inference which can be enabled by setting :infer-externs :auto for your build.

Example Config
{...
 :builds
 {:app
  {:target :browser
   ...
   :compiler-options {:infer-externs :auto}
   }}}}

With :auto the compiler will perform additional checks at compile time for your files only. It won’t warn you about possible externs issues in library code. :all will enable it for everthing but be aware that you may get a lot of warnings.

When enabled you’ll get warnings whenever the Compiler cannot figure out whether you are working with JS or CLJS code.

Example Code
(defn wrap-baz [x]
  (.baz x))
Example Warning
------ WARNING #1 --------------------------------------------------------------
 File: ~/project/src/demo/thing.cljs:23:3
--------------------------------------------------------------------------------
  21 |
  22 | (defn wrap-baz [x]
  23 |   (.baz x))
---------^----------------------------------------------------------------------
 Cannot infer target type in expression (. x baz)
--------------------------------------------------------------------------------

In :advanced the compiler will be renaming .baz to something "shorter" and Externs inform the Compiler that this is an external property that should not be renamed.

shadow-cljs can generate the appropriate externs if you add a typehint to the object you are performing native interop on.

Type-hint to help externs generation
(defn wrap-baz [x]
  (.baz ^js x))

The ^js typehint will cause the compiler to generate proper externs and the warning will go away. The property is now safe from renaming.

Multiple interop calls
(defn wrap-baz [x]
  (.foo ^js x)
  (.baz ^js x))

It can get tedious to annotate every single interop call so you can annotate the variable binding itself. It will be used in the entire scope for this variable. Externs for both calls will still be generated.

Annotate x directly
(defn wrap-baz [^js x]
  (.foo x)
  (.baz x))
Important
Don’t annotate everything with ^js. Sometimes you may be doing interop on CLJS or ClosureJS objects. Those do not require externs. If you are certain you are working with a CLJS Object prefer using the ^clj hint. It is not the end of the world when using ^js incorrectly but it may affect some optimizations when a variable is not renamed when it could be.

Calls on globals do not require a typehint when using direct js/ calls.

No hint required, externs inferred automatically
(js/Some.Thing.coolFunction)

Calls on :require bindings are also inferred automatically.

No hint required for :as and :refer bindings
(ns my.app
  (:require ["react" :as react :refer (createElement)]))

(react/createElement "div" nil "hello world")
(createElement "div" nil "hello world")

12.2.2. Manual Externs

Some libraries provide Externs as separate .js files. You can include them into your build via the :externs compiler options.

Manual Externs Config
{...
 :builds
 {:app
  {:target :browser
   ...
   :compiler-options {:externs ["path/to/externs.js" ...]}
   }}}
Tip
The compiler looks for files relative to the project root first. It will also attempt to load them from the classpath if no file is found.

13. Editor Integration

Unfortunately most editors these days expect you to work with the Leiningen project.clj setup so you might need to use the Leiningen integration for them to work properly.

13.1. Cursive

Cursive does not currently support resolving dependencies via shadow-cljs.edn. You can run shadow-cljs pom to generate a pom.xml and import that using the IntelliJ.

Alternatively you can create a dummy project.clj or use the full Leiningen integration.

(defproject your/project "0.0.0"
  :dependencies
  [[thheller/shadow-cljs "2.0.150"]]

  :source-paths
  ["src"])

You can run npx shadow-cljs server inside the Terminal provided by IntelliJ and use Clojure REPL → Remote Run Configuration to connect to the provided nREPL server.

13.2. emacs / cider

The generic nREPL instructions should provide everything required but additional config might be required.

13.3. Proto REPL (Atom)

Proto REPL is mostly intended for Clojure development so most features do not work for ClojureScript. It is however possible to use it for simple evals.

You need to setup a couple of things to get it working.

1) Create a user.clj in on of your :source-paths.

 (ns user)

 (defn reset [])

The file must define the user/reset fn since Proto REPL will call that when connecting. If user/reset is not found it will call tools.namespace which destroys the running shadow-cljs server. We don’t want that. You could do something here but we don’t need to do anything for CLJS.

2) add [proto-repl "0.3.1"] to your :dependencies.

3) Configure a fixed nREPL port

4) Start shadow-cljs server or shadow-cljs watch your-build.

5) Run the Atom Command Proto Repl: Remote Nrepl Connection connect to localhost and the port you configured

6) Eval (shadow.cljs.devtools.api/watch :your-build) (if you used server in 4)

7) Eval (shadow.cljs.devtools.api/nrepl-select :your-build). The REPL connection is now in CLJS mode, meaning that everything you eval will be eval’d in JS. You can eval :repl/quit to get back to Clojure Mode. If you get [:no-worker :browser] you need to start the watch first.

8) Before you can eval CLJS you need to connect your client (eg. your Browser when building a :browser App).

9) Eval some JS, eg. (js/alert "foo"). If you get There is no connected JS runtime the client is not connected properly. Otherwise the Browser should show an alert.

13.4. Calva (VS Code)

(Only tested with browser targets so far. Probably works with other targets too.)

13.4.1. Dependencies

You need VS Code and install the Calva extension.

Since Calva uses nRepl and Cider you need to include this dependency in shadow-cljs.edn:

[cider/cider-nrepl "0.16.0"]

shadow-cljs will inject the required cider-nrepl middleware once it sees this dependency.

13.4.2. Connecting Calva to the REPLs

Once that is done start your shadow app. (Using whatever build instead of app.):

$ shadow-cljs watch app

Once the app is loaded in the browser, and you see JS runime connected in the terminal where you started the app, Calva can connect to its repl. Open the project in VS Code and Calva will by default try to auto connect and prompt you with a list of builds read from shadow-cljs.edn. Select the right one (:app in this example) and Calva’s Clojure and Clojurescript support is activated.

(If you already have the project open in VS Code when you start the app, issue the clojure4vscode: connect command.)

13.4.3. Features

Some of the things you can now do:

Intellisence and stuff
  • Peek at definitions on hover.

  • Get auto completion help.

  • Navigate to definitions (cmd-click on Mac, might be ctrl-click on Windows and Linux).

Evaluation of the file, forms and selection
  • Evaluate the file: ctrl+alt+v enter (This is done automatically one opening files.)

  • Evaluate inline: ctrl+alt+v e

  • Evaluate and replace them in the editor: ctrl+alt+v r

  • Pretty print evaluation resuls: ctrl+alt+v p

  • Send forms to the integrated terminal repls for evaluation: ctrl+alt+v alt+e

Run tests
  • Run namespace tests: ctrl+alt+v t

  • Run all tests: ctrl+alt+v shift+t (Really clunky in large projects so far.)

  • Rerun previously failing tests: ctrl+alt+v ctrl+t

  • Test failures are marked in the explorer and editors and listed in the Problem tab for easy access.

Terminal repls
  • Show repl terminal: ctrl+alt+v z

  • Switch namespace in terminal repl to that of the currently open file: ctrl+alt+v n

  • Load current file and switch namespace in: ctrl+alt+v alt+n

Cljc files
  • Switch between Clojure and Clojurescript repl ctrl+alt+v alt+c (or click the green cljc/clj button in the status bar). This determines both which repl is backing the editor and what terminal repl is being accessed, see above.

14. What to do when things don’t work?

Since the JS world is still evolving rapidly and not everyone is using the same way to write and distribute code there are some things shadow-cljs cannot work around automatically. These can usually be solved with custom :resolve configs, but there may also be bugs or oversights.

If you cannot resolve such an issue with the instructions in this chapter, then try asking on the #shadow-cljs Slack channel.

15. Hacking

15.1. Patching Libraries

The shadow-cljs compiler ensures that things on your source paths are compiled first, overriding files from JARs. This means that you can copy a source file from a library, patch it, and include it in your own source directory.

This is a convenient way to test out fixes (even to shadow-cljs itself!) without having to clone that project and understand its setup, build, etc.