├── .gitignore ├── CHANGES.md ├── README.md ├── browser-connected-repl-sample ├── .gitignore ├── README.md ├── project.clj ├── resources │ └── index.html └── src │ ├── clj │ └── cemerick │ │ └── austin │ │ └── bcrepl_sample.clj │ └── cljs │ └── cemerick │ └── austin │ └── bcrepl_sample.cljs ├── epl-v10.html ├── project.clj └── src └── clj ├── austin └── plugin.clj └── cemerick ├── austin.clj └── austin └── repls.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml* 6 | *.jar 7 | *.class 8 | .lein-deps-sum 9 | .lein-failures 10 | .lein-plugins 11 | .classpath 12 | .project 13 | .settings 14 | 15 | .externalToolBuilders 16 | .repl 17 | out 18 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## `0.1.6` 4 | 5 | * Changed to track ClojureScript REPL API changes in >= 2665 6 | * `exec-env` now supports `:phantom-cmds`, for phantomjs-compatible command 7 | lines that require arguments 8 | 9 | ## `0.1.5` 10 | 11 | * Austin's browser REPL environment will no longer print verbosely when it is 12 | the result of an evaluation. 13 | * Official (non-SNAPSHOT) support for `cljs.env/default-compiler-env`'s options. 14 | * Minor documentation tweaks. 15 | 16 | ## [`0.1.4`](https://github.com/cemerick/austin/issues?milestone=2&state=closed) 17 | 18 | * Austin REPLs now have ClojureScript source maps turned on by default. (gh-33) 19 | * `cemerick.austin/repl-env` (and all helper functions that delegate to it) now 20 | accept a `:host` option (defaults to `"localhost"`, can be any 21 | network-addressable hostname) (gh-41) 22 | 23 | ## `0.1.3` 24 | 25 | Released to address a derp in the upstream Piggieback dependency. 26 | 27 | ## `0.1.2` 28 | 29 | * Adds support for ClojureScript compiler environments introduced in `0.0-2014`. 30 | Now requires that version of ClojureScript or higher. 31 | 32 | ## [`0.1.1`](https://github.com/cemerick/austin/issues?milestone=1&page=1&state=closed) 33 | 34 | * The port that Austin's HTTP server starts on can now be configured via system 35 | property, environment variable, or by explicitly starting it with a given port 36 | number. (gh-4, gh-5) 37 | * A comprehensible error message is now emitted if the executable named when 38 | creating a new `cemerick.austin/exec-env` (or running the 39 | `cemerick.austin.repls/exec` shortcut) (`phantomjs` by default) is not 40 | available. (gh-12, gh-13) 41 | * The `:static-dir` option is now properly utilized (gh-2) 42 | 43 | ## `0.1.0` 44 | 45 | Changes from `cljs.repl.browser`, from which this codebase was started: 46 | 47 | * Multiple concurrent browser-REPLs can be safely used from the same project 48 | * Austin's HTTP server is now always-on, and auto-selects an open port; this 49 | means you can have multiple concurrent browser-REPLs running from _different_ 50 | projects without faffing around with `:port` arguments, etc. 51 | * Each browser-REPL session supports a new top-level "entry" URL that can be 52 | used to easily start the REPL in a browser or other JS runtime (i.e. you don't 53 | need to have a separate webapp running to initiate the browser-REPL 54 | connection) 55 | * The entry (and REPL) URLs are available in slots on the browser-REPL's 56 | environment, making it trivial to automate browser-REPL sessions with e.g. 57 | phantomjs (see `exec-env` for an easy automated browser-REPL option) 58 | * Replaced the custom HTTP server with `com.sun.net.httpserver.*` bits ([a 59 | standard part of J2SE 60 | 6+](http://docs.oracle.com/javase/7/docs/technotes/guides/net/enhancements-6.0.html)) 61 | * The `:port` argument to `repl-env` is no longer supported; the lifecycle of 62 | the server is not tied to the creation of a browser-REPL environment. If you 63 | need to get the port of the running browser-REPL server, use 64 | `(get-browser-repl-port)`; if you need a URL you can use with 65 | `clojure.browser.repl/connect` as shown in existing browser-REPL tutorials, 66 | it's available under `:repl-url` from the browser-REPL environment you want to 67 | connect to. 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Austin 2 | 3 | 4 | 5 | A significant refactoring of the ClojureScript-standard browser-repl environment 6 | that's as easy to "configure" and use as a Clojure REPL. 7 | 8 | [![](https://dl.dropboxusercontent.com/u/35498822/austin-6-large.png)](http://youtu.be/HoLs0V8T5AA?t=41s) 9 | 10 | ## Why? 11 | 12 | Austin has one objective: to get you into a fast ClojureScript REPL suited for 13 | your project running in a browser environment as quickly and painlessly as 14 | possible, with full support for the nREPL toolchain. 15 | 16 | [Check out the screencast demonstrating how Austin is 17 | used](http://www.youtube.com/watch?v=a1Bs0pXIVXc&feature=youtu.be), or forge 18 | ahead for detailed documentation. 19 | 20 | 21 | ## Status 22 | 23 | I've been using this browser-repl alternative for more than a year with good results, 24 | and others have banged it around some as well. That said, I've only recently 25 | begun to think about the API around its configuration and project integration 26 | (in particular, the `cemerick.austin.repls` namespace). It all works nice, but 27 | changes in that department are almost surely going to happen. 28 | 29 | ### Compatibility 30 | 31 | When using Austin via nREPL, it depends upon 32 | [Piggieback](https://github.com/cemerick/piggieback). Please refer to 33 | [Piggieback's compatibility notes](https://github.com/cemerick/piggieback#compatibility-notes) 34 | to see if there are any known problems with using it (and therefore Austin) 35 | with your preferred toolchain. 36 | 37 | ## Changelog 38 | 39 | Available [here](http://github.com/cemerick/austin/blob/master/CHANGES.md). 40 | 41 | Austin is largely a refactoring of the original ClojureScript REPL. These 42 | changes include: 43 | 44 | * Multiple concurrent browser-REPLs can be safely used from the same project 45 | * Austin's HTTP server is now always-on, and auto-selects an open port; this 46 | means you can have multiple concurrent browser-REPLs running from _different_ 47 | projects without faffing around with `:port` arguments, etc. 48 | * Each browser-REPL session supports a new top-level "entry" URL that can be 49 | used to easily start the REPL in a browser or other JS runtime (i.e. you don't 50 | need to have a separate webapp running to initiate the browser-REPL 51 | connection) 52 | * The entry (and REPL) URLs are available in slots on the browser-REPL's 53 | environment, making it trivial to automate browser-REPL sessions with e.g. 54 | phantomjs. See ['Project REPLs'](#project-repls) for easy-mode "project" 55 | REPLs. 56 | * Replaced the custom HTTP server with `com.sun.net.httpserver.*` bits ([a 57 | standard part of J2SE 58 | 6+](http://docs.oracle.com/javase/7/docs/technotes/guides/net/enhancements-6.0.html)) 59 | * The `:port` argument to `repl-env` is no longer supported; the lifecycle of 60 | the server is not tied to the creation of a browser-REPL environment. If you 61 | need to get the port of the running browser-REPL server, use 62 | `(get-browser-repl-port)`; if you need a URL you can use with 63 | `clojure.browser.repl/connect` as shown in existing browser-REPL tutorials, 64 | it's available under `:repl-url` from the browser-REPL environment you want to 65 | connect to. See ['Browser-connected REPLs'](#browser-connected-repls) for 66 | easy-mode browser-connected REPLs 67 | 68 | ## "Installation" 69 | 70 | Austin is available in Maven Central. Add it to your `project.clj`'s list of 71 | `:plugins`, probably in your `:dev` profile: 72 | 73 | ```clojure 74 | :profiles {:dev {:plugins [[com.cemerick/austin "0.1.6"]]}} 75 | ``` 76 | 77 | Also, just like in Clojure development, your ClojureScript source roots must be 78 | listed in e.g. `:source-paths` and/or `:test-source-paths` in order for 79 | ClojureScript source files to be picked up properly. i.e. just having them 80 | enumerated in your lein-cljsbuild configuration(s) is not sufficient. 81 | 82 | **Note that Austin requires ClojureScript `0.0-2665` or higher.** 83 | 84 | Austin contains some Leiningen middleware that does the following: 85 | 86 | * Adds a _dependency_ on Austin to your project, which transitively brings in 87 | ClojureScript and Piggieback. 88 | * Modifies your project's `:repl-options` to include Piggieback's 89 | `wrap-cljs-repl` middleware. 90 | * Adds `(require '[cemerick.austin.repls :refer (exec) :rename {exec 91 | austin-exec}])` to your project's `:injections`, thus making 92 | `cemerick.austin.repls/exec` available as `austin-exec` in the `user` 93 | namespace for your fast'n'easy ClojureScript browser REPL pleasure. 94 | 95 | ## Usage 96 | 97 | _If you're impatient, [skip on down](#project-repls) to start a ClojureScript 98 | REPL using phantomjs/slimerjs/Chrome/etc in about 10 seconds._ 99 | 100 | Austin provides two types of ClojureScript REPL environments. One, returned by 101 | calls to `cemerick.austin/repl-env`, is analogous to the standard ClojureScript 102 | browser-REPL environment implemented in `cljs.repl.browser`, with various 103 | usability improvements. The other, returned by calls to 104 | `cemerick.austin/exec-env`, provides all the same functionality as `repl-env`, 105 | but also fully manages the lifecycle of an external JavaScript runtime that is 106 | used to service all REPL interactions (i.e. you don't need to have an app 107 | running in a GUI browser to get a browser-REPL going). Either of these REPL 108 | environments can be used with either of: 109 | 110 | * `cljs.repl/repl`, described in the various "core" ClojureScript tutorials as 111 | the primary entry point for all things REPL. This is suitable in terminal 112 | settings, can be used w/ e.g. `inferior-lisp` in emacs, and so on, but cannot 113 | be used with nREPL. 114 | * `cemerick.piggieback/cljs-repl`, the nREPL-compatible analogue to 115 | `cljs.repl/repl`, provided by Piggieback 116 | 117 | Austin's two types of REPL environments roughly correspond to the two primary 118 | scenarios for ClojureScript REPLs: 119 | 120 | * _Project_ REPLs, where you want a ClojureScript REPL that has all of your 121 | project's dependencies, sources, and other resources available, but is 122 | generally not using or requiring your application's front-end to be running in 123 | a GUI browser, i.e. a headless JavaScript runtime is sufficient, which may or 124 | may not have a DOM. This is generally when `exec-env` is used. 125 | * _Browser-connected REPLs_, the original use case of ClojureScript 126 | browser-REPLs, where you want a ClojureScript REPL connected to a browser 127 | runtime within which you've loaded your front-end application. 128 | 129 | This nomenclature is a bit hand-wavy, since the JavaScript runtimes used by 130 | project REPLs are almost always _also_ browsers; hopefully I'll come up with a 131 | better term for the first category eventually. 132 | 133 | ### Project REPLs 134 | 135 | To start a project REPL, just pass the result of calling `exec-env` to the 136 | ClojureScript REPL function that corresponds with your environment: 137 | 138 | * If you're using nREPL, `(cemerick.piggieback/cljs-repl :repl-env 139 | (cemerick.austin/exec-env))` 140 | * If you're not using nREPL, `(cljs.repl/repl (cemerick.austin/exec-env))` 141 | 142 | Alternatively, you can use `cemerick.austin.repls/cljs-repl`, a convenience 143 | function that will detect whether you're using nREPL or not, and pass a new exec 144 | environment to the correct ClojureScript REPL function. So, 145 | `(cemerick.austin.repls/cljs-repl (cemerick.austin/exec-env))` is equivalent to 146 | the two examples above; this particular combination is so commonly used that 147 | it's wrapped up into a single function, `(cemerick.austin.repls/exec)`, probably 148 | the easiest way to start a ClojureScript REPL that uses a browser JavaScript 149 | runtime. _Note that `cemerick.austin.repls/exec` passes all of its arguments 150 | along to `exec-env`._ 151 | 152 | #### `exec-env`'s browser runtimes 153 | 154 | Any of the above options will give you a headless ClojureScript REPL that has all 155 | of your project's dependencies, sources, and other resources available. 156 | `exec-env` uses `phantomjs` by default, so you'll need to have that installed 157 | and on your `PATH`. If you are using a different _phantomjs-compatible_ 158 | headless browser implementation (e.g. slimerjs, or perhaps your package manager 159 | installs phantomjs with a different name?), you can pass the name of that binary 160 | as :phantom-cmd, e.g. `(exec-env :phantom-cmd "slimerjs")`. 161 | 162 | Whichever process is started will be automatically terminated when you stop the 163 | ClojureScript REPL (via `:cljs/quit`), or the parent Clojure REPL. 164 | 165 | ##### Using other browser runtimes 166 | 167 | I've been saying "headless" here because it's often most convenient to avoid 168 | using "headed" browsers, which necessarily open a new window for each 169 | ClojureScript REPL you start. But, if you really want to, you _can_ use a full 170 | GUI browser with `exec-env`, which can be handy if you need to see the results 171 | of DOM manipulations, etc., without having to set up and connect to a browser 172 | running your application. To do this, just pass the terminal commands necessary 173 | to start your preferred browser (such that Austin can append the browser-repl 174 | URL to the command) to `exec-env` or `exec` as a `:exec-cmds` vector keyword 175 | argument: 176 | 177 | ```clojure 178 | user=> (cemerick.austin.repls/exec 179 | :exec-cmds ["open" "-ga" "/Applications/Google Chrome.app"]) 180 | Browser-REPL ready @ http://localhost:59423/4877/repl/start 181 | Type `:cljs/quit` to stop the ClojureScript REPL 182 | nil 183 | cljs.user=> (apply + (js/Array 1 2 3)) 184 | 6 185 | ``` 186 | 187 | The command strings passed to `exec` in this example will open the browser-REPL 188 | endpoint URL in a new Chrome window in the background on Mac OS X. Substitute 189 | whatever invocation you like for your preferred browser / operating system. 190 | 191 | ### Browser-connected REPLs 192 | 193 | This was always the primary use case for the original browser-repl: load your 194 | application up in a browser, have it connect back to your Clojure / 195 | ClojureScript compiler environment, and you can develop/debug/inspect/etc your 196 | running ClojureScript application as it runs in its target environment. 197 | 198 | This repo provides a completely self-contained sample project demonstrating and 199 | documenting how to use Austin for your browser-connected REPL'ing needs. [Check 200 | it 201 | out](https://github.com/cemerick/austin/blob/master/browser-connected-repl-sample). 202 | 203 | ### Other usage tidbits 204 | 205 | #### Server port selection 206 | 207 | By default, Austin's embedded HTTP server (which is what accepts requests from 208 | all JavaScript runtimes hosting a ClojureScript REPL) starts on a random 209 | system-assigned port. If you're using the provided facilities for generating 210 | Javascript to insert into your app's HTML to connect back to the HTTP server 211 | (i.e. `cemerick.austin.repls/browser-connected-repl-js`), then this is ideal: 212 | the server will always find an open port, and running multiple applications, 213 | each with N browser-REPLs, will always work. 214 | 215 | However, if you need to fix the port used by the HTTP server, there are three 216 | ways to go about it: 217 | 218 | * set the `AUSTIN_DEFAULT_SERVER_PORT` environment variable before starting your 219 | Clojure process 220 | * set the `cemerick.austin.default-server-port` system property; _this will only 221 | take effect if you have not yet caused the server to start automatically by 222 | creating a browser-REPL environment_. 223 | * explicitly start Austin's server, providing the desired port number, e.g. 224 | `(cemerick.austin/start-server 9000)`. 225 | 226 | ## TODO 227 | 228 | * ISO a reasonable automated test strategy 229 | 230 | ## Need Help? 231 | 232 | Ping `cemerick` on freenode irc or 233 | [twitter](http://twitter.com/cemerick) if you have questions or would 234 | like to contribute. 235 | 236 | ## License and credits 237 | 238 | Big shout out to Brenton Ashworth, Alex Redington, and Bobby Calderwood (the 239 | authors of the original browser-repl), Brandon Bloom for pushing hard on making 240 | ClojureScript easier to use, and everyone else in #clojure and on the mailing 241 | list(s) that took the time to take Austin for a spin when it was still just a 242 | [gist](https://gist.github.com/cemerick/5091059) and then a `.patch` file. 243 | 244 | Copyright ©2013 [Chas Emerick](http://cemerick.com) and other contributors. 245 | 246 | ``` 247 | Portions copyright (c) Rich Hickey. All rights reserved. The use and 248 | distribution terms for this software are covered by the Eclipse 249 | Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 250 | which can be found in the file epl-v10.html at the root of this 251 | distribution. By using this software in any fashion, you are 252 | agreeing to be bound by the terms of this license. You must 253 | not remove this notice, or any other, from this software. 254 | ``` 255 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml* 6 | *.jar 7 | *.class 8 | .lein-deps-sum 9 | .lein-failures 10 | .lein-plugins 11 | .classpath 12 | .project 13 | .settings 14 | 15 | .externalToolBuilders 16 | .repl 17 | out 18 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/README.md: -------------------------------------------------------------------------------- 1 | # Sample project demonstrating real-world usage of [Austin](http://github.com/cemerick/austin)'s browser-connected REPL support 2 | 3 | If you're reading this on github, feel free to follow along, but it'll 4 | be a lot more fun if you clone [the 5 | repo](http://github.com/cemerick/austin) and get your hands dirty! 6 | 7 | Austin is a really just a significant refactoring of ClojureScript's 8 | standard browser-REPL, so [all of its 9 | tutorials](https://github.com/clojure/clojurescript/wiki/The-REPL-and-Evaluation-Environments) 10 | generally apply to Austin as well. However, Austin provides a workflow 11 | that I personally find much easier to use, especially if I want to have 12 | multiple browser-connected REPLs in flight at the same time. 13 | 14 | ## Running the sample app 15 | 16 | Assuming you've cloned [Austin's 17 | repo](https://github.com/cemerick/austin) to `$AUSTIN` (wherever that 18 | is), do this: 19 | 20 | 1. `cd` to `$AUSTIN/browser-connected-repl-sample`, and run: 21 | 22 | $ lein do cljsbuild once, repl 23 | 24 | This will compile the dummy sample ClojureScript namespace in 25 | `$AUSTIN/src/cljs`, which happens to require the ClojureScript 26 | browser-REPL client-side namespace. 27 | 28 | 2. Once you're in the REPL (it will start up in the sample app's main 29 | namespace, `cemerick.austin.bcrepl-sample`), evaluate `(run)`. That 30 | just starts jetty on port `8080`; if you open a browser to [that 31 | server](http://localhost:8080), you'll see this page (which the 32 | sample re-uses as its only content). 33 | 3. Create a new Austin ClojureScript REPL environment, like so: 34 | 35 | (def repl-env (reset! cemerick.austin.repls/browser-repl-env 36 | (cemerick.austin/repl-env))) 37 | 38 | This also `reset!`'s the new REPL environment into the 39 | `browser-repl-env` atom. The sample app uses code like this: 40 | 41 | [:script (cemerick.austin.repls/browser-connected-repl-js)] 42 | 43 | to drop a snippet of JavaScript into the page that will cause the 44 | browser-REPL to connect to whichever REPL environment is in 45 | `browser-repl-env`; your app should do likewise. Be sure to load 46 | the code returned by the `(browser-connected-repl-js)` call as the 47 | last JavaScript loaded by your app's page. 48 | 49 | Note that for snippet to work, you'll need to have a `cljs` file in 50 | your project that requires `clojure.browser.repl`. This project's 51 | `cljs` file [has exactly that](https://github.com/cemerick/austin/blob/master/browser-connected-repl-sample/src/cljs/cemerick/austin/bcrepl_sample.cljs) in the ns declaration: 52 | 53 | (ns cemerick.austin.bcrepl-sample 54 | (:require [clojure.browser.repl])) 55 | 56 | 4. Turn your Clojure REPL into a ClojureScript REPL tied to that REPL 57 | environment with 58 | 59 | (cemerick.austin.repls/cljs-repl repl-env) 60 | 61 | 5. Now that the ClojureScript REPL is ready, you need to load 62 | [http://localhost:8080](http://localhost:8080), or reload it if you 63 | brought it up before the REPL environment was created and `reset!` 64 | into the `browser-repl-env` atom. Once you do that, evaluate some 65 | ClojureScript to make sure your shiny new REPL is working, e.g. 66 | 67 | (js/alert "Salut!") 68 | 69 | (Note: if you see no response, try temporarily disabling browser 70 | extensions. A problem has been seen where the Google Voice 71 | extension in Chrome somehow prevents the browser from listening 72 | for packets from the REPL. See the full discussion in [this 73 | issue](https://github.com/cemerick/austin/issues/17). 74 | 75 | You can reload your app's page as many times as you like; it will 76 | re-connect on each page load to the same REPL environment. If you want 77 | to connect to a *different* REPL environment, just put the it into 78 | `browser-repl-env` prior to loading the page you'd like to have 79 | connected to it. At some point, Austin may provide a bit of 80 | ClojureScript that will allow you to choose (from within the browser) 81 | which REPL environment to which you'd like to connect… 82 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.cemerick/austin-repl-sample "NOT_DEPLOYED" 2 | :source-paths ["src/clj" "src/cljs"] 3 | :dependencies [[org.clojure/clojure "1.5.1"] 4 | [org.clojure/clojurescript "0.0-2202"] 5 | [ring "1.2.2"] 6 | [compojure "1.1.8"] 7 | [enlive "1.1.5"]] 8 | 9 | :profiles {:dev {:repl-options {:init-ns cemerick.austin.bcrepl-sample} 10 | :plugins [[com.cemerick/austin "0.1.4"] 11 | [lein-cljsbuild "1.0.3"]] 12 | :cljsbuild {:builds [{:source-paths ["src/cljs"] 13 | :compiler {:output-to "target/classes/public/app.js" 14 | :optimizations :simple 15 | :pretty-print true}}]}}}) 16 | 17 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sample project demonstrating real-world usage of Austin's browser-connected REPL 9 | support

10 |

If you're reading this on github, feel free to follow along, but 11 | it'll be a lot more fun if you clone the repo and get 13 | your hands dirty!

14 | 15 |

Austin is a really just a significant refactoring of ClojureScript's 16 | standard browser-REPL, so all 18 | of its tutorials generally apply to Austin as well. 19 | However, Austin provides a workflow that I personally find much 20 | easier to 21 | use, especially if I want to have multiple browser-connected REPLs 22 | in flight at the same time.

23 | 24 |

Running the sample app

25 | 26 | Assuming you've cloned Austin's repo to 28 | $AUSTIN (wherever that is), do this: 29 | 30 |
    31 |
  1. cd to 32 | $AUSTIN/browser-connected-repl-sample, and run: 33 |
    $ lein do cljsbuild once, repl
    This will compile the 34 | dummy sample ClojureScript namespace in 35 | $AUSTIN/src/cljs, which happens to require the 36 | ClojureScript browser-REPL client-side namespace.
  2. 37 |
  3. Once you're in the REPL (it will start up in the 38 | sample app's main namespace, 39 | cemerick.austin.bcrepl-sample), evaluate 40 | (run). That just starts jetty on port 41 | 8080; if you open a browser to that server, you'll 43 | see this page (which the sample re-uses as its only 44 | content).
  4. 45 |
  5. Create a new Austin ClojureScript REPL environment, 46 | like so: 47 |
    (def repl-env (reset! cemerick.austin.repls/browser-repl-env
    48 |                       (cemerick.austin/repl-env)))
    49 | This also reset!'s the new REPL environment into the 50 | browser-repl-env atom. The sample app uses 51 | code like this: 52 | 53 |
    [:script (cemerick.austin.repls/browser-connected-repl-js)]
    54 | 55 | to drop a snippet of JavaScript into the page that will cause 56 | the browser-REPL to connect to whichever REPL environment is in 57 | browser-repl-env; your app should do likewise.
  6. 58 |
  7. Turn your Clojure REPL into a ClojureScript REPL 59 | tied to that REPL environment with 60 |
    (cemerick.austin.repls/cljs-repl repl-env)
  8. 61 |
  9. Now that the ClojureScript REPL is ready, you need to load http://localhost:8080, or 63 | reload it if you brought it up before the REPL environment was 64 | created and reset! into the 65 | browser-repl-env atom. Once you do that, evaluate 66 | some ClojureScript to 67 | make sure your shiny new REPL 68 | is working, e.g.
    (js/alert "Salut!")
  10. 69 |
70 | 71 |

72 | You can reload your app's page as many times as you like; it will 73 | re-connect on each page load to the same REPL environment. If you want 74 | to connect to a different REPL environment, just put the 75 | it into browser-repl-env prior to loading the page you'd 76 | like to have connected to it. At some point, Austin may provide a 77 | bit of ClojureScript that will allow you to choose (from within the 78 | browser) which REPL environment to which you'd like to connect… 79 |

80 | 81 | 82 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/src/clj/cemerick/austin/bcrepl_sample.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.austin.bcrepl-sample 2 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 3 | [net.cgrand.enlive-html :as enlive] 4 | [compojure.route :refer (resources)] 5 | [compojure.core :refer (GET defroutes)] 6 | ring.adapter.jetty 7 | [clojure.java.io :as io])) 8 | 9 | (enlive/deftemplate page 10 | (io/resource "index.html") 11 | [] 12 | [:body] (enlive/append 13 | (enlive/html [:script (browser-connected-repl-js)]))) 14 | 15 | (defroutes site 16 | (resources "/") 17 | (GET "/*" req (page))) 18 | 19 | (defn run 20 | [] 21 | (defonce ^:private server 22 | (ring.adapter.jetty/run-jetty #'site {:port 8080 :join? false})) 23 | server) 24 | 25 | -------------------------------------------------------------------------------- /browser-connected-repl-sample/src/cljs/cemerick/austin/bcrepl_sample.cljs: -------------------------------------------------------------------------------- 1 | (ns cemerick.austin.bcrepl-sample 2 | (:require [clojure.browser.repl])) 3 | 4 | (defn hello 5 | [] 6 | (js/alert "hello")) 7 | 8 | (defn whoami 9 | [] 10 | (.-userAgent js/navigator)) 11 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.cemerick/austin "0.1.7-SNAPSHOT" 2 | :description "The ClojureScript browser-repl, rebuilt stronger, faster, easier." 3 | :url "http://github.com/cemerick/austin" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :min-lein-version "2.0.0" 7 | :source-paths ["src/clj"] 8 | :dependencies [[org.clojure/clojure "1.6.0"] 9 | [org.clojure/clojurescript "0.0-2665"] 10 | [com.cemerick/piggieback "0.1.5"]] 11 | 12 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 13 | 14 | :repositories {"oss-public" "https://oss.sonatype.org/content/groups/public/"} 15 | 16 | :scm {:url "git@github.com:cemerick/austin.git"} 17 | :pom-addition [:developers [:developer 18 | [:name "Chas Emerick"] 19 | [:url "http://cemerick.com"] 20 | [:email "chas@cemerick.com"] 21 | [:timezone "-5"]]]) 22 | -------------------------------------------------------------------------------- /src/clj/austin/plugin.clj: -------------------------------------------------------------------------------- 1 | (ns austin.plugin 2 | (:require [clojure.java.io :as io])) 3 | 4 | (def ^:private austin-version 5 | (-> (io/resource "META-INF/leiningen/com.cemerick/austin/project.clj") 6 | slurp 7 | read-string 8 | (nth 2))) 9 | 10 | (assert (string? austin-version) 11 | (str "Something went wrong, version of austin is not a string: " 12 | austin-version)) 13 | 14 | (defn middleware 15 | [project] 16 | (-> project 17 | (update-in [:dependencies] 18 | (fnil into []) 19 | [['com.cemerick/austin austin-version]]) 20 | (update-in [:repl-options :nrepl-middleware] 21 | (fnil into []) 22 | '[cemerick.piggieback/wrap-cljs-repl]) 23 | (update-in [:injections] 24 | (fnil into []) 25 | '[(require '[cemerick.austin.repls 26 | :refer (exec) 27 | :rename {exec austin-exec}])]))) 28 | -------------------------------------------------------------------------------- /src/clj/cemerick/austin.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Rich Hickey. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns cemerick.austin 10 | (:require [clojure.java.io :as io] 11 | [cljs.compiler :as comp] 12 | [cljs.closure :as cljsc] 13 | [cljs.env :as env] 14 | [cljs.repl :as repl]) 15 | (:import cljs.repl.IJavaScriptEnv 16 | java.net.InetSocketAddress 17 | (com.sun.net.httpserver HttpServer HttpHandler HttpExchange))) 18 | 19 | (declare handle-request) 20 | 21 | (defn- create-server 22 | [port] 23 | (doto (HttpServer/create (InetSocketAddress. port) 0) 24 | (.createContext "/" (reify HttpHandler 25 | (handle [this req] (handle-request req)))) 26 | (.setExecutor clojure.lang.Agent/soloExecutor) 27 | .start)) 28 | 29 | (defn default-server-port 30 | "Returns the port to which the Austin HTTP server will be bound by default, 31 | either when first used or when restarted via nullary call to `start-server`. 32 | The sources of configuration are, in order: 33 | 34 | * system property `cemerick.austin.default-server-port` 35 | * environment variable `AUSTIN_DEFAULT_SERVER_PORT` 36 | * default, 0 (autoselection of an open port)" 37 | [] 38 | (Integer/parseInt (or (System/getProperty "cemerick.austin.default-server-port") 39 | (System/getenv "AUSTIN_DEFAULT_SERVER_PORT") 40 | "0"))) 41 | 42 | (defonce 43 | ^{:doc "A deref-able that contains the HTTP server that services all Austin REPLs."} 44 | server 45 | (delay (create-server (default-server-port)))) 46 | 47 | (defn stop-server 48 | "Stops the (assumed to be running) HTTP server that services all Austin REPLS." 49 | [] 50 | (.stop @server 0)) 51 | 52 | (defn start-server 53 | "Starts the HTTP server that services all Austin REPLs. Optionally takes a 54 | [port] argument. Does not stop any already-running HTTP server, see 55 | `stop-server` for that. 56 | 57 | Note that Austin automatically initializes its HTTP server as a side effect of 58 | the first call to `get-browser-repl-port`; you don't have to call this 59 | before using Austin, unless you need the server running on a particular port." 60 | ([] 61 | (start-server (default-server-port))) 62 | ([port] 63 | ; once people are explicitly starting the server, any derefable in `server` 64 | ; is fine 65 | (alter-var-root #'server (fn [_] (atom (create-server port)))))) 66 | 67 | (defn get-browser-repl-port 68 | "Returns the port to which the Austin HTTP server is bound. If it is not 69 | already running, it will be started as a side effect of the first call to this 70 | function." 71 | [] 72 | (-> @server .getAddress .getPort)) 73 | 74 | (defn- send-response 75 | [^HttpExchange ex status string-body & {:keys [content-type] 76 | :or {content-type "text/html"}}] 77 | (let [utf8 (.getBytes string-body "UTF-8")] 78 | (doto ex 79 | (-> .getResponseHeaders (.putAll {"Server" ["ClojureScript REPL"] 80 | "Content-Type" [(str content-type "; charset=utf-8")]})) 81 | (.sendResponseHeaders status (count utf8)) 82 | (-> .getResponseBody (doto (.write utf8) .flush .close))))) 83 | 84 | (defn- send-404 85 | [ex path] 86 | (send-response ex 404 87 | (str "" 88 | "

Page not found

" 89 | "No page " path " found on this server." 90 | ""))) 91 | 92 | (def ^:private session-init {:return-value-fn nil 93 | :client-js nil 94 | :loaded-libs #{} 95 | :preloaded-libs #{} 96 | :open-exchange nil 97 | :exchange-promise nil 98 | :ordering nil 99 | :opts {} 100 | :*out* nil}) 101 | 102 | (defonce ^:private sessions (atom {})) 103 | 104 | (defn- deliver-exchange 105 | [session-id exch] 106 | (if-let [promise (-> @sessions (get session-id) :exchange-promise)] 107 | (do (swap! sessions update-in [session-id] assoc :open-exchange nil :exchange-promise nil) 108 | (deliver promise exch)) 109 | (swap! sessions assoc-in [session-id :open-exchange] exch))) 110 | 111 | (defn- open-exchange 112 | [session-id] 113 | (let [p (promise)] 114 | (if-let [exch (-> @sessions (get session-id) :open-exchange)] 115 | (do (swap! sessions assoc-in [session-id :open-exchange] nil) 116 | (deliver p exch)) 117 | (swap! sessions assoc-in [session-id :exchange-promise] p)) 118 | p)) 119 | 120 | (defn- set-return-value-fn 121 | "Save the return value function which will be called when the next 122 | return value is received." 123 | [session-id f] 124 | (swap! sessions (fn [old] (assoc-in old [session-id :return-value-fn] f)))) 125 | 126 | (defn- send-for-eval 127 | "Given a form and a return value function, send the form to the 128 | browser for evaluation. The return value function will be called 129 | when the return value is received." 130 | ([session-id form return-value-fn] 131 | (send-for-eval @(open-exchange session-id) session-id form return-value-fn)) 132 | ([exch session-id form return-value-fn] 133 | (set-return-value-fn session-id return-value-fn) 134 | (send-response exch 200 form :content-type "text/javascript"))) 135 | 136 | (defn- return-value 137 | "Called by the server when a return value is received." 138 | [session-id val] 139 | (when-let [f (-> @sessions (get session-id) :return-value-fn)] 140 | (f val))) 141 | 142 | (defn- repl-client-js [session-id] 143 | (if-let [session (get @sessions session-id)] 144 | (slurp @(:client-js session)) 145 | ; TODO maybe we can make -tear-down yank the REPL environment out of 146 | ; browser-repl-env automatically? 147 | (format ";console.error('Austin ClojureScript REPL session %s does not exist. Maybe you have a stale ClojureScript REPL environment in `cemerick.austin.repls/browser-repl-env`?');" 148 | session-id))) 149 | 150 | (defn- send-repl-client-page 151 | [^HttpExchange ex session-id] 152 | (let [url (format "http://%s/%s/repl" 153 | (-> ex .getRequestHeaders (get "Host") first) 154 | session-id)] 155 | (send-response ex 200 156 | (str " 157 | " 160 | "" 163 | "")))) 164 | 165 | (defn- send-repl-index 166 | [ex session-id] 167 | (let [url (format "http://%s/%s/repl" 168 | (-> ex .getRequestHeaders (get "Host") first) 169 | session-id)] 170 | (send-response ex 200 171 | (str " 172 | " 175 | "" 178 | "")))) 179 | 180 | (defn- send-static 181 | [ex session-id path] 182 | (let [opts (get @sessions session-id) 183 | st-dir (-> opts :opts :static-dir)] 184 | (if (and st-dir 185 | (not= "/favicon.ico" path)) 186 | (let [path (if (= "/" path) "/index.html" path)] 187 | (if-let [local-path (seq (for [x (if (string? st-dir) [st-dir] st-dir) 188 | :when (.exists (io/file (str x path)))] 189 | (str x path)))] 190 | (send-response ex 200 (slurp (first local-path)) :content-type 191 | (condp #(.endsWith %2 %1) path 192 | ".html" "text/html" 193 | ".css" "text/css" 194 | ".html" "text/html" 195 | ".jpg" "image/jpeg" 196 | ".js" "text/javascript" 197 | ".png" "image/png" 198 | "text/plain")) 199 | (send-404 ex path))) 200 | (send-404 ex path)))) 201 | 202 | (defmulti ^:private handle-post :type) 203 | 204 | (defmulti ^:private handle-get 205 | (fn [{:keys [http-exchange session-id path]}] 206 | (when session-id path))) 207 | 208 | (defmethod handle-post :ready 209 | [{:keys [session-id http-exchange]}] 210 | (swap! sessions #(update-in % [session-id] merge 211 | {:loaded-libs (-> % (get session-id) :preloaded-libs) 212 | :ordering (agent {:expecting nil :fns {}})})) 213 | (env/with-compiler-env 214 | (-> @sessions (get session-id) :opts ::env/compiler) 215 | (send-for-eval http-exchange session-id 216 | (cljsc/-compile 217 | '[(ns cljs.user) 218 | (set! *print-fn* clojure.browser.repl/repl-print)] {}) 219 | identity))) 220 | 221 | (defn- add-in-order 222 | [{:keys [expecting fns]} order f] 223 | {:expecting (or expecting order) :fns (assoc fns order f)}) 224 | 225 | (defn- run-in-order 226 | [{:keys [expecting fns]}] 227 | (loop [order expecting 228 | fns fns] 229 | (if-let [f (get fns order)] 230 | (do (f) 231 | (recur (inc order) (dissoc fns order))) 232 | {:expecting order :fns fns}))) 233 | 234 | (defn- constrain-order 235 | "Elements to be printed in the REPL will arrive out of order. Ensure 236 | that they are printed in the correct order." 237 | [session-id order f] 238 | (doto (-> @sessions (get session-id) :ordering) 239 | (send-off add-in-order order f) 240 | (send-off run-in-order))) 241 | 242 | (defmethod handle-post :print 243 | [{:keys [content order session-id http-exchange]}] 244 | (constrain-order session-id order 245 | (fn [] 246 | (binding [*out* (-> @sessions (get session-id) :*out*)] 247 | (print (read-string content))) 248 | (.flush *out*))) 249 | (send-response http-exchange 200 "ignore__")) 250 | 251 | (defmethod handle-post :result 252 | [{:keys [content order session-id http-exchange]}] 253 | (constrain-order session-id order 254 | (fn [] 255 | (return-value session-id content) 256 | (deliver-exchange session-id http-exchange)))) 257 | 258 | (defn- request-path 259 | [^HttpExchange req] 260 | (-> req .getRequestURI .getPath)) 261 | 262 | (defmethod handle-get "/start" 263 | [{:keys [http-exchange session-id]}] 264 | (send-repl-index http-exchange session-id)) 265 | 266 | (defmethod handle-get :default 267 | [{:keys [http-exchange session-id path]}] 268 | (if session-id 269 | (if path 270 | (send-static http-exchange session-id path) 271 | (send-repl-client-page http-exchange session-id)) 272 | (send-404 http-exchange (request-path http-exchange)))) 273 | 274 | (defn ^:private handle-request 275 | [^HttpExchange req] 276 | (let [[[_ session-id static-path]] (re-seq #"/(\d+)/repl(/.+)?" (request-path req))] 277 | (try 278 | (case (.getRequestMethod req) 279 | "GET" (handle-get {:path static-path 280 | :session-id session-id 281 | :http-exchange req}) 282 | "POST" (handle-post (assoc (-> req .getRequestBody io/reader slurp read-string) 283 | :http-exchange req 284 | :session-id session-id))) 285 | (catch Throwable t (.printStackTrace t))))) 286 | 287 | (defn- browser-eval 288 | "Given a string of JavaScript, evaluate it in the browser and return a map representing the 289 | result of the evaluation. The map will contain the keys :type and :value. :type can be 290 | :success, :exception, or :error. :success means that the JavaScript was evaluated without 291 | exception and :value will contain the return value of the evaluation. :exception means that 292 | there was an exception in the browser while evaluating the JavaScript and :value will 293 | contain the error message. :error means that some other error has occurred." 294 | [session-id form] 295 | (let [return-value (promise)] 296 | (send-for-eval session-id form (partial deliver return-value)) 297 | (let [ret @return-value] 298 | (try (read-string ret) 299 | (catch Exception e 300 | {:status :error 301 | :value (str "Could not read return value: " ret)}))))) 302 | 303 | (defn- load-javascript 304 | "Accepts a REPL environment, a list of namespaces, and a URL for a 305 | JavaScript file which contains the implementation for the list of 306 | namespaces. Will load the JavaScript file into the REPL environment 307 | if any of the namespaces have not already been loaded from the 308 | ClojureScript REPL." 309 | [{:keys [session-id] :as repl-env} ns-list url] 310 | (let [missing (remove (-> @sessions (get session-id) :loaded-libs) ns-list)] 311 | (when (seq missing) 312 | (browser-eval session-id (slurp url)) 313 | (swap! sessions update-in [session-id :loaded-libs] (partial apply conj) missing)))) 314 | 315 | (defrecord BrowserEnv [] 316 | repl/IJavaScriptEnv 317 | (-setup [this options] 318 | (swap! sessions update-in [(:session-id this) :*out*] (constantly *out*)) 319 | (require 'cljs.repl.reflect) 320 | (some-> this :src repl/analyze-source) 321 | (comp/with-core-cljs options)) 322 | (-evaluate [this _ _ js] (browser-eval (:session-id this) js)) 323 | (-load [this ns url] (load-javascript this ns url)) 324 | (-tear-down [this] 325 | (swap! sessions dissoc (:session-id this)))) 326 | 327 | ; squelch default verbose record printing so we don't kill people's scrollback 328 | (defmethod print-method BrowserEnv [env ^java.io.Writer w] 329 | (.write w (str env))) 330 | (defmethod print-dup BrowserEnv [o w] 331 | (print-method o w)) 332 | (#'clojure.pprint/use-method 333 | clojure.pprint/simple-dispatch 334 | BrowserEnv 335 | #'clojure.pprint/pprint-simple-default) 336 | 337 | (defn- compile-client-js [opts] 338 | (cljsc/build '[(ns clojure.browser.repl.client 339 | (:require [goog.events :as event] 340 | [clojure.browser.repl :as repl])) 341 | (defn start [url] 342 | (event/listen js/window 343 | "load" 344 | (fn [] 345 | (repl/start-evaluator url))))] 346 | ; the options value used as an ifn somewhere in cljs.closure :-/ 347 | (-> (into {} opts) 348 | ; TODO why isn't the :working-dir option for the brepl env 349 | ; called :output-dir in the first place? 350 | (assoc :output-dir (:working-dir opts)) 351 | (dissoc :source-map)))) 352 | 353 | (defn- create-client-js-file [opts file-path] 354 | (let [file (io/file file-path)] 355 | (when (not (.exists file)) 356 | (spit file (with-out-str (compile-client-js (assoc opts :output-to :print))))) 357 | file)) 358 | 359 | (defn- provides-and-requires 360 | "Return a flat list of all provided and required namespaces from a 361 | sequence of IJavaScripts." 362 | [deps] 363 | (flatten (mapcat (juxt :provides :requires) deps))) 364 | 365 | (defn- always-preload 366 | "Return a list of all namespaces which are always loaded into the browser 367 | when using a browser-connected REPL." 368 | [] 369 | (let [cljs (provides-and-requires (cljsc/cljs-dependencies {} ["clojure.browser.repl"])) 370 | goog (provides-and-requires (cljsc/js-dependencies {} cljs))] 371 | (disj (set (concat cljs goog)) nil))) 372 | 373 | (defn repl-env 374 | "Create a browser-connected REPL environment. 375 | 376 | Options: 377 | 378 | session-id: The id of the (pre-existing) session to bind to 379 | working-dir: The directory where the compiled REPL client JavaScript will 380 | be stored. Defaults to \".repl\". 381 | serve-static: Should the REPL server attempt to serve static content? 382 | Defaults to true. 383 | static-dir: List of directories to search for static content. Defaults to 384 | [\".\" \"out/\"]. 385 | preloaded-libs: List of namespaces that should not be sent from the REPL server 386 | to the browser. This may be required if the browser is already 387 | loading code and reloading it would cause a problem. 388 | optimizations: The level of optimization to use when compiling the client 389 | end of the REPL. Defaults to :simple. 390 | host: The host URL on which austin will run the clojurescript repl. 391 | Defaults to \"localhost\". 392 | src: The source directory containing user-defined cljs files. Used to 393 | support reflection. Defaults to \"src/\". 394 | " 395 | [& {:as opts}] 396 | {:pre [(or (not (contains? opts :session-id)) 397 | (string? (:session-id opts)))]} 398 | (env/with-compiler-env (env/default-compiler-env opts) 399 | (let [opts (merge (BrowserEnv.) 400 | {:optimizations :simple 401 | :working-dir ".repl" 402 | :serve-static true 403 | :static-dir ["." "out/"] 404 | :preloaded-libs [] 405 | :src "src/" 406 | :host "localhost" 407 | :source-map true 408 | :session-id (str (rand-int 9999)) 409 | ::env/compiler env/*compiler*} 410 | opts) 411 | session-id (:session-id opts) 412 | repl-url (format "http://%s:%s/%s/repl" (:host opts) (get-browser-repl-port) session-id) 413 | opts (assoc opts 414 | :repl-url repl-url 415 | :entry-url (str repl-url "/start") 416 | :working-dir (str (:working-dir opts) "/" session-id)) 417 | preloaded-libs (set (concat (always-preload) 418 | (map str (:preloaded-libs opts))))] 419 | (swap! sessions update-in [session-id] #(merge %2 %) 420 | (assoc session-init 421 | :ordering (agent {:expecting nil :fns {}}) 422 | :opts opts 423 | :preloaded-libs preloaded-libs 424 | :loaded-libs preloaded-libs 425 | :client-js (future (create-client-js-file 426 | opts 427 | (io/file (:working-dir opts) "client.js"))))) 428 | (println (str "Browser-REPL ready @ " (:entry-url opts))) 429 | opts))) 430 | 431 | ; an IJavaScriptEnv that delegates to another [browser-env], but also manages 432 | ; the lifecycle of an external java.lang.Process that actually hosts evalution 433 | (deftype DelegatingExecEnv [browser-env command ^:volatile-mutable process] 434 | cljs.repl/IJavaScriptEnv 435 | (-setup [this options] 436 | (cljs.repl/-setup browser-env options) 437 | (let [command (into-array String (concat command [(:entry-url browser-env)]))] 438 | (set! process (try 439 | (.. Runtime getRuntime (exec command)) 440 | (catch Exception e 441 | (throw (java.io.IOException. 442 | (str "Failed to exec \"" (clojure.string/join " " command) "\"\n" 443 | "Error was:\n " (.getMessage e) "\n"))))))) 444 | this) 445 | (-evaluate [this a b c] (cljs.repl/-evaluate browser-env a b c)) 446 | (-load [this ns url] (cljs.repl/-load browser-env ns url)) 447 | (-tear-down [_] 448 | (cljs.repl/-tear-down browser-env) 449 | (.destroy process)) 450 | 451 | clojure.lang.ILookup 452 | (valAt [_ k] (get browser-env k)) 453 | (valAt [_ k default] (get browser-env k default)) 454 | 455 | ; here so that (into {} env) will work, necessary for turning this env into cljsc option map 456 | clojure.lang.Seqable 457 | (seq [_] (seq browser-env))) 458 | 459 | (defn exec-env* 460 | [browser-repl-env command+args] 461 | {:pre [(every? string? command+args)]} 462 | (DelegatingExecEnv. browser-repl-env command+args nil)) 463 | 464 | (defn exec-env 465 | "Create a browser-REPL environment backed by an external javascript runtime 466 | launched via exec. 467 | 468 | Accepts all of the arguments supported by `repl-env`, 469 | plus an optional :exec-cmds value, which, if provided, must be a seq of strings 470 | that constitute the command to be executed when the browser-REPL is set up. 471 | (The :entry-url of the browser-repl will be passed as an additional argument in 472 | this command.) The default :exec-cmds is 473 | 474 | [\"phantomjs\" \"/path/to/generated-temp-phantomjs-script.js\"] 475 | 476 | e.g. to start a browser-repl in the background using Chrome on OS X, 477 | evaluate: 478 | 479 | (exec-env :exec-cmds [\"open\" \"-ga\" \"/Applications/Google Chrome.app\"]) 480 | 481 | If you are using a different _phantomjs-compatible_ headless browser 482 | implementation (e.g. slimerjs, or perhaps your package manager installs 483 | phantomjs with a different name?), you can pass the name of that binary 484 | as :phantom-cmd, or a series of arguments to start a phantomjs-compatible process 485 | as :phantom-cmds, e.g.: 486 | 487 | (exec-env :phantom-cmd \"slimerjs\") 488 | (exec-env :phantom-cmds [\"xvfb-run\" \"slimerjs\"])" 489 | [& {:keys [exec-cmds phantom-cmd phantom-cmds] :as args}] 490 | (let [exec-command (or exec-cmds 491 | (conj (or phantom-cmds 492 | [(or phantom-cmd "phantomjs")]) 493 | (let [f (doto (java.io.File/createTempFile "phantomjs_repl" ".js") 494 | .deleteOnExit 495 | (spit (str "var page = require('webpage').create();" 496 | "page.open(require('system').args[1]);")))] 497 | (.getAbsolutePath f)))) 498 | benv (apply repl-env (apply concat (dissoc args :exec-cmds)))] 499 | (exec-env* benv exec-command))) 500 | 501 | ;; TODO unconvinced of the utility of the reflection stuff 502 | ;; In any case, it can't be used here, explicitly depends on cljs.browser.repl 503 | ; Get reflection handlers hooked up. Presumably we want to do this all the time? 504 | #_(require 'cljs.repl.reflect) 505 | 506 | -------------------------------------------------------------------------------- /src/clj/cemerick/austin/repls.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.austin.repls 2 | (:require [cemerick.austin :refer (exec-env)] 3 | [clojure.tools.nrepl.middleware.interruptible-eval :as nrepl-eval] 4 | [cemerick.piggieback :as pb] 5 | cljs.repl)) 6 | 7 | (def browser-repl-env 8 | "An atom into which you can `reset!` the Austin REPL environment to which you 9 | want your browser-connected REPLs to connect. (This is strictly a convenience, 10 | you can easily achieve the same thing by other means without touching this 11 | atom.) A typical usage pattern might be: 12 | 13 | In your nREPL (or other REPL implementation) session: 14 | 15 | (def repl-env (reset! cemerick.austin.repls/browser-repl-env 16 | (cemerick.austin/repl-env))) 17 | (cemerick.austin.repls/cljs-repl repl-env) 18 | 19 | And, somewhere in your webapp (demonstrating using hiccup markup, but whatever 20 | you use to generate HTML will work): 21 | 22 | [:html 23 | ; ... etc ... 24 | [:body [:script (cemerick.austin.repls/browser-connected-repl-js)]]] 25 | 26 | `browser-connected-repl-js` uses the REPL environment in this atom to construct 27 | a JavaScript string that will connect the browser runtime to that REPL environment 28 | on load. 29 | 30 | When you want your app to connect to a different REPL environment, just 31 | `reset!` `cemerick.austin.repls/browser-repl-env` again." 32 | (atom nil)) 33 | 34 | (defn browser-connected-repl-js 35 | "Uses the REPL environment in `browser-repl-env` to construct 36 | a JavaScript string that will connect the browser runtime to that REPL environment 37 | on load. See `browser-repl-env` docs for more." 38 | [] 39 | (when-let [repl-url (:repl-url @browser-repl-env)] 40 | (format ";goog.require('clojure.browser.repl');clojure.browser.repl.connect.call(null, '%s');" 41 | repl-url))) 42 | 43 | (defn cljs-repl 44 | "Same as `cljs.repl/repl`, except will use the appropriate REPL entry point 45 | (Piggieback's `cljs-repl` or `cljs.repl/repl`) based on the the current 46 | environment (i.e. whether nREPL is being used or not, respectively)." 47 | [repl-env & options] 48 | (if (thread-bound? #'nrepl-eval/*msg*) 49 | (apply pb/cljs-repl :repl-env repl-env options) 50 | (apply cljs.repl/repl repl-env options))) 51 | 52 | (defn exec 53 | "Starts a ClojureScript REPL using Austin's `exec-env` REPL environment 54 | using `cljs-repl` in this namespace (and so can be used whether you're using 55 | nREPL or not). All arguments are passed on to `exec-env` without 56 | modification." 57 | [& exec-env-args] 58 | (cljs-repl (apply exec-env exec-env-args))) 59 | --------------------------------------------------------------------------------