-
-
Notifications
You must be signed in to change notification settings - Fork 208
Quick Start
This is a quick introduction to Figwheel. It is intended to provide the reader with enough information to use Figwheel productively.
The aim of this document is empowerment, not bewilderment.
This Quick Start fills the same role as the ClojureScript Quick Start. Please see that Quick Start for a solid introduction to how to use the ClojureScript compiler.
If you are brand new to ClojureScript it is highly recommended that you do the ClojureScript Quick Start first. If you skip this you will probably suffer.
Leiningen is a terrific build tool for Clojure. To use this tutorial you need to have leiningen installed on your machine. Please visit the leiningen home page to learn how to get lein
installed and running.
Create a directory to work in called hello_seymore
. Change into that directory and create a ClojureScript source file, a project file and an index.html.
mkdir hello_seymore
cd hello_seymore
touch project.clj
touch index.html
mkdir -p src/hello_seymore
touch src/hello_seymore/core.cljs
Edit src/hello_seymore/core.cljs
to look like:
(ns hello-seymore.core)
(.log js/console "Hey Seymore sup?!")
Now you will need edit the project.clj
as follows:
(defproject hello-seymore "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/clojurescript "1.10.914"]]
:plugins [[lein-figwheel "0.5.20"]]
:clean-targets [:target-path "out"]
:cljsbuild {
:builds [{:id "dev"
:source-paths ["src"]
:figwheel true
:compiler {:main "hello-seymore.core"}
}]
})
At this point make sure you are in the project root directory hello_seymore
and run Figwheel as follows:
lein figwheel
You should see a bunch of clojure libraries get downloaded and installed into your local maven repository (which happens to be in ~/.m2
on my Mac).
After that Figwheel will start up, compile your hello-seymore.core
library and try to start a repl, BUT THE REPL WILL NOT START. Sorry for the caps, but this behavior is expected.
Type Ctrl-C to quit the Figwheel process.
If you list your project directory you should see this:
$ ls
figwheel_server.log
index.html
main.js
out
project.clj
src
target
Some new files have been created. The main.js
file and the out
directory contain your compiled ClojureScript code.
If you look at the contents of main.js
you will see:
if(typeof goog == "undefined") document.write('<script src="out/goog/base.js"></script>');
document.write('<script src="out/cljs_deps.js"></script>');
document.write('<script>if (typeof goog != "undefined") { goog.require("hello_seymore.core"); } else { console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?"); };</script>');
document.write("<script>if (typeof goog != \"undefined\") { goog.require(\"figwheel.connect\"); }</script>");
The last line of main.js
loads the code that will connect to the Figwheel server. This is what enables the Figwheel server to communicate with the application, which is running in the browser.
In order to run the hello Seymore program, we will need an HTML file to load the compiled program into in the browser.
Edit the index.html
file to look like this:
<!DOCTYPE html>
<html>
<head></head>
<body>
<script src="main.js" type="text/javascript"></script>
</body>
</html>
Now run Figwheel again:
$ lein figwheel
and load the index.html
in the browser from the filesystem. The location bar in your browser should have a file://<...>/hello_seymore/index.html
url in it.
Change back to the terminal where Figwheel is starting and when it finishes you should see a REPL prompt.
Go ahead and type some ClojureScript at the REPL prompt:
=> (+ 1 2 3)
6
If you get 6
as a response then you have successfully set up Figwheel!!!
You can also see that the REPL is connected to the browser:
=> (js/alert "Am I connected to Figwheel?")
nil
You should see the alert in the browser window. Only after you click on "Ok" there, will the REPL return nil
.
Also, go ahead and open up the browser's dev tools so you can see the messages from Figwheel. You should see something like this (in Chrome):
Now go back to your src/hello_seymore/core.cljs
file and change the line that looks like:
(.log js/console "Hey Seymore sup?!")
to
(.log js/console "Hey Seymore! wts goin' on?")
and save the file. You should now see Hey Seymore! wts goin' on?
printed in the dev console of your browser.
Congratulations!! You have set up Figwheel and your code is getting loaded into the browser as you save it.
As you can see, side-effects from print functions happen on every reload. This might not be desirable for all side-effects. Code that only triggers desired side-effects is called "reloadable code". We will discuss how to write such code later. Please remember that you are responsible for writing reloadable code! :)
When working with Figwheel you should really have a separate terminal open to watch the
figwheel_server.log
. This can be immensely helpful when things aren't working correctly.So open another terminal and type:
$ tail -f figwheel_server.log
Let's create a simple program for demonstration purposes. This is going to be a bare bones program that uses the sablono ClojureScript interface to React.js.
First add
[cljsjs/react "15.2.1-1"]
[cljsjs/react-dom "15.2.1-1"]
[sablono "0.7.4"]
to the :dependencies
list in your project.clj
.
You will need to restart Figwheel to pick up the new dependency.
Whenever you add new dependencies and restart Figwheel, also run
lein clean
. It will reduce the chances of working with old code
Add a place for us to mount our app in your index.html
.
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="app"></div> <!-- add this line -->
<script src="main.js" type="text/javascript"></script>
</body>
</html>
Now, edit your src/hello_seymore/core.cljs
file:
(ns hello-seymore.core
(:require [sablono.core :as sab]))
(def app-state (atom { :likes 0 }))
(defn like-seymore [data]
(sab/html [:div
[:h1 "Seymore's quantified popularity: " (:likes @data)]
[:div [:a {:href "#"
:onClick #(swap! data update-in [:likes] inc)}
"Thumbs up"]]]))
(defn render! []
(.render js/ReactDOM
(like-seymore app-state)
(.getElementById js/document "app")))
(add-watch app-state :on-change (fn [_ _ _ _] (render!)))
(render!)
Since Figwheel is running, once you save this file you should see the application running in the browser.
Let's talk about the reloadability of this tiny React app.
First, try the program out by clicking the Thumbs up link several times. You will see Seymore's popularity increase. Popularity should really be measured by how many times people are willing to click a thumbs up eh?
Now, if you change the source files by simply adding a blank line and saving it (something I actually do pretty often), the code will reload and you will see the popularity count go to zero. This is because we are redefining the app-state
atom on every code load. This is not what we want. You can fix this by using defonce
instead of def
on app-state
as follows:
(defonce app-state (atom {:likes 0}))
After you make this change you will see that the state of the program will persist through reloads.
Something else to notice is that the call to add-watch
is getting called over and over again on reload. But this is OK because it is just replacing the current :on-change
listener, making this top level side-effect reloadable.
Also, you will notice that we have a top level call to render!
at the end of the file. This forces a re-render every time the file is loaded. This is helpful so that we will render app changes as we edit the file.
Remove the like-seymore
function from the core.cljs
file and add it to its own file src/hello_seymore/components.cljs
.
Updated core.cljs
:
(ns hello-seymore.core
(:require [sablono.core :as sab]
[hello-seymore.components :refer [like-seymore]]))
(defonce app-state (atom { :likes 0 }))
(defn render! []
(.render js/ReactDOM
(like-seymore app-state)
(.getElementById js/document "app")))
(add-watch app-state :on-change (fn [_ _ _ _] (render!)))
(render!)
New src/hello_seymore/components.cljs
:
(ns hello-seymore.components
(:require [sablono.core :as sab]))
(defn like-seymore [data]
(sab/html [:div
[:h1 "Seymore's quantified popularity: " (:likes @data)]
[:div [:a {:href "#"
:onClick #(swap! data update-in [:likes] inc)}
"Thumbs up"]]]))
As long as Figwheel was running this whole time your refactor should have been live loaded into the browser. After reflecting on the coolness of this, go ahead and edit the components.cljs
file and remove the word "quantified" and save it. You will see your changes show up as expected.
Figwheel will autoreload css as well. You will need to add some server level configuration to get this feature.
First create a CSS file css/style.css
.
body {
background-color: yellow;
}
Edit the project.clj
(defproject hello-seymore "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/clojurescript "1.10.914"]
[cljsjs/react "15.2.1-1"]
[cljsjs/react-dom "15.2.1-1"]
[sablono "0.7.4"]]
:plugins [[lein-figwheel "0.5.20"]]
:clean-targets [:target-path "out"]
:cljsbuild {
:builds [{:id "dev"
:source-paths ["src"]
:figwheel true
:compiler {:main hello-seymore.core }
}]
}
:figwheel { ;; <-- add server level config here
:css-dirs ["css"]
}
)
And of course don't forget to add a link to your css in the index.html
:
<!DOCTYPE html>
<html>
<head>
<!-- add link here -->
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app"></div>
<script src="main.js" type="text/javascript"></script>
</body>
</html>
Now restart Figwheel, reload your index.html
into the browser, and edit the style.css
:
body {
background-color: green;
}
Your page in the browser should now be green and not yellow. Without reloading the page!
You may not want to have your code auto-reloaded but still want to benefit from Figwheel's auto-building and its REPL. All you have to do is add :autoload false
to the :figwheel
entry in your build config.
In this case your build config would look like this:
(defproject hello-seymore "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/clojurescript "1.10.914"]
[cljsjs/react "15.2.1-1"]
[cljsjs/react-dom "15.2.1-1"]
[sablono "0.7.4"]]
:plugins [[lein-figwheel "0.5.20"]]
:clean-targets [:target-path "out"]
:cljsbuild {
:builds [{:id "dev"
:source-paths ["src"]
:figwheel { :autoload false } ;; <<- add this
:compiler {:main hello-seymore.core }
}]
})
It is recommended that if you start needing a server to serve your ClojureScript assets that you just bite the bullet and write your own. But for convenience Figwheel has a built-in webserver. In order to use it you will have to place your assets in resources/public
.
Following the example we are using you would need to make these changes:
Move your assets into resources/public
:
mkdir -p resources/public
$ mv index.html css resources/public
Now you need to change your build config to output your compiled ClojureScript to resources/public
as well. For good measure we will place them in a cljs
directory.
(defproject hello-seymore "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/clojurescript "1.10.914"]
[cljsjs/react "15.2.1-1"]
[cljsjs/react-dom "15.2.1-1"]
[sablono "0.7.4"]]
:plugins [[lein-figwheel "0.5.20"]]
:clean-targets ^{:protect false} [:target-path "out" "resources/public/cljs"] ;; Add "resources/public/cljs"
:cljsbuild {
:builds [{:id "dev"
:source-paths ["src"]
:figwheel true
:compiler {:main hello-seymore.core
;; add the following
:asset-path "cljs/out"
:output-to "resources/public/cljs/main.js"
:output-dir "resources/public/cljs/out"}
}]
}
:figwheel {
:css-dirs ["resources/public/css"] ;; <-- Add "resources/public/"
}
)
So now we have changed where the compiler is placing the compiled assets. It's very important to get :asset-path
correct otherwise main.js
will have code that doesn't point to your compiled assets and nothing will work. It is also important to add our new target path to the :clean-targets
, otherwise lein clean
won't work.
Since we changed the location of main.js
, we need to reflect that in index.html
:
<!DOCTYPE html>
<html>
<head>
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app"></div>
<script src="cljs/main.js" type="text/javascript"></script> ;; <-- changed to "cljs/main.js"
</body>
</html>
Now you can restart Figwheel.
You can now find your application at http://localhost:3449/index.html
Since all code evaluated in the REPL actually runs on the browser, we have some powerful tools at our disposal. For example, we can take our app-state
to places our UI interface can't easily reach to test for edge cases. Imagine that there might be a problem when the counter displays a 5 digit number after a number of things happened. Would you do those things and then click 10000 times on "Thumbs up"?
You'll get a much better REPL experience if you install rlwrap and run
rlwrap lein figwheel
you can install
rlwrap
on OSX withbrew install rlwrap
Start the REPL and go to the hello-seymore.core
namespace
> (in-ns 'hello-seymore.core)
nil
Then change app-state
to whatever number you want to test and check it on the browser:
> (reset! app-state {:likes 10000})
{:likes 10000}