├── LICENSE
├── README.md
├── env
├── dev
│ └── cljs
│ │ └── reddit_viewer
│ │ └── dev.cljs
└── prod
│ └── cljs
│ └── reddit_viewer
│ └── prod.cljs
├── project.clj
├── public
└── index.html
└── src
└── reddit_viewer
├── chart.cljs
├── core.cljs
└── sample_data.cljs
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 reagent-project
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [JS-Workshop](https://github.com/ClojureTO/JS-Workshop)
2 |
3 | # Part 1: Reagent
4 |
5 | ## Before the Workshop
6 |
7 | Please make sure that you have a copy of the JDK and Leiningen build tool setup to follow along with the workshop.
8 | You can follow installation instructions in the links below:
9 |
10 | * [JDK 1.8+](http://www.azul.com/downloads/zulu/)
11 | * [Leiningen](https://leiningen.org/)
12 |
13 | To compile our ClojureScript code we will use [figwheel](https://figwheel.org/). If you want to use [shadow-cljs](https://github.com/thheller/shadow-cljs) you should follow along with [this](https://github.com/ClojureTO/JS-Workshop/tree/shadow-cljs) version of the workshop.
14 |
15 | ### Creating and running the project
16 |
17 | Run the following commands to create a new project and run it to ensure that the setup was completed successfully:
18 |
19 | lein new reagent-frontend reddit-viewer +figwheel
20 | cd reddit-viewer
21 | lein figwheel
22 |
23 | If the project starts up successfully, then you should have a browser window open at `localhost:3449/index.html`.
24 |
25 | ## During the Workshop
26 |
27 | This is a comprehensive guide to the workshop itself, for those playing along from home!
28 |
29 | We'll update project dependencies in `project.clj` to look as follows:
30 |
31 | ```clojure
32 | :dependencies [[org.clojure/clojure "1.8.0" :scope "provided"]
33 | [org.clojure/clojurescript "1.9.671" :scope "provided"]
34 | [reagent "0.7.0"]
35 | [cljsjs/chartjs "2.5.0-0"]
36 | [cljs-ajax "0.6.0"]]
37 | ```
38 |
39 | Next, let's replace the generated CSS link with the Bootstrap CSS in the `public/index.html` file:
40 |
41 | ```xml
42 |
43 |
44 |
45 |
46 |
47 | ```
48 |
49 | start the project in development mode:
50 |
51 | lein figwheel
52 |
53 | Leiningen will download the dependencies and start compiling the project, this can take a minute first time around.
54 | Once the project compilation finishes, a browser window will open at [http://localhost:3449/index.html](http://localhost:3449/index.html).
55 |
56 | ## Editing the project
57 |
58 | Now that we have the project running, let's see how we can add some functionality to it.
59 | We'll open up the `reddit_viewer/core.cljs` file that has some initial boilerplate in it and see what it's doing.
60 |
61 | ```clojure
62 | (ns reddit-viewer.core
63 | (:require
64 | [reagent.core :as r]))
65 |
66 | ;; -------------------------
67 | ;; Views
68 |
69 | (defn home-page []
70 | [:div [:h2 "Welcome to Reagent"]])
71 |
72 | ;; -------------------------
73 | ;; Initialize app
74 |
75 | (defn mount-root []
76 | (r/render [home-page] (.getElementById js/document "app")))
77 |
78 | (defn init! []
79 | (mount-root))
80 | ```
81 |
82 | The top section of the file contains a namespace declaration. The namespace requires the `reagent.core` namespace that's
83 | used to create the UI.
84 |
85 | The `home-page` function creates a Reagent component. The component contains a `div` with an `h2` tag inside it.
86 |
87 | Reagent uses Clojure literal notation for vectors and maps to represent HTML. The tag is defined using a vector, where the first element is the keyword representing the tag name, followed by an optional map of attributes, and the tag content.
88 |
89 | For example, `[:div [:h2 "Welcome to Reagent"]` maps to `
Welcome to Reagent
`. If we wanted to add `id` and `class` to the `div`, we could do that as follows: `[:div {:id "foo" :class "bar baz"} ...]`.
90 |
91 | Since setting the `id` and `class` attributes is a very common operation, Reagent provides a shortcut for doing that using syntax similar to CSS selectors: `[:div#foo.bar.baz ...]`.
92 |
93 | This component is rendered inside the DOM element with the ID `app`. This element is defined in the `public/index.html` file
94 | by the `mount-root` function.
95 |
96 | Finally, we have the `init!` function that serves as the entry point for the application.
97 |
98 | ### Task 1: Loading data using Ajax and viewing it.
99 |
100 | Let's start by creating a container to hold the results:
101 |
102 | ```clojure
103 | (defonce posts (r/atom nil))
104 | ```
105 |
106 | The `atom` is a container for mutable data. We'll initialize it with a `nil` value.
107 |
108 | Next, we'll require the `ajax.core` namespace and add a couple of functions that will load posts from the `http://www.reddit.com/r/Catloaf.json?sort=new&limit=9` URL, filter out the ones with images,
109 | and save them in the `posts` atom:
110 |
111 | ```clojure
112 | (ns reddit-viewer.core
113 | (:require
114 | [ajax.core :as ajax]
115 | [reagent.core :as r]))
116 |
117 | (defonce posts (r/atom nil))
118 |
119 | (defn find-posts-with-preview [posts]
120 | (filter #(= (:post_hint %) "image") posts))
121 |
122 | (defn load-posts []
123 | (ajax/GET "http://www.reddit.com/r/Catloaf.json?sort=new&limit=10"
124 | {:handler #(->> (get-in % [:data :children])
125 | (map :data)
126 | (find-posts-with-preview)
127 | (reset! posts))
128 | :response-format :json
129 | :keywords? true}))
130 | ```
131 |
132 | The `load-posts` function loads the JSON data and converts it to a Clojure data structure. We pass the `ajax/GET` function the URL and
133 | a map of options. The options contain the `:handler` key pointing to the function that should be called to handle the successful response,
134 | the `:response-format` key that hints that the response type is JSON, and `:keywords?` hint indicating that we would like to convert JSON
135 | string keys into Clojure keywords for maps.
136 |
137 | The original data has the following structure:
138 |
139 | ```clojure
140 | {:data {:children [{:data {...}} ...]}}
141 | ```
142 |
143 | The top level data structure is a map that contains a key called `:data`, this key points to a map that contains a key called
144 | `:children`. Finally, the `:children` key points to a collection of maps representing the posts. Each map, in turn, has a key
145 | called `:data` that contains the data for the post.
146 |
147 | Our `:handler` function grabs the collection of posts, and maps across them to get the `:data` key containing the information about
148 | each post. It then calls the `find-posts-with-preview` function to filter out posts without images. After we process the original response data, we reset the `posts` atom with the result.
149 |
150 | We can test our function in the Figwheel REPL by running the following commands:
151 |
152 | ```clojure
153 | (in-ns 'reddit-viewer.core)
154 | (load-posts)
155 | (first @posts)
156 | ```
157 |
158 | We should see the data contained in the first item in the collection of posts that was loaded.
159 |
160 | ### Task 2: Rendering the data
161 |
162 | Each post map contains a `:url` key that points to an image. Let's write a component function to render the image from the first post that looks as follows:
163 |
164 | ```clojure
165 | (defn display-post [{:keys [url]}]
166 | (when url [:img {:src url}]))
167 | ```
168 |
169 | When a Reagent component function returns `nil` it is omitted in the DOM, so the `display-posts` component will only be rendered when provided with a map containing a `:url` key that has a value.
170 |
171 | We can now parent this component under the `home-page` component:
172 |
173 | ```clojure
174 | (defn home-page []
175 | [:div [:h2 "Welcome to Reagent"]
176 | [display-post (first @posts)]])
177 | ```
178 |
179 | Note that we're putting the `display-post` component in a vector `[display-post]` as opposed to calling it as a function with `(display-post)`.
180 |
181 | This is a property of how the Reagent library works. The templates specify the structure of the page. Reagent
182 | then manages the lifecycle of the component functions, and decides when they need to be called based on the state of the data.
183 |
184 | If we called the function directly by writing `(display-post)`, then it would be executed a single time when the code is initialized, and
185 | it would not be repainted when the contents of `posts` atom change.
186 |
187 | By using the vector notation and writing `[display-post]`, we're telling Reagent where we would like to render the `display-post` component, and let it manage when to call it based on the state of the data.
188 |
189 | Reagent atoms are reactive meaning that any time the atom is dereferenced using the `@` notation, a listener is created. When the atom value changes, all the listeners are notified of the change, and the components are repainted.
190 |
191 | We can tests this by going to the REPL and clearing the `posts` atom:
192 |
193 | ```clojure
194 | (reset! posts nil)
195 | ```
196 |
197 | We can see that the image disappears on the page once the contents of the atom have been cleared. Let's run the `(load-posts)` function again:
198 |
199 | ```clojure
200 | (load-posts)
201 | ```
202 |
203 | We should be seeing the cat picture once again as the `display-post` component is repainted with new data.
204 |
205 | #### Working with HTML
206 |
207 | We've now seen that the data is being loaded, but it's not terribly nice to look at. Let's render it in a better way using Bootstrap CSS.
208 | We'll update the `display-post` component function as follows:
209 |
210 | ```clojure
211 | (defn display-post [{:keys [permalink subreddit title score url]}]
212 | [:div.card.m-2
213 | [:div.card-block
214 | [:h4.card-title
215 | [:a {:href (str "http://reddit.com" permalink)} title " "]]
216 | [:div [:span.badge.badge-info {:color "info"} subreddit " score " score]]
217 | [:img {:width "300px" :src url}]]])
218 | ```
219 |
220 | Now that we can render a single post nicely, let's write a function that will render a multiple posts:
221 |
222 | ```clojure
223 | (defn display-posts [posts]
224 | (when-not (empty? posts)
225 | [:div
226 | (for [posts-row (partition-all 3 posts)]
227 | ^{:key posts-row}
228 | [:div.row
229 | (for [post posts-row]
230 | ^{:key post}
231 | [:div.col-4 [display-post post]])])]))
232 | ```
233 |
234 | The function will accept a collection of posts as its parameter. It will then check whether the collection is empty.
235 |
236 | When the `posts` are not empty, we'll partition them into groups of three.
237 | We'll create a Bootstrap row for each group and pass the posts in the row to the `display-post` function we wrote earlier.
238 |
239 | Note that we're using the `^{:key posts-row}` notation for dynamic collections elements. This provides Reagent with a unique identifier for each element to decide when to repaint it efficiently. If the key was omitted, then Reagent would repaint all elements whenever any of the elements need repainting.
240 |
241 | With that in place, we can update the `home-page` component to render the posts:
242 |
243 | ```clojure
244 | (defn home-page []
245 | [:div.card>div.card-block
246 | [display-posts @posts]])
247 | ```
248 |
249 | ### Task 3: Manipulating the data
250 |
251 | We're able to load the posts, and have a UI for render them. Let's take a look at adding the ability to sort the posts, and see how the UI will track the changes for us.
252 |
253 | We'll add a `sort-posts` component function that looks as follows:
254 |
255 | ```clojure
256 | (defn sort-posts [title sort-key]
257 | (when-not (empty? @posts)
258 | [:button.btn.btn-secondary
259 | {:on-click #(swap! posts (partial sort-by sort-key))}
260 | (str "sort posts by " title)]))
261 | ```
262 |
263 | This function will check that the posts are not empty, and add a button to sort the posts by the specified key.
264 |
265 | Let's add a couple of buttons to the `home-page` that will allow us to sort posts by their score and comments:
266 |
267 | ```clojure
268 | (defn home-page []
269 | [:div.card>div.card-block
270 | [:div.btn-group
271 | [sort-posts "score" :score]
272 | [sort-posts "comments" :num_comments]]
273 | [display-posts @posts]])
274 | ```
275 |
276 | Note that as we're updating the UI, we're retaining the state of the application. As new components are added, the `posts` atom state is retained. We can modify the way the UI looks without having to reload the application to see the changes.
277 |
278 | ### Task 4: JavaScript interop
279 |
280 | So far we've been working exclusively with Reagent components. Now, let's take a look at using a plain JavaScript library that expects to manipulate the DOM directly.
281 |
282 | Let's create a new namespace called `reddit-viewer.chart` in the `src/reddit_viewer/chart.cljs` file to handle charting our data using the Chart.js library. The namespace declaration will look as follows:
283 |
284 |
285 | ```clojure
286 | (ns reddit-viewer.chart
287 | (:require
288 | [cljsjs.chartjs]
289 | [reagent.core :as r]))
290 | ```
291 |
292 | Next, we'll write a function that calls Chart.js to render given data in a DOM node as a bar chart:
293 |
294 | ```clojure
295 | (defn render-data [node data]
296 | (js/Chart.
297 | node
298 | (clj->js
299 | {:type "bar"
300 | :data {:labels (map :title data)
301 | :datasets [{:label "votes"
302 | :data (map :score data)}]}
303 | :options {:scales {:xAxes [{:display false}]}}})))
304 | ```
305 |
306 | The above code is equivalent to writing the following JavaScript:
307 |
308 | ```
309 | new Chart(node
310 | {type: "bar",
311 | data: {
312 | labels: data.map(function(x) {return x.title}),
313 | datasets:
314 | [{
315 | label: "votes",
316 | data: data.map(function(x) {return x.ups})
317 | }]
318 | },
319 | options: {
320 | scales: {xAxes: [{display: false}]}
321 | }
322 | });
323 | ```
324 |
325 | Now that we have the code to render the chart, we need to have access to a DOM node. Since Reagent is based on React, it uses a virtual DOM and renders components in the browser DOM as needed.
326 |
327 | So far we've been writing components as functions that return HTML elements. However, these functions only represent the render method of a React class.
328 |
329 | In order to get access to the DOM we have to implement other lifecycle functions that get called when the component is mounted, updated, and unmounted. This is achieved by calling the `create-class` function:
330 |
331 | ```clojure
332 | (defn chart-posts-by-votes [data]
333 | (let [chart (r/atom nil)]
334 | (r/create-class
335 | {:component-did-mount (render-chart chart data)
336 | :component-did-update (render-chart chart data)
337 | :component-will-unmount (fn [_] (destroy-chart chart))
338 | :render (fn [] (when @data [:canvas]))})))
339 | ```
340 |
341 | The function accepts a map keyed on the lifecycle events. Whenever each event occurs, the associated function will be called.
342 |
343 | We'll track the state of the chart using an atom. This will be necessary because we have to destroy the existing chart when component is unmounted.
344 |
345 | You can see that the `:render` key points to a function that will return the `:canvas` element when data is available.
346 |
347 | The `:component-did-mount` and `:component-did-update` keys point to the `render-chart` function that w'll write next:
348 |
349 | ```clojure
350 | (defn render-chart [chart data]
351 | (fn [component]
352 | (when (not-empty @data)
353 | (let [node (r/dom-node component)]
354 | (destroy-chart chart)
355 | (reset! chart (render-data node @data))))))
356 | ```
357 |
358 | This function is a closure that returns a function that will receive the React component. The inner function will check if there's any data available, and if so, then it will grab the mounted DOM node by calling `r/dom-node` on the `component`. It will attempt to clear the existing chart by calling the `destroy-chart` function, and then create a new chart by calling the `render-data` function we wrote earlier.
359 |
360 | Finally, we'll implement the `destroy-chart` function as follows:
361 |
362 | ```clojure
363 | (defn destroy-chart [chart]
364 | (when @chart
365 | (.destroy @chart)
366 | (reset! chart nil)))
367 | ```
368 |
369 | This function will check whether there's an existing chart present and call its `destroy` method. It will then reset the `chart` atom to a `nil` value.
370 |
371 | With that in place, we can navigate back to the `reddit-viewer.core` namespace, and require the `reddit-viewer.chart` namespace there:
372 |
373 | ```clojure
374 | (ns reddit-viewer.core
375 | (:require
376 | [ajax.core :as ajax]
377 | [reagent.core :as r]
378 | [reddit-viewer.chart :as chart]))
379 | ```
380 |
381 | We'll now update the `home-page` component to display the chart:
382 |
383 | ```clojure
384 | (defn home-page []
385 | [:div.card>div.card-block
386 | [:div.btn-group
387 | [sort-posts "score" :score]
388 | [sort-posts "comments" :num_comments]]
389 | [chart/chart-posts-by-votes posts]
390 | [display-posts @posts]])
391 | ```
392 |
393 | We should now see the chart rendered, and it should update when we change the sort order of our data using the `score` and `comment` sorting buttons.
394 |
395 | ### Task 5: Managing local state within components
396 |
397 | As a final touch, let's add a navbar to separate the posts and the chart into separate views. We'll start by adding a `navitem` function that creates a navigation link given a title, an atom containing the currently selected view, and the id of the nav item:
398 |
399 | ```clojure
400 | (defn navitem [title view id]
401 | [:li.nav-item
402 | {:class-name (when (= id @view) "active")}
403 | [:a.nav-link
404 | {:href "#"
405 | :on-click #(reset! view id)}
406 | title]])
407 | ```
408 |
409 | The component checks whether the current id in the view matches the item id in order to decide whether its class should be set to active. When it's clicked, the component will reset the `view` atom to its id.
410 |
411 | We can now create a Bootstrap navbar with links to posts and the chart:
412 |
413 | ```clojure
414 | (defn navbar [view]
415 | [:nav.navbar.navbar-toggleable-md.navbar-light.bg-faded
416 | [:ul.navbar-nav.mr-auto.nav
417 | {:className "navbar-nav mr-auto"}
418 | [navitem "Posts" view :posts]
419 | [navitem "Chart" view :chart]]])
420 | ```
421 |
422 | Finally, we'll update the home page to use the `navbar` component. The home page will now need to track a local state to know what view it needs to display.
423 | This is accomplished by creating a local atom called `view`:
424 |
425 | ```clojure
426 | (defn home-page []
427 | (let [view (r/atom :posts)]
428 | (fn []
429 | [:div
430 | [navbar view]
431 | [:div.card>div.card-block
432 | [:div.btn-group
433 | [sort-posts "score" :score]
434 | [sort-posts "comments" :num_comments]]
435 | (case @view
436 | :chart [chart/chart-posts-by-votes posts]
437 | :posts [display-posts @posts])]])))
438 | ```
439 |
440 | Notice that we return an anonymous function from inside the `let` statement. This is a Reagent mechanic for creating local state within components.
441 |
442 | If the inner function was not present, then the top level function would be called each time the component was repainted and the `let` statement would be reinitialized.
443 |
444 | When a component returns a function as the result, Reagent knows to call that function when subsequent calls to that component occur.
445 |
446 | Since this is a common operation, Reagent provides a helper macro called `with-let`. We can rewrite the above function using it as follows:
447 |
448 | ```clojure
449 | (defn home-page []
450 | (r/with-let [view (r/atom :posts)]
451 | [:div
452 | [navbar view]
453 | [:div.card>div.card-block
454 | [:div.btn-group
455 | [sort-posts "score" :score]
456 | [sort-posts "comments" :num_comments]]
457 | (case @view
458 | :chart [chart/chart-posts-by-votes posts]
459 | :posts [display-posts @posts])]]))
460 | ```
461 |
462 | That completes all the functionality we set out to add to our application. The only thing left to do is to compile it for production use.
463 |
464 | ## Excercises
465 |
466 | * Add a loading dialog that will be displayed when images are being loaded
467 | * Add a button to select the number of posts to fetch
468 | * Add the ability to select what subreddit the images are loaded from
469 | * Add the ability to load posts from multiple subreddits
470 | * Add tabs to show posts by subreddit
471 |
472 | ## Compiling for release
473 |
474 | So far we've been working with ClojureScript in development mode. This compilation method allows for fast incremental compilation and reloading. However, it generates very large JavaScript files.
475 |
476 | To use our app in production we'll want to use the advanced compilation method that will produce optimized JavaScript. This is accomplished by running the following command:
477 |
478 | lein package
479 |
480 | This will produce a single minified JavaScript file called `public/js/app.js` that's ready for production use.
481 |
482 | ## [Part 2: re-frame integration](https://github.com/ClojureTO/JS-Workshop/tree/re-frame)
483 |
484 | ## Libraries used in the project
485 |
486 | * [Chart.js](http://www.chartjs.org/) - used to generate the bar chart
487 | * [cljs-ajax](https://github.com/JulianBirch/cljs-ajax) - used to fetch data from Reddit
488 | * [Reagent](https://reagent-project.github.io/) - ClojureScript interface for React
--------------------------------------------------------------------------------
/env/dev/cljs/reddit_viewer/dev.cljs:
--------------------------------------------------------------------------------
1 | (ns ^:figwheel-no-load reddit-viewer.dev
2 | (:require [reddit-viewer.core :as core]
3 | [devtools.core :as devtools]
4 | [figwheel.client :as figwheel :include-macros true]))
5 |
6 | (devtools/install!)
7 |
8 | (enable-console-print!)
9 |
10 | (core/init!)
11 |
--------------------------------------------------------------------------------
/env/prod/cljs/reddit_viewer/prod.cljs:
--------------------------------------------------------------------------------
1 | (ns reddit-viewer.prod
2 | (:require [reddit-viewer.core :as core]))
3 |
4 | ;;ignore println statements in prod
5 | (set! *print-fn* (fn [& _]))
6 |
7 | (core/init!)
8 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject reddit-viewer "0.1.0-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 |
7 | :dependencies [[org.clojure/clojure "1.10.0" :scope "provided"]
8 | [org.clojure/clojurescript "1.10.520" :scope "provided"]
9 | [reagent "0.8.1"]
10 | [cljsjs/chartjs "2.7.3-0"]
11 | [cljs-ajax "0.8.0"]]
12 |
13 | :plugins [[lein-cljsbuild "1.1.7"]
14 | [lein-figwheel "0.5.18"]]
15 |
16 | :min-lein-version "2.5.0"
17 | :source-paths ["src"]
18 | :clean-targets ^{:protect false}
19 | [:target-path
20 | [:cljsbuild :builds :app :compiler :output-dir]
21 | [:cljsbuild :builds :app :compiler :output-to]]
22 |
23 | :resource-paths ["public"]
24 |
25 | :figwheel {:http-server-root "."
26 | :nrepl-port 7002
27 | :nrepl-middleware ["cemerick.piggieback/wrap-cljs-repl"]
28 | :css-dirs ["public/css"]}
29 |
30 | :cljsbuild {:builds {:app
31 | {:source-paths ["src" "env/dev/cljs"]
32 | :compiler
33 | {:main "reddit-viewer.dev"
34 | :output-to "public/js/app.js"
35 | :output-dir "public/js/out"
36 | :asset-path "js/out"
37 | :source-map true
38 | :optimizations :none
39 | :pretty-print true}
40 | :figwheel
41 | {:open-urls ["http://localhost:3449/index.html"]
42 | :on-jsload "reddit-viewer.core/mount-root"}}
43 | :release
44 | {:source-paths ["src" "env/prod/cljs"]
45 | :compiler
46 | {:output-to "public/js/app.js"
47 | :output-dir "public/js/release"
48 | :asset-path "js/out"
49 | :optimizations :advanced
50 | :pretty-print false}}}}
51 |
52 | :aliases {"package" ["do" "clean" ["cljsbuild" "once" "release"]]}
53 |
54 | :profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]
55 | [figwheel-sidecar "0.5.18"]
56 | [nrepl "0.6.0"]
57 | [cider/piggieback "0.4.0"]]}})
58 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
ClojureScript has not been compiled!
12 |
please run lein figwheel in order to start the compiler