A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://shadow-cljs.github.io/docs/UsersGuide.html below:

Shadow CLJS User’s Guide

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.

6.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.

6.2. Development Options

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

6.2.1. REPL

When running watch code for the REPL is injected automatically and usually does not require additional configuration. Additional options are available to control REPL behavior:

{...
 :builds
 {:app {...
        :devtools {:repl-init-ns my.app
                   :repl-pprint true
                   ...}}}}
6.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 either in the :devtools/:preloads section of shadow-cljs.edn or within the :preloads key of a specific module:

{...
 :builds
 {:app {...
        :devtools {:preloads [fulcro.inspect.preload]
                   ...}}}}

For example to only include the preloads within a main module during development, and not in a web worker:

{...
 :builds
 {:app {...
        :modules {:main {...
                         :preloads
                         [com.fulcrologic.fulcro.inspect.preload
                          com.fulcrologic.fulcro.inspect.dom-picker-preload]
                         :depends-on #{:shared}}
                  :shared {:entries []}
                  :web-worker {...
                    :depends-on #{:shared}
                    :web-worker true}}}}}

:preloads are only applied to development builds and will not be applied to release builds.

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.) If you don’t want to have cljs-devtools in specific targets, you can suppress this by adding :console-support false to the :devtools section of those targets. 6.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
Hot Reload of Transitive Dependents

By default, compiled files and files explicitly requiring those are reloaded. This approach may not be sufficient eg. during development for :react-native target. To reload also all transitive dependents, use :reload-strategy option with value :full as follows:

Important

This may become slow for larger apps, only use it if you really need it.
{...
 :builds
 {:app
  {:target :react-native
   :init-fn some.app/init
   :output-dir "app"
   ...
   :devtools
   {:reload-strategy :full}}}}
6.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.

A non-reloadable ns

(ns ^:dev/once my.thing)

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

Namespaces can also be tagged to always reload.

An always-reloadable ns

(ns ^:dev/always my.thing)

(js/console.warn "will execute on every code change")
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 be 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
                   ...}}}}

Important

Hooks cannot be declared in the cljs.user namespace. Hooks are only used if the namespace containing them is actually included in the build. If you use an extra namespace make sure to include it via :preloads.

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. 6.3. Build Hooks

It is sometimes desirable to execute some custom code at a specific stage in the compilation pipeline. :build-hooks let you declare which functions should be called and they have full access to the build state at that time. This is quite powerful and opens up many possible tool options.

They are configured per build under the :build-hooks key

Exampe :build-hooks

{...
 :builds
 {:app {:target ...
        :build-hooks
        [(my.util/hook 1 2 3)]
        ...}}}}

Example hook code

(ns my.util)

(defn hook
  {:shadow.build/stage :flush}
  [build-state & args]
  (prn [:hello-world args])
  build-state)

This example would call (my.util/hook build-state 1 2 3) after the build completed the :flush stage (ie. written to disk). The example would print [:hello-world (1 2 3)] but please do something more useful in actual hooks.

The hook is a just a normal Clojure function with some additional metadata. The {:shadow.build/stage :flush} metadata informs the compiler to call this hook for :flush only. You may instead configure {:shadow.build/stages #{:configure :flush}} if the hook should be called after multiple stages. At least one configured stage is required since the hook otherwise would never do anything.

All build hooks will be called after the :target work is done. They will receive the build-state (a clojure map with all the current build data) as their first argument and must return this build-state modified or unmodified. When using multiple stages you can add additional data to the build-state that later stages can see. It is strongly advised to use namespaced keys only to ensure not accidentally breaking the entire build.

The build-state has some important entries which might be useful for your hooks:

Important

With a running watch all hooks will be called repeatedly for each build. Avoid doing too much work as they can considerably impact your build performance. 6.3.1. Compilation Stages

The possible stages the :build-hooks can use are:

With a running watch the :configure is only called once. Any of the others may be called again (in order) for each re-compile. The build-state will be re-used until the build config changes at which point it will be thrown away and a fresh one will be created.

6.4. 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 ...}


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

6.5. 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]}}
   
   :dev {:closure-defines {your.app/VERBOSE true}}
   
   :closure-defines {your.app/VERBOSE true}
   
   :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

6.6. 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

Unsupported or non-applicable Options

Options that don’t have any effect at all include

6.6.1. Warnings as Errors

It is sometimes desireable to fail a build with warnings rather than continuing with the build (eg. in CI envs). You can use the :warnings-as-errors compiler options to customize how that is handled.

Treat all warnings as errors

{...
 :builds
 {:app
  {...
   :compiler-options {:warnings-as-errors true}}}}

Only throw certain warnings

