Skip to content

weaver-viii/How-To-Debug-CLJS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OUTDATED: Please see figwheel for "interacting", doo for "testing", and Dirac to help with "tracing"

ClojureScript Debugging

I want to share some ClojureScript debugging practices that I find helpful. This project provides a simple web app example intended for studying/practicing the following debugging workflow:

  • Logging - easily and descriptively log your application state
  • Testing - quickly test your code without opening a web browser
  • Interacting - play with your code while it's running on your web page
  • Tracing - pause and step through code

Setup

  1. Install Leiningen and PhantomJS.

  2. In a terminal, run the auto-compiler from the project directory:

    lein cljsbuild auto
    
  3. In another terminal at project root, install and run a simple web server:

    npm install -g http-server
    http-server
    
  4. Navigate your browser to http://localhost:8080/index.htm

page

Logging

The easiest way to debug your application is to log messages to the browser console:

(js/console.log "b =>" (pr-str b))

pr-str serializes the given clojure value into a string for easy viewing.

To make this even easier, this project includes an inspect macro that prints any number of values:

(inspect a)
; prints:
;   a => 1

(inspect a b c (+ 1 2 3))
; prints:
;   a => 1
;   b => :foo-bar
;   c => ["hi" "world"]
;   (+ 1 2 3) => 6

You can see the result of the inspect calls in the browser console:

logging

Testing

It's nice having the ability to test your code without opening a browser. The following command runs all the tests contained in the test/ directory:

lein test

It will run three tests and print the following:

testing

Pure Function Testing

It is simple to create a test case that verifies the output of a function:

(deftest greeting
  (is (= "Hello there, Bob" (main/greeting "Bob"))))

DOM Testing

You can test a function's side effects on the DOM as well. Just keep in mind that by default, the PhantomJS test runner is evaluating all your code in the context of a blank DOM page.

(deftest greeting-drawn

  ; Create the DOM element.
  (-> ($ "<div id='greeting'></div>")
      (.appendTo "body"))

  ; Test DOM side effect when modifying atom.
  (reset! main/username "Billy")
  (is (= "Hello there, Billy"
         (.html ($ "#greeting"))))

  ; Remove DOM element from test environment.
  (-> ($ "#greeting")
      (.remove)))

So if you're testing a simple function that only touches a few DOM elements, you can create those DOM elements in the test.

For more complex DOM tests, you could reconfigure the PhantomJS runner to start with an existing page rather than a blank one. The default test command running tests on a blank page can be seen in project.clj:

:test-commands {"phantomjs" ["phantomjs" :runner
                             "public/js/jquery-1.9.1.min.js"
                             "public/js/main.js"]}

The :runner keyword is expanded to the default runner script, which can be replaced with a custom script for opening test pages with a preconfigured DOM, or even for directly running the web app as is.

Asynchronous Tests

You can also test asynchronous functions by making your test wait for a callback. Asynchronous test functions must have:

  • the metadata ^:async before the function name
  • a call to (done) to signify when the test is done
(deftest ^:async greeting-clear

  ; Create the DOM element.
  (-> ($ "<div id='greeting'></div>") (.appendTo "body"))

  ; Make sure DOM greeting is cleared after 2 seconds.
  (js/setTimeout
    (fn []
      (is (= "" (.html ($ "#greeting")))) ; check if clear
      (-> ($ "#greeting") (.remove))      ; remove DOM element
      (done))                             ; exit test
    2000))

Interacting

If you want to interact with the web app's code while it is running, you can hook up an interactive ClojureScript prompt (REPL) to a browser session displaying your page:

  1. From the terminal at your project directory, run:

    lein repl
    
    (brepl)
  2. Open http://localhost:8080/index.htm in your browser.

  3. Go back to the terminal and run:

    (js/alert "hello")
  4. You should see this:

interacting

Trying things

Type the following to enter the project's namespace under main.

(ns example.main)

You can set the value of the username atom with the following.

(reset! username "me")

You can inspect the value of that atom too:

@username

If you want to use other symbols, like the JQuery $ function:

(ns example.main (:require [jayq.core :refer [$]]))

Now you can inspect the value of the greeting in the DOM.

(.html ($ "#greeting"))

If all goes well, you should see "Hello there, me" on the web page too.

Tracing

You can place a breakpoint somewhere in your program if you want to halt the program and/or manually trace the flow of execution from there.

Breakpoints

The project provides a breakpoint macro which causes the browser to halt when the line is reached, but only when the Developer window of your browser is open.

(breakpoint)

If the developer window is open, you should see the Javascript code that your ClojureScript code compiles to:

tracing-js

Notice that you can see the current value of new_name on the right bar corresponds to the local binding of new-name in ClojureScript.

You can also toggle breakpoints from this view by clicking the line numbers.

Source Maps

You can see your ClojureScript code instead of its compiled Javascript form in the debugger by using lein super-debug instead of lein cljsbuild auto.

lein super-debug

This is just a custom alias (see project.clj) for:

lein with-profile +maps cljsbuild auto

Now you will see this instead:

tracing-cljs

This works by generating source maps in order to associate ClojureScript line numbers with compiled Javascript line numbers. Unfortunately this extra compilation step currently takes a long time (~7 extra seconds every recompile), so I recommend only using lein super-debug when you specifically need to trace your code.

NOTE I recently found out that source maps are instantaneous for :optimizations :none, which really should be used in when developing. (This project needs to be updated to use this setting)

About

Tutorial and example for debugging ClojureScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published