|
| 1 | += ClojureScript Command Line |
| 2 | +Mike Fikes |
| 3 | +2018-03-26 |
| 4 | +:jbake-type: post |
| 5 | +:toc: macro |
| 6 | +:icons: font |
| 7 | + |
| 8 | +ifdef::env-github,env-browser[:outfilesuffix: .adoc] |
| 9 | + |
| 10 | +[[clojurescript-command-line-toc]] |
| 11 | +toc::[] |
| 12 | + |
| 13 | +ClojureScript now has an exciting new command line capability which dramatically simplifies using ClojureScript for many common use cases. In this post, we'll take you through a quick tour of the capabilities being introduced with `cljs.main`. |
| 14 | + |
| 15 | +[[clojurescript-compiler]] |
| 16 | +=== Standalone ClojureScript JAR |
| 17 | + |
| 18 | +In order to try these examples, if you are on macOS or Linux, you can use the https://clojure.org/guides/getting_started[`clj`] command line tool, with `deps.edn` set up as: |
| 19 | + |
| 20 | +[source,clojure] |
| 21 | +---- |
| 22 | +{:deps {org.clojure/clojurescript {:mvn/version "1.10.238"}}} |
| 23 | +---- |
| 24 | + |
| 25 | +On Windows, you will need to obtain the https://github.com/clojure/clojurescript/releases/latest[standalone ClojureScript JAR]. |
| 26 | + |
| 27 | +[[starting-a-browser-repl]] |
| 28 | +== Starting a Browser REPL |
| 29 | + |
| 30 | +Let's jump right in! You can start up a browser REPL by running the following command |
| 31 | + |
| 32 | +[source,bash] |
| 33 | +---- |
| 34 | +clj -m cljs.main |
| 35 | +---- |
| 36 | + |
| 37 | +On Windows: |
| 38 | + |
| 39 | +[source,bash] |
| 40 | +---- |
| 41 | +java -cp cljs.jar cljs.main |
| 42 | +---- |
| 43 | + |
| 44 | +When you do this, the REPL will start and will automatically launch a browser to connect back to it. |
| 45 | + |
| 46 | +image::/images/content/news/2018-03-26-clojurescript-command-line/browser.png[Browser REPL Serving Default index.html,450] |
| 47 | + |
| 48 | +You will see the following in the REPL terminal: |
| 49 | + |
| 50 | +[source,text] |
| 51 | +---- |
| 52 | +ClojureScript 1.10.238 |
| 53 | +cljs.user=> |
| 54 | +---- |
| 55 | + |
| 56 | +We are now in an interactive environment, with the ability to control the page. Try the following in the REPL to cause an alert to pop up in your browser: |
| 57 | + |
| 58 | +[source,clojure] |
| 59 | +---- |
| 60 | +(js/alert "Hello CLJS!") |
| 61 | +---- |
| 62 | + |
| 63 | +[[creating-a-browser-app]] |
| 64 | +== Creating a Browser App |
| 65 | + |
| 66 | +Now, let's cobble together a simple browser-based ClojureScript app and explore the ability of `cljs.main` to compile your app's source. |
| 67 | + |
| 68 | +Note that in the previous section, `cljs.main` synthetically-generated an `index.html` for us. We'll want to create a custom `index.html` for our app in the current directory: |
| 69 | + |
| 70 | +[source,html] |
| 71 | +---- |
| 72 | +<html> |
| 73 | + <body> |
| 74 | + <canvas id="canvas" width="400" height="300"></canvas> |
| 75 | + <script src="out/main.js"></script> |
| 76 | + </body> |
| 77 | +</html> |
| 78 | +---- |
| 79 | + |
| 80 | +Add the following source in `src/my_app/core.cljs` (or `src\my_app\core.cljs` if on Windows). |
| 81 | + |
| 82 | +[source,clojure] |
| 83 | +---- |
| 84 | +(ns my-app.core) |
| 85 | +
|
| 86 | +(def ctx (-> js/document |
| 87 | + (.getElementById "canvas") |
| 88 | + (.getContext "2d"))) |
| 89 | +
|
| 90 | +(defn draw-shape [x y radius color] |
| 91 | + (set! (.-fillStyle ctx) color) |
| 92 | + (.beginPath ctx) |
| 93 | + (.arc ctx x y radius 0 (* 2 Math/PI)) |
| 94 | + (.fill ctx)) |
| 95 | +
|
| 96 | +(draw-shape 150 150 100 "blue") |
| 97 | +---- |
| 98 | + |
| 99 | +Now, let's use `cljs.main` to first compile this source (using `-c`), and, when done, start up a browser REPL (using `-r`), and additionally watch for changes in our source (using `-w`): |
| 100 | + |
| 101 | +[source,bash] |
| 102 | +---- |
| 103 | +clj -m cljs.main -w src -c my-app.core -r |
| 104 | +---- |
| 105 | + |
| 106 | +On Windows: |
| 107 | + |
| 108 | +[source,bash] |
| 109 | +---- |
| 110 | +java -cp "cljs.jar;src" cljs.main -w src -c my-app.core -r |
| 111 | +---- |
| 112 | + |
| 113 | +Note that we are also adding our `src` directory to the classpath. |
| 114 | + |
| 115 | +When this launches, you should see a blue circle in your browser. |
| 116 | + |
| 117 | +image::/images/content/news/2018-03-26-clojurescript-command-line/blue-circle.png[Browser REPL Showing Blue Circle,400] |
| 118 | + |
| 119 | +Try interacting with the app by drawing other circles. For example, try this in the REPL: |
| 120 | + |
| 121 | +[source,clojure] |
| 122 | +---- |
| 123 | +(my-app.core/draw-shape 350 200 50 "red") |
| 124 | +---- |
| 125 | + |
| 126 | +image::/images/content/news/2018-03-26-clojurescript-command-line/blue-red-circle.png[Browser REPL Showing Blue and Red Circle,400] |
| 127 | + |
| 128 | +What if you change your source? Change the `2` to a `1` in the `draw-shape` implementation, and refresh your browser. Now instead of circles, the app will draw semi-circles. |
| 129 | + |
| 130 | +[[creating-a-node-app]] |
| 131 | +== Creating a Node App |
| 132 | + |
| 133 | +In the previous sections, we were relying on `cljs.main` to establish a browser REPL environment. But, `cljs.main` has a command line flag (`-re`) that allows you to specify an alternate REPL environment. |
| 134 | + |
| 135 | +For example, if have Node installed, you can use `cljs.main` to launch a Node-based REPL by supplying `-re node`: |
| 136 | + |
| 137 | +[source,bash] |
| 138 | +---- |
| 139 | +clj -m cljs.main -re node |
| 140 | +---- |
| 141 | + |
| 142 | +On Windows: |
| 143 | + |
| 144 | +[source,bash] |
| 145 | +---- |
| 146 | +java -cp cljs.jar cljs.main -re node |
| 147 | +---- |
| 148 | + |
| 149 | +If you do this, you will be dropped directly into a Node-based REPL: |
| 150 | + |
| 151 | +[source,text] |
| 152 | +---- |
| 153 | +ClojureScript 1.10.238 |
| 154 | +cljs.user=> (+ 2 3) |
| 155 | +5 |
| 156 | +cljs.user=> (exists? js/require) |
| 157 | +true |
| 158 | +---- |
| 159 | + |
| 160 | +Let's make a small Node-based app. Replace the contents of our `my-app.core` namespace with |
| 161 | + |
| 162 | +[source,clojure] |
| 163 | +---- |
| 164 | +(ns my-app.core) |
| 165 | +
|
| 166 | +(defn square [x] |
| 167 | + (* x x)) |
| 168 | +
|
| 169 | +(defn -main [& args] |
| 170 | + (-> args first js/parseInt square prn)) |
| 171 | +---- |
| 172 | + |
| 173 | +With this in place, let's run this app using `cljs.main` to run `-main` in a specified namespace (using `-m`): |
| 174 | + |
| 175 | +[source,bash] |
| 176 | +---- |
| 177 | +clj -m cljs.main -re node -m my-app.core 5 |
| 178 | +---- |
| 179 | + |
| 180 | +On Windows: |
| 181 | + |
| 182 | +[source,bash] |
| 183 | +---- |
| 184 | +java -cp "cljs.jar;src" cljs.main -re node -m my-app.core 5 |
| 185 | +---- |
| 186 | + |
| 187 | +Running this will automatically compile our namespace, launch Node, and execute our `-main`, passing our command line argument `5`, thus causing it to print `25`. |
| 188 | + |
| 189 | +What if we'd like to produce a standalone JavaScript file that we can use with Node to do the same? |
| 190 | + |
| 191 | +First, add one helper to the end of `my-app.core`: |
| 192 | + |
| 193 | +[source,clojure] |
| 194 | +---- |
| 195 | +(set! *main-cli-fn* -main) |
| 196 | +---- |
| 197 | + |
| 198 | +Now we are going to compile a `simple` (using `-O`) build, targeting |
| 199 | +Node (using `-t`), specifying where we'd like our final output file (using `-o`): |
| 200 | + |
| 201 | +[source,bash] |
| 202 | +---- |
| 203 | +clj -m cljs.main -t node -O simple -o main.js -c my-app.core |
| 204 | +---- |
| 205 | + |
| 206 | +On Windows: |
| 207 | + |
| 208 | +[source,bash] |
| 209 | +---- |
| 210 | +java -cp "cljs.jar;src" cljs.main -t node -O simple -o main.js -c my-app.core |
| 211 | +---- |
| 212 | + |
| 213 | +With this, you can copy `main.js` to wherever you'd like and run |
| 214 | + |
| 215 | +[source,bash] |
| 216 | +---- |
| 217 | +node main.js 5 |
| 218 | +---- |
| 219 | + |
| 220 | +and it will print `25`. |
| 221 | + |
| 222 | +[[running-tests-in-nashorn]] |
| 223 | +== Running Tests in Nashorn |
| 224 | + |
| 225 | +The built-in Nashorn environment is accessible using `cljs.main`, and with it there is no need for any external JavaScript environment. Let's use this to run some tests. |
| 226 | + |
| 227 | +First, add a new file for a `my-app.core-test` namespace |
| 228 | + |
| 229 | +[source,clojure] |
| 230 | +---- |
| 231 | +(ns my-app.core-test |
| 232 | + (:require |
| 233 | + [my-app.core] |
| 234 | + [clojure.test :refer [deftest is]])) |
| 235 | +
|
| 236 | +(deftest square-test |
| 237 | + (is (== 25 (my-app.core/square 5)))) |
| 238 | +---- |
| 239 | + |
| 240 | +Let's run these tests under Nashorn (by specifying `-re nashorn`). To do things a little differently, let's use `-i` to load a resource, and `-e` to evaluate a form that will kick off our tests: |
| 241 | + |
| 242 | +[source,bash] |
| 243 | +---- |
| 244 | +clj -m cljs.main -re nashorn -i src/my_app/core_test.cljs -e "(cljs.test/run-tests 'my-app.core-test)" |
| 245 | +---- |
| 246 | + |
| 247 | +On Windows |
| 248 | + |
| 249 | +[source,bash] |
| 250 | +---- |
| 251 | +java -cp "cljs.jar;src" cljs.main -re nashorn -i src\my_app\core_test.cljs -e "(cljs.test/run-tests 'my-app.core-test)" |
| 252 | +---- |
| 253 | + |
| 254 | +With this, you will see |
| 255 | + |
| 256 | +[source,text] |
| 257 | +---- |
| 258 | +Testing my-app.core-test |
| 259 | +
|
| 260 | +Ran 1 tests containing 1 assertions. |
| 261 | +0 failures, 0 errors. |
| 262 | +---- |
| 263 | + |
| 264 | +[[other-affordances]] |
| 265 | +== Other Affordances |
| 266 | + |
| 267 | +The above took you through a quick tour covering most of the options available in `cljs.main`. There are other options available, and you can get help on them by running |
| 268 | + |
| 269 | +[source,bash] |
| 270 | +---- |
| 271 | +clj -m cljs.main -h |
| 272 | +---- |
| 273 | + |
| 274 | +On Windows: |
| 275 | +[source,bash] |
| 276 | +---- |
| 277 | +java -cp cljs.jar cljs.main -h |
| 278 | +---- |
| 279 | + |
| 280 | +A couple of interesting options that might be useful are `-co` and `-ro`. They provide the ability to configure any compiler https://clojurescript.org/reference/compiler-options[compiler option] or https://clojurescript.org/reference/repl-options[REPL option], (which go under `-co`) and REPL-environment-specific options (which go under `-ro`). These can act as an "escape hatch" if you need to specify something for which `cljs.main` doesn't provide a command-line flag. |
| 281 | + |
| 282 | +For example, the following will apply the `:repl-verbose` option (thus showing the JavaScript being emitted while using the REPL): |
| 283 | + |
| 284 | +[source,bash] |
| 285 | +---- |
| 286 | +clj -m cljs.main -co "{:repl-verbose true}" -re node -r |
| 287 | +---- |
| 288 | + |
| 289 | +On Windows: |
| 290 | + |
| 291 | +[source,bash] |
| 292 | +---- |
| 293 | +java -cp cljs.jar cljs.main -co "{:repl-verbose true}" -re node -r |
| 294 | +---- |
| 295 | + |
| 296 | +You can specify EDN directly on the command line, as shown above, or you can supply the names of files containing EDN. With this capability, you can pretty much use `cljs.main` to do anything you'd like with the ClojureScript compiler. |
| 297 | + |
| 298 | +We hope you find the new `cljs.main` feature useful and that it simplifies many of the common tasks you need to accomplish with the ClojureScript compiler! |
0 commit comments