{...
 :builds
 {:app
  {...
   :compiler-options {:warnings-as-errors #{:undeclared-var}}}}

A set of possible warning-type keywords can be found here.

Only throw for certain namespaces

{...
 :builds
 {:app
  {...
   :compiler-options {:warnings-as-errors {:ignore #{some.ns some.library.*}
                                           :warnings-types #{:undeclared-var}}}

:ignore takes a set of symbols refering to namespaces. Either direct matches or .* wildcards are allowed. :warning-types has the same functionality as above, not specifying it means all warnings will throw except the ignored namespaces.

6.7. Output Language Options

By default the generated JS output will be compatible with ES6 and all "newer" features will be transpiled to compatible code using polyfills. This is currently the safest default and supports most browsers in active use (including IE10+).

You can select other output options if you only care about more modern environments and want to keep the original code without replacements (eg. node, Chrome Extensions, …​)

Important

Note that this mostly affects imported JS code from npm or .js files from the classpath. CLJS will currently only generate ES5 output and is not affected by setting higher options.

You can configure this via the :output-feature-set in :compiler-options. The older :language-out option should not be used as :output-feature-set replaced it.

Supported options are:

Example

{...
 :builds
 {:script
  {:target :node-script
   :main foo.bar/main
   ...
   :compiler-options {:output-feature-set :es7}}}}

Documentation on these options is a bit sparse and is mostly documented in the code here.

6.8. 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]))


(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]
   
   
   
   #?@(:node [[]]
       :cljs [["codemirror" :as CodeMirror]])))

#?(:node 
   (defn init-cm [dom-node]
    :no-op)
   :cljs 
   (defn init-cm [dom-node]
     ... actual impl ...))

...

:reader-features config examples

{...
 :builds
 
 {:app
  {:target :browser
   ...}
  
  
  :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]))




(defn init-cm [dom-node] :no-op)
...

Important

This feature is only available in .cljc files and will fail in .cljs files. 6.9. Overriding from the CLI

It is sometimes desirable to make small adjustments to the build configuration from the command line with values that can’t be added statically to the shadow-cljs.edn config or may change depending on the environment you are in.

You can pass additional config data via the --config-merge {:some "data"} command line option which will be merged into the build config. Data added from the CLI will override data from the shadow-cljs.edn file.

Example shadow-cljs.edn config

{...
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   ...}}}

Overriding the :output-dir from the CLI

$ shadow-cljs release app --config-merge '{:output-dir "somewhere/else"}'

Overriding the :closure-defines from the CLI

$ shadow-cljs release app --config-merge '{:closure-defines {your.app/DEBUG true}}'

--config-merge expects one EDN map and can be used multiple times, they will be merged left to right. The data added is also visible to build-hooks. It will also accept a file path like --config-merge a/path.edn or --config-merge classpath:a/resource.edn.

Important

If you specify multiple build ids the data will be merged into all specified builds. shadow-cljs release frontend backend --config-merge '{:hello "world"}' will be applied to both. 6.10. Using Environment Variables

It is possible to use environment variables to set configuration values in shadow-cljs.edn but you should consider using --config-merge instead. If you really must use an environment variable you can do so via the #shadow/env "FOO" reader tag. You can also use the shorter #env.

Example shadow-cljs.edn config

{...
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :closure-defines {your.app/URL #shadow/env "APP_URL"}
   ...}}}

The are also a few more supported forms that you can use #shadow/env with.

#shadow/env "APP_URL"
#shadow/env ["APP_URL"]

#shadow/env ["APP_URL" "default-value"]
#shadow/env ["APP_URL" :default "default-value"]

#shadow/env ["PORT" :as :int :default 8080]

Supported :as coercions are :int, :bool, :keyword, :symbol. Supplied :default values will not be converted and are expected to be in the correct type already.

Important

The environment variables used when the shadow-cljs process was started are used. If a server process is used its environment variables will be used over those potentially set by other commands. This is mostly relevant during development but may be confusing. --config-merge does not have this limitation. 6.11. Build and Target defaults

It is possible to use set defaults that will be used for all builds, or for all targets of a certain type.

Configuration merge order is as follows :build-defaults:target-defaults → actual build config → extra config overrides.

Example shadow-cljs.edn config

{...
 :build-defaults
 {:closure-defines
   {your.app/VERBOSE true}}

 :target-defaults
 {:browser
   {:js-options
     {:resolve {"react" {:target :global
                         :global "React"}}}}}

 :builds
 {:app
  {:target :browser
   ...}}}

In this example the :app target will inherit both :build-defaults and the :target-defaults for :browser.

Important

Configs later in the merge order can override, but not remove previous configuration items. Once a default is set, the only way to remove it is by overriding it.

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4