Compile Configuration
Figwheel handles setting up several compilation options for you, but it’s very important to understand these options, and how Figwheel uses them to inject itself into a build. Having this knowledge will empower you to understand how your build is working and how to adjust the options so that they serve you better.
Here are the compiler options we’ll cover:
--print-config
:output-to
:output-dir
:target
:optimizations
:main
:asset-path
:preloads
:closure-defines
The --print-config
option
You can use the figwheel.main
CLI option --print-config
(or -pc
)
to examine the compile options that Figwheel is sending to the
ClojureScript compiler. The --print-config
CLI flag will only print
out the configuration, it will not run the command.
It’s important to note that order matters for
figwheel.main
CLI options. The-pc
flag is considered an init option and as such it must always come before a main option (-c
,-b
,-r
,-m
are all main options). In the commands that we’ve been using so far in this documentation, this means it has to come before the-b
option.
Keeping with our hello-world.core
example let’s print out the
configuration.
$ clojure -m figwheel.main -pc -b dev -r
The above command should print out something like the following:
[Figwheel] :pprint-config true - printing config:
---------------------- Figwheel options ----------------------
{:pprint-config true, :mode :repl, :watch-dirs ("src")}
---------------------- Compiler options ----------------------
{:main hello-world.core,
:output-to "target/public/cljs-out/dev-main.js",
:output-dir "target/public/cljs-out/dev",
:asset-path "cljs-out/dev",
:preloads
[figwheel.core
figwheel.main
figwheel.repl.preload
devtools.preload
figwheel.main.evalback],
:aot-cache false,
:closure-defines
{figwheel.repl/connect-url
"ws://localhost:9500/figwheel-connect?fwprocess=b96800&fwbuild=dev"},
:repl-requires
([figwheel.repl :refer-macros [conns focus]]
[figwheel.main
:refer-macros
[stop-builds start-builds build-once reset clean status]])}
As you can see there are two sections; one displaying the current config options for Figwheel and one displaying the final compile options to be sent to the ClojureScript compiler.
Looking at the compiler options you will see that while we only
supplied {:main hello-world.core}
in our dev.cljs.edn
, Figwheel
added a few more options. We will focus on explaining these options.
The :output-to
option
The :output-to
option is “the path to the JavaScript file that will
be output”. This is simple enough. You provide the :output-to
option a path
to a file where the compiled ClojureScript should be output. This file
will always represent your main compiled artifact.
However, the contents of the file and how we load this file will vary
based on the :optimizations
, :main
, and :target
options.
The most important thing to remember when working with the Figwheel
server is that the :output-to
option needs to point to a path that
is basically the classpath + public
.
The Figwheel default for this path is in the target/public
directory
because we want to separate files that are compiled (and thus
temporary) from files that we edit and keep in version control like
HTML and CSS.
The Figwheel default as mentioned before uses the build name and looks like
target/public/cljs-out/[build-name]-main.js
If you don’t use a build file then the :output-to
path may look like
target/public/cljs-out/main.js
This can lead to problems if you are using different command line options to obtain different compilation results.
The :output-dir
option
The :output-dir
sets the output directory for temporary files used
during compilation.
Regardless of the :optimizations
level, the compiler will output a
file for each ClojureScript “namespace” in your project and all of its
dependencies, as well as the Google Closure libraries and foreign
libraries that are utilized.
If you have a hello-world.core
example you can examine the
:output-dir
after a compile and see the temporary files:
target/public/cljs-out/dev
├── cljs
│ ├── core.cljs
│ ├── core.js
│ ├── core.js.map
│ ├── pprint.cljs
│ ├── pprint.cljs.cache.json
│ ├── pprint.js
│ ├── pprint.js.map
│ ├── stacktrace.cljc
│ ├── stacktrace.cljc.cache.json
│ ├── stacktrace.js
│ ├── stacktrace.js.map
... a lot more
These files also serve as a cache for the compiler which enables incremental compiles. If your source file has a timestamp newer than the file in the output directory, then the compiler will compile the source file. Caches can get stale however and you will want to delete your output directory on a regular basis.
When no :output-dir
is defined, Figwheel will provide a default
:output-dir
which will have the form:
target/public/cljs-out/[build-name]
For example the default :output-dir
for our dev.cljs.edn
build
file will be target/public/cljs-out/dev
.
You may notice that the :output-dir
path is placed so that its
contents are available via the classpath. The reason for this is
because when we use the default :optimizations
level :none
many of
these “temporary files” are directly loaded into our client
environment. For example, in a web environment when we load our
:output-to
file, the Google Closure base code will calculate the
files that need loading and then load them in order. Hence the browser
needs to serve these files and this is why they need to be available on
the classpath.
So if you choose to customize the :output-dir
path keep in mind that
they will need to be found by the client environment if you are using
:optimizations :none
.
But … you should never put the :output-dir
directly on the
classpath. This means if target
is in the classpath, you should
never set :output-dir
to the target
directory. If you do this you
are making your compiled artifacts resolvable by the CLJS
compiler. This means that when the CLJS compiler looks for a required
CLJS file it will possibly find it in the target
directory and this
will cause major problems.
The :optimizations
option
The :optimizations
compiler option designates the optimizations
level that the Google Closure compiler should use. The available
optimization levels are:
:none
- the default level, no code optimizations:whitespace
- basically just removes whitespace and comments:simple
- removes whitespace and shortens local variable names:advanced
- more aggressive renaming, dead code removal, global inlining
The Figwheel --build
option and the ClojureScript REPL both require
:optimizations
to be set to :none
. :none
is the default so it
does not need to be specified in the configuration options map.
As we noted before the :none
level does not produce a single
self-contained compiled artifact, but rather creates an artifact that
loads all of the separately compiled namespaces.
All the other levels (:whitespace
, :simple
, and :advanced
)
produce a single compiled artifact to the :output-to
file. These are
often used for producing your final deployable asset when you use
these optimizations settings.
The :advanced
level provides absolutely amazing compression and
optimization but it is also the most finicky. This
guide can be helpful.
The :main
option
The :main
option specifies an entry point (root namespace) for your
compiled artifact. When :optimizations
is :none
the :main
option
will cause the compiler to generate a file that will in turn load all
the files that are needed by the :main
namespace. Actually, it does
more than this. It adds all the :closure-defines
that we have
specified in our configuration, and requires all of the :preloads
.
The :main
option bootstraps your client environment and as such it
behaves differently depending on the :target
option.
Figwheel does not have a default for the :main
option and requires
that you provide a namespace value.
You can provide a symbol or a string, and it needs to be a namespace that is available in your source path directories that are on the classpath.
The example we have been using in this documentation has been:
:main hello-world.core
The :asset-path
option
This only affects the build if you are using the :main
option with
:optimizations :none
. The generated bootstrap script in the
:output-to
file needs to know the path to your temporary files in
:output-dir
. This path is relative to your webroot.
As you can see above in the compile options for our example
hello-world.core
project our :asset-path
is this:
:asset-path "cljs-out/dev"
Basically you can think of the :asset-path
as your :output-dir
minus your webroot directory.
So in our case:
"target/public/cljs-out/dev" - "target/public/" => "cljs-out/dev"
If your application can’t seem to find the files it needs to load, it
normally means you have a misconfigured :asset-path
.
The :target
option
There are three values for the :target
option. Actually there are two
values and the absence of a :target
option. If you don’t specify a
:target
then it will be assumed that our client environment is the
Browser.
The other valid targets are :nodejs
and :webworker
.
The :target
option will change the output of the :main
bootstrap
script which gets output to the :ouput-to
file. The script will
handle the environmental needs of loading your ClojureScript code for
that particular environment.
The :target
option will also change the output code based on what
the environment needs.
Figwheel does not add or change the :target
option. It will respond
to it and ensure that it starts a Node
backed REPL if it needs to.
The :preloads
option
When we load a ClojureScript application we start at the :main
namespace to find all the files that need to be loaded. Sometimes we
want to inject functionality (like REPL support) into our
applications.
The :preloads
option allows you to inject namespaces into your
runtime environment.
Figwheel takes advantage of this to inject its code into your
environment. You can see this in the hello-world.core
example
project above. Figwheel appended several :preloads
to the
configuration:
:preloads
[figwheel.core
figwheel.main
figwheel.repl.preload
devtools.preload
figwheel.main.evalback]
All of these namespaces add additional functionality to your
application. Notice that this is how we add devtools
and the
figwheel.repl
connection to your build.
You can add pre-loads as well to add behavior to your development or production builds.
The :closure-defines
option
The :closure-defines
option is especially powerful. It allows us to
define namespaced constants with goog-define
and configure their
values in the :closure-defines
map.
Let’s say we want to have DEBUG
and LOCALE
variables that we set at
compile time.
(ns hello-world.core)
(goog-define DEBUG false)
(goog-define LOCALE "en")
Important:
goog-define
only works with String, Booolean, and Number values.
So we’ve defined these constants along with their default values. We
can override these default values in our configuration with the
:closure-defines
option like so:
:closure-defines {hello-world.core/DEBUG true
hello-world.core/LOCALE "fr"}
Figwheel uses :closure-defines
to supply the connection URL to the
figwheel.repl
connection code.
You can see in our example above that its value is:
:closure-defines
{figwheel.repl/connect-url
"ws://localhost:9500/figwheel-connect?fwprocess=b96800&fwbuild=dev"}