├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── boot.properties ├── build.boot ├── doc └── hud-messages.md ├── src └── adzerk │ ├── boot_reload.clj │ └── boot_reload │ ├── client.cljs │ ├── connection.cljs │ ├── display.cljs │ ├── reload.cljs │ ├── server.clj │ ├── util.clj │ └── websocket.cljs ├── test.sh └── test └── adzerk └── boot_reload ├── server_test.clj └── util_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | ### BOOT ####################################################################### 2 | 3 | /.boot/ 4 | 5 | ### LEININGEN ################################################################## 6 | 7 | .lein-* 8 | 9 | ### MAVEN (include pom.xml in /base) ########################################### 10 | 11 | *.jar 12 | *.war 13 | pom.xml 14 | pom.xml.asc 15 | 16 | ### NREPL ###################################################################### 17 | 18 | .repl-* 19 | .nrepl-* 20 | 21 | ### JAVA ####################################################################### 22 | 23 | /hs_err_pid*.log 24 | 25 | ### OSX ######################################################################## 26 | 27 | .DS_Store 28 | 29 | ### EMACS ###################################################################### 30 | 31 | [#]*[#] 32 | 33 | ### VIM ######################################################################## 34 | 35 | *.swn 36 | *.swo 37 | *.swp 38 | 39 | ### PROJECT #################################################################### 40 | 41 | /zzz/ 42 | /target/ 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | script: ./test.sh 4 | install: 5 | - mkdir -p ~/bin 6 | - export PATH=~/bin:$PATH 7 | - curl -L https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh -o ~/bin/boot 8 | - chmod +x ~/bin/boot 9 | jdk: 10 | - oraclejdk8 11 | - oraclejdk9 12 | cache: 13 | directories: 14 | - $HOME/.m2 15 | - $HOME/.boot/cache 16 | - $HOME/bin 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.1 (2020-03-13) 2 | 3 | - Fix compatibility with ClojureScript 1.10.741 and up ([#127](https://github.com/adzerk-oss/boot-reload/pull/127)) 4 | 5 | (Release isn't gpg signed as Boot doesn't setup gpg file information for Clojars 6 | correctly) 7 | 8 | ## 0.6.0 (2018-06-30) 9 | 10 | - Update dependencies 11 | - jsloader/load replaced with safeLoad ([#122](https://github.com/adzerk-oss/boot-reload/pull/122)) 12 | - hide popup scrollbar if not needed ([#123](https://github.com/adzerk-oss/boot-reload/pull/123)) 13 | 14 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.5.2...0.6.0)** 15 | 16 | ## 0.5.2 (2017-08-17) 17 | 18 | - Fix for using with latest Closure library (which next ClojureScript uses) 19 | 20 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.5.1...0.5.2)** 21 | 22 | ## 0.5.1 (5.2.2017) 23 | 24 | - Munge reserved JS names in boot-reload client namespace (Fixes [#113](https://github.com/adzerk-oss/boot-reload/issues/113)) 25 | 26 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.5.0...0.5.1)** 27 | 28 | ## 0.5.0 (3.1.2017) 29 | 30 | - Close HttpKit server used for Reload WebSocket connection when task is closed ([#107](https://github.com/adzerk-oss/boot-reload/issues/107)) 31 | - Uses `localhost` as fallback WebSocket host, but also shows the warning if the 32 | host can't be properly detected (.e.g when serving from `file:` URI). ([#92](https://github.com/adzerk-oss/boot-reload/issues/92), [#98](https://github.com/adzerk-oss/boot-reload/issues/98)) 33 | - Checks if `window` methods exists before trying to access them, to prevent problems with React-native and other 34 | environments where they don't exist ([#110](https://github.com/adzerk-oss/boot-reload/pull/110)) 35 | - Changed mechanism used by Boot-reload to inject Cljs code to the build. 36 | - This probably doesn't change anything, but there is small change this 37 | could fix some rare, hard to reproduce errors about Boot-reload Cljs 38 | file not being found. 39 | - Use `.cljs.edn` path for Boot-reload client namespace name 40 | - Read client options from `:boot-reload` property in `.cljs.edn` to allow different values for different builds 41 | 42 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.13...0.5.0)** 43 | 44 | ## 0.4.13 (18.10.2016) 45 | 46 | - Generic way tasks to send messages to HUD: https://github.com/adzerk-oss/boot-reload/blob/master/doc/hud-messages.md ([#72](https://github.com/adzerk-oss/boot-reload/issues/72)) 47 | 48 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.12...0.4.13)** 49 | 50 | ## 0.4.12 (20.7.2016) 51 | 52 | - Fix CLJS logo issues with CLJS 1.9.76 and below ([#87](https://github.com/adzerk-oss/boot-reload/issues/87)) 53 | 54 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.11...0.4.12)** 55 | 56 | ## 0.4.11 (27.6.2016) 57 | 58 | - Fix CLJS logo reloading issues with CLJS 1.9.76 and up ([#84](https://github.com/adzerk-oss/boot-reload/pull/84)) 59 | 60 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.10...0.4.11)** 61 | 62 | ## 0.4.10 (25.6.2016) 63 | 64 | - Use absolute paths when reloading files ([#83](https://github.com/adzerk-oss/boot-reload/pull/83)) 65 | 66 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.9...0.4.10)** 67 | 68 | ## 0.4.9 (22.6.2016) 69 | 70 | - Added `:only-by-re` option to control which files should trigger reload ([#70](https://github.com/adzerk-oss/boot-reload/pull/70)) 71 | - Fix reload-css/img through changed-href? ([#80](https://github.com/adzerk-oss/boot-reload/pull/80)) 72 | 73 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.8...0.4.9)** 74 | 75 | ## 0.4.8 (25.5.2016) 76 | 77 | - Fixed Boot 2.6 compatibility 78 | - Added `ws-port` option ([#73](https://github.com/adzerk-oss/boot-reload/pull/73)) to set WS port for the client 79 | 80 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.7...0.4.8)** 81 | 82 | ## 0.4.7 (3.4.2016) 83 | 84 | - Fixed URIs in Windows ([#71](https://github.com/adzerk-oss/boot-reload/pull/71)) 85 | 86 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.6...0.4.7)** 87 | 88 | ## 0.4.6 (24.3.2016) 89 | 90 | - Default to current host if `ws-host` is not set ([#62](https://github.com/adzerk-oss/boot-reload/pull/62)) 91 | - Add new `cljs-asset-path` option to prepend a path before URL ([#65](https://github.com/adzerk-oss/boot-reload/pull/65)) 92 | 93 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.5...0.4.6)** 94 | 95 | ## 0.4.5 (31.1.2016) 96 | 97 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.4...0.4.5)** 98 | 99 | - Fixed use with React Native ([#58](https://github.com/adzerk-oss/boot-reload/pull/58)) 100 | - Added `disable-hud` option ([#55](https://github.com/adzerk-oss/boot-reload/pull/55)) 101 | - Load changed JS files in strict order ([#53](https://github.com/adzerk-oss/boot-reload/pull/53)) 102 | - This might make reload a bit slower, but will guarantee that files are evaluated in dependency order 103 | 104 | ## 0.4.4 (11.1.2015) 105 | 106 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.3...0.4.4)** 107 | 108 | - Fixed build 109 | 110 | ## 0.4.3 (10.1.2015) 111 | 112 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.2...0.4.3)** 113 | 114 | - **Broken release** 115 | - Added `asset-host` option. It can be used to manually set where the changed files 116 | are reloaded. ([#51](https://github.com/adzerk-oss/boot-reload/issues/51), 117 | [#54](https://github.com/adzerk-oss/boot-reload/issues/54)) 118 | - Increased HUD `z-index`. 119 | 120 | ## 0.4.2 (8.11.2015) 121 | 122 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.1...0.4.2)** 123 | 124 | - Do not try reloading the main shim file [#46](https://github.com/adzerk-oss/boot-reload/issues/46) 125 | - Fixed asset-path for non-root uses [#43](https://github.com/adzerk-oss/boot-reload/issues/43) 126 | 127 | ## 0.4.1 (11.10.2015) 128 | 129 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.4.0...0.4.1)** 130 | 131 | - Cap maximum HUD height 132 | - Guard against cases where HUD container is removed from DOM 133 | 134 | ## 0.4.0 (4.10.2015) 135 | 136 | **[compare](https://github.com/adzerk-oss/boot-reload/compare/0.3.2...0.4.0)** 137 | 138 | - Added HUD 139 | - **Requires**: `[adzerk/boot-cljs "1.7.48.5"]` 140 | - Reads warning and exception information set by Boot-cljs and displays 141 | the warnings and exceptions in the browser. 142 | - Added `open-file` option which can be set to run a command when a warning 143 | or exception is clicked in browser. This should support e.g. Vim and Emacs. 144 | - Added `secure` option which can be used to tell client to connect using 145 | `wss` protocol. 146 | 147 | ## 0.3.2 (28.8.2015) 148 | 149 | - Add `ws-host` option. Can be used to set the address to which the client connects to. 150 | - Add `ids` option. Can be used to enable reload for only specific Cljs builds. 151 | - Fix #23 152 | 153 | ## 0.3.1 (14.6.2015) 154 | 155 | - Added `:asset-path` option 156 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Contributions are welcome. 4 | 5 | Please file bug reports and feature requests to https://github.com/adzerk-oss/boot-reload/issues. 6 | 7 | ## Making changes 8 | 9 | * Fork the repository on Github 10 | * Create a topic branch from where you want to base your work (usually the master branch) 11 | * Check the formatting rules from existing code (no trailing whitepace, mostly default indentation) 12 | * Ensure any new code is well-tested, and if possible, any issue fixed is covered by one or more new tests 13 | * Push your code to your fork of the repository 14 | * Make a Pull Request 15 | 16 | ## Commit messages 17 | 18 | 1. Separate subject from body with a blank line 19 | 2. Limit the subject line to 50 characters 20 | 3. Capitalize the subject line 21 | 4. Do not end the subject line with a period 22 | 5. Use the imperative mood in the subject line 23 | - "Add x", "Fix y", "Support z", "Remove x" 24 | 6. Wrap the body at 72 characters 25 | 7. Use the body to explain what and why vs. how 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of Washington and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boot-reload [![Downloads](https://jarkeeper.com/adzerk/boot-reload/downloads.svg)](https://jarkeeper.com/adzerk/boot-reload) [![Build Status](https://travis-ci.org/adzerk-oss/boot-reload.svg?branch=master)](https://travis-ci.org/adzerk-oss/boot-reload) [![Dependencies Status](https://jarkeeper.com/adzerk/boot-reload/status.svg)](https://jarkeeper.com/adzerk/boot-reload) 2 | 3 | [![Clojars Project][2]][3] 4 | 5 | [Boot][1] task to automatically reload resources in the browser when files in 6 | the project change. Communication with the client is via websockets. 7 | 8 | * Provides the `reload` task 9 | * Reload client can show warnings and exceptions **heads-up display** 10 | * Other tasks can use [this API](./doc/hud-messages.md) to send messages to Boot-reload 11 | * Supports [boot-cljs] (Requires `[adzerk/boot-cljs "1.7.48-5"]`) 12 | * Supports [less4clj] and [sass4clj] 13 | 14 | ## Usage 15 | 16 | Add dependency to `build.boot` and `require` the task: 17 | 18 | ```clj 19 | (set-env! :dependencies '[[adzerk/boot-reload "X.Y.Z" :scope "test"]]) 20 | 21 | (require '[adzerk.boot-reload :refer [reload]]) 22 | ``` 23 | 24 | Add the task to your development pipeline **before `(cljs ...)`**: 25 | 26 | ```clj 27 | (deftask dev [] 28 | (comp 29 | (reload) 30 | (cljs))) 31 | ``` 32 | 33 | ## Additional Info 34 | 35 | You can see the options available on the command line: 36 | 37 | ```bash 38 | boot reload --help 39 | ``` 40 | 41 | or in the REPL: 42 | 43 | ```clj 44 | boot.user=> (doc reload) 45 | ``` 46 | 47 | ## Examples 48 | 49 | For in-depth, up-to-date examples of how to use `reload` in development, see 50 | [Boot templates and example projects](https://github.com/clojure/clojurescript/wiki#boot) 51 | in the ClojureScript wiki. 52 | 53 | ## License 54 | 55 | Copyright © 2014 Adzerk
56 | Copyright © 2015-2017 Juho Teperi 57 | 58 | Distributed under the Eclipse Public License either version 1.0 or (at 59 | your option) any later version. 60 | 61 | [1]: https://github.com/boot-clj/boot 62 | [2]: http://clojars.org/adzerk/boot-reload/latest-version.svg?cache=6 63 | [3]: http://clojars.org/adzerk/boot-reload 64 | [boot-cljs]: https://github.com/boot-clj/boot-cljs 65 | [sass4clj]: https://github.com/Deraen/sass4clj 66 | [less4clj]: https://github.com/Deraen/less4clj 67 | -------------------------------------------------------------------------------- /boot.properties: -------------------------------------------------------------------------------- 1 | #http://boot-clj.com 2 | #Thu Dec 24 12:31:47 EET 2015 3 | BOOT_VERSION=2.8.1 4 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :resource-paths #{"src"} 3 | :source-paths #{"test"} 4 | :dependencies '[[org.clojure/clojure "1.9.0" :scope "provided"] 5 | [http-kit "2.3.0" :scope "test"] 6 | [metosin/bat-test "0.4.0" :scope "test"]]) 7 | 8 | (require '[metosin.bat-test :refer [bat-test]]) 9 | 10 | (def +version+ "0.6.1") 11 | 12 | (task-options! 13 | pom {:project 'adzerk/boot-reload 14 | :version +version+ 15 | :description "Boot task to automatically reload page resources in the browser." 16 | :url "https://github.com/adzerk/boot-reload" 17 | :scm {:url "https://github.com/adzerk/boot-reload"} 18 | :license {"Eclipse Public License" 19 | "http://www.eclipse.org/legal/epl-v10.html"}}) 20 | 21 | (deftask build [] 22 | (comp 23 | (pom) 24 | (jar) 25 | (install))) 26 | 27 | (ns-unmap *ns* 'test) 28 | 29 | (deftask test [] 30 | (comp 31 | (bat-test))) 32 | 33 | (deftask dev [] 34 | (comp 35 | (watch) 36 | (repl :server true) 37 | (test) 38 | (build) 39 | (target))) 40 | 41 | (deftask deploy [] 42 | (comp 43 | (test) 44 | (build) 45 | (push :repo "clojars" 46 | ;; Clojars needs different deploy url. 47 | :repo-map {:url "https://clojars.org/repo"} 48 | ;; Boot uses old pomegranate and doesn't setup checksums for gpg files 49 | ;; clojars now needs. 50 | ; :gpg-sign (not (.endsWith +version+ "-SNAPSHOT")) 51 | ))) 52 | -------------------------------------------------------------------------------- /doc/hud-messages.md: -------------------------------------------------------------------------------- 1 | # HUD messages 2 | 3 | **Subject to change in following versions.** 4 | 5 | Example implementation: https://github.com/Deraen/less4clj/blob/master/boot-less/src/deraen/boot_less.clj#L104-L111 6 | 7 | Shared data format for exception data or warning. 8 | 9 | ```clj 10 | {:message "Message" 11 | :file "src/css/main.less.less" 12 | :line 10 13 | :column 1} 14 | ``` 15 | 16 | ## Exceptions 17 | 18 | - *Legacy* `ex-data` contains `:from` property with value `:boot-cljs` 19 | - `ex-data` contains property `:adzerk.boot-reload/exception` with truthy value 20 | 21 | ## Warnings 22 | 23 | - *Legacy* `:adzerk.boot-cljs/warnings` from `.cljs.edn` file fileset metadata 24 | - `:adzerk.boot-reload/warnings` from fileset metadata of any file 25 | 26 | ## TODO 27 | 28 | - Support error/warning context, should probably contain few lines of code near the 29 | error, and information to display proper line numbers for those lines (e.g. start line) 30 | - Should support showing multiple errors, e.g. boot-less can return multiple errors 31 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload 2 | {:boot/export-tasks true} 3 | (:require 4 | [boot.core :as b] 5 | [clojure.java.io :as io] 6 | [clojure.set :as set] 7 | [clojure.string :as string] 8 | [boot.pod :as pod] 9 | [boot.file :as file] 10 | [boot.util :as util] 11 | [boot.core :refer :all] 12 | [boot.from.backtick :as bt] 13 | [adzerk.boot-reload.util :as rutil])) 14 | 15 | (def ^:private deps '[[http-kit "2.3.0"]]) 16 | 17 | (defn- make-pod [] 18 | (future (-> (get-env) (update-in [:dependencies] into deps) pod/make-pod))) 19 | 20 | (defn- changed [before after only-by-re static-files] 21 | (letfn [(maybe-filter-by-re [files] 22 | (if only-by-re 23 | (by-re only-by-re files) 24 | files))] 25 | (when before 26 | (->> (fileset-diff before after :hash) 27 | output-files 28 | maybe-filter-by-re 29 | (sort-by :dependency-order) 30 | (map tmp-path) 31 | (remove static-files))))) 32 | 33 | (defn- start-server [pod {:keys [ip port ws-host ws-port secure?] :as opts}] 34 | (let [{:keys [ip port]} (pod/with-call-in pod (adzerk.boot-reload.server/start ~opts)) 35 | listen-host (cond (= ip "0.0.0.0") "localhost" :else ip) 36 | client-host (cond ws-host ws-host (= ip "0.0.0.0") "localhost" :else ip) 37 | proto (if secure? "wss" "ws")] 38 | (util/info "Starting reload server on %s\n" (format "%s://%s:%d" proto listen-host port)) 39 | (format "%s://%s:%d" proto client-host (or ws-port port)))) 40 | 41 | (defn- write-cljs! [tmp client-ns url ws-host {:keys [on-jsload asset-host]}] 42 | (util/info "Writing %s to connect to %s...\n" (.getPath (rutil/ns->file client-ns "cljs")) url) 43 | (let [out (doto (rutil/ns->file tmp client-ns "cljs") 44 | io/make-parents)] 45 | (->> (bt/template 46 | ((ns ~(symbol client-ns) 47 | (:require 48 | [adzerk.boot-reload.client :as client] 49 | ~@(when on-jsload [(symbol (namespace on-jsload))]))) 50 | (client/connect ~url {:on-jsload #(~(or on-jsload '+)) 51 | :asset-host ~asset-host 52 | :ws-host ~ws-host}))) 53 | (map pr-str) 54 | (interpose "\n") 55 | (apply str) 56 | (spit out)))) 57 | 58 | (defn- send-visual! [pod messages] 59 | (when-not (empty? messages) 60 | (pod/with-call-in pod 61 | (adzerk.boot-reload.server/send-visual! 62 | ~messages)))) 63 | 64 | (defn- send-changed! [pod asset-path cljs-asset-path target-path changed] 65 | (when-not (empty? changed) 66 | (pod/with-call-in pod 67 | (adzerk.boot-reload.server/send-changed! 68 | {:target-path ~target-path 69 | :asset-path ~asset-path 70 | :cljs-asset-path ~cljs-asset-path} 71 | ~changed)))) 72 | 73 | (defn- add-init! 74 | [client-ns spec out-file cljs-edn-path] 75 | (let [client-ns (symbol client-ns)] 76 | (when (not= :nodejs (-> spec :compiler-options :target)) 77 | (util/info "Adding :require %s to %s...\n" client-ns cljs-edn-path) 78 | (io/make-parents out-file) 79 | (-> spec 80 | (update-in [:require] conj client-ns) 81 | pr-str 82 | ((partial spit out-file)))))) 83 | 84 | (defn- relevant-cljs-edn [fileset ids] 85 | (let [relevant (map #(str % ".cljs.edn") ids) 86 | f (if ids 87 | #(b/by-path relevant %) 88 | #(b/by-ext [".cljs.edn"] %))] 89 | (-> fileset b/input-files f))) 90 | 91 | (deftask reload 92 | "Live reload of page resources in browser via websocket. 93 | 94 | The default configuration starts a websocket server on a random available 95 | port on localhost. 96 | 97 | Open-file option takes three arguments: line number, column number, relative 98 | file path. You can use positional arguments if you need different order. 99 | Arguments shouldn't have spaces. 100 | Examples: 101 | vim --remote +norm%sG%s| %s 102 | emacsclient -n +%s:%s %s 103 | 104 | Client options can also be set in .cljs.edn file, using property :boot-reload, e.g. 105 | :boot-reload {:on-jsload frontend.core/reload}" 106 | 107 | [b ids BUILD_IDS #{str} "Only inject reloading into these builds (= .cljs.edn files)" 108 | ;; Websocket Server 109 | i ip ADDR str "The IP address for the websocket server to listen on. (optional)" 110 | p port PORT int "The port the websocket server listens on. (optional)" 111 | _ ws-port PORT int "The port the websocket will connect to. (optional)" 112 | w ws-host WSADDR str "The websocket host clients connect to. Defaults to current host. (optional)" 113 | s secure bool "Flag to indicate whether the client should connect via wss. Defaults to false." 114 | ;; Other Configuration 115 | j on-jsload SYM sym "The callback to call when JS files are reloaded. (client, optional)" 116 | _ asset-host HOST str "The asset-host where to load files from. Defaults to host of opened page. (client, optional)" 117 | a asset-path PATH str "Sets the output directory for temporary files used during compilation. (optional)" 118 | c cljs-asset-path PATH str "The actual asset path. This is added to the start of reloaded urls. (optional)" 119 | o open-file COMMAND str "The command to run when warning or exception is clicked on HUD. Passed to format. (optional)" 120 | v disable-hud bool "Toggle to disable HUD. Defaults to false (visible)." 121 | t target-path VAL str "Target path to load files from, used WHEN serving files using file: protocol. (optional)" 122 | _ only-by-re REGEXES [regex] "Vector of path regexes (for `boot.core/by-re`) to restrict reloads to only files within these paths (optional)."] 123 | 124 | (let [pod (make-pod) 125 | tmp (tmp-dir!) 126 | prev-pre (atom nil) 127 | prev (atom nil) 128 | url (start-server @pod {:ip ip :port port :ws-host ws-host 129 | :ws-port ws-port :secure? secure 130 | :open-file open-file})] 131 | (b/cleanup (pod/with-call-in @pod (adzerk.boot-reload.server/stop))) 132 | (fn [next-task] 133 | (fn [fileset] 134 | (pod/with-call-in @pod 135 | (adzerk.boot-reload.server/set-options {:open-file ~open-file})) 136 | 137 | ;; Only changed cljs.edn files 138 | (doseq [f (relevant-cljs-edn (b/fileset-diff @prev-pre fileset) ids)] 139 | (let [path (tmp-path f) 140 | spec (-> f tmp-file slurp read-string) 141 | client-ns (str "adzerk.boot-reload." (rutil/path->ns path)) 142 | out-file (io/file tmp path) 143 | client-opts (merge {:on-jsload on-jsload 144 | :asset-host asset-host} 145 | (:boot-reload spec))] 146 | ;; ws-host is used by client, but is not client option because it mainly used by server 147 | (write-cljs! tmp client-ns url ws-host client-opts) 148 | (add-init! client-ns spec out-file path))) 149 | 150 | ;; Special case: boot-cljs used without .cljs.edn 151 | ;; in that case we can just create client file with any name and it will be 152 | ;; required by boot-cljs. 153 | ;; If file already exists, do nothing. 154 | (when (and (empty? (relevant-cljs-edn fileset ids)) 155 | (nil? (b/tmp-get fileset (.getPath (rutil/ns->file "adzerk.boot-reload" "cljs"))))) 156 | (write-cljs! tmp "adzerk.boot-reload" url ws-host {:on-jsload on-jsload 157 | :asset-host asset-host})) 158 | 159 | (reset! prev-pre fileset) 160 | (let [fileset (-> fileset (add-resource tmp) commit!) 161 | fileset (try 162 | (next-task fileset) 163 | (catch Exception e 164 | ;; FIXME: Supports only single error, e.g. less compiler 165 | ;; can give multiple errors. 166 | (if (and (not disable-hud) 167 | (or (= :boot-cljs (:from (ex-data e))) 168 | (:adzerk.boot-reload/exception (ex-data e)))) 169 | (send-visual! @pod {:exception (merge {:message (.getMessage e)} 170 | (ex-data e))})) 171 | (throw e)))] 172 | (let [cljs-edn (relevant-cljs-edn fileset ids) 173 | ;; cljs uses specific key for now 174 | ;; but any other file can contain warnings for boot-reload 175 | warnings (concat (mapcat :adzerk.boot-cljs/warnings cljs-edn) 176 | (mapcat :adzerk.boot-reload/warnings (b/input-files fileset))) 177 | static-files (->> cljs-edn 178 | (map b/tmp-path) 179 | (map(fn [x] (string/replace x #"\.cljs\.edn$" ".js"))) 180 | set)] 181 | (if-not disable-hud 182 | (send-visual! @pod {:warnings warnings})) 183 | ; Only send changed files when there are no warnings 184 | ; As prev is updated only when changes are sent, changes are queued untill they can be sent 185 | (when (empty? warnings) 186 | ;; IDEA: asset-path & cljs-asset-path could be handed in the client side and 187 | ;; they would only need to be written to client cljs. Would allow different options for each build. 188 | (send-changed! @pod 189 | asset-path 190 | cljs-asset-path 191 | target-path 192 | (changed @prev fileset only-by-re static-files)) 193 | (reset! prev fileset)) 194 | fileset)))))) 195 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/client.cljs: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.client 2 | (:require 3 | [adzerk.boot-reload.websocket :as ws] 4 | [adzerk.boot-reload.reload :as rl] 5 | [adzerk.boot-reload.display :as d] 6 | [adzerk.boot-reload.connection :refer [send-message! ws-conn alive?]] 7 | [clojure.browser.net :as net] 8 | [clojure.browser.event :as event] 9 | [cljs.reader :as reader] 10 | [goog.net.jsloader :as jsloader] 11 | [goog.html.legacyconversions :as legacyconversions]) 12 | (:import 13 | goog.Uri)) 14 | 15 | ;; Thanks, lein-figwheel & lively! 16 | (defn patch-goog-base! [] 17 | (set! (.-provide js/goog) (.-exportPath_ js/goog)) 18 | (set! (.-CLOSURE_IMPORT_SCRIPT (.-global js/goog)) (fn [file] 19 | (when (.inHtmlDocument_ js/goog) 20 | (jsloader/safeLoad (legacyconversions/trustedResourceUrlFromString file)))))) 21 | 22 | (defn resolve-url [url ws-host] 23 | (let [passed-uri (Uri. url) 24 | protocol (.getScheme passed-uri) 25 | hostname (some-> js/window .-location .-hostname) 26 | host (cond 27 | ws-host ws-host 28 | (seq hostname) hostname 29 | :else (do (js/console.warn "Both :ws-host and window.location.hostname are empty, using localhost as default." 30 | "This might happen if you are accessing the files directly instead of over http." 31 | "You should probably set :ws-host manually.") 32 | "localhost")) 33 | port (.getPort passed-uri)] 34 | (str protocol "://" host ":" port))) 35 | 36 | (defmulti handle (fn [msg opts] (:type msg))) 37 | 38 | (defmethod handle :reload 39 | [{:keys [files]} opts] 40 | (rl/reload files opts)) 41 | 42 | (defmethod handle :visual 43 | [state opts] 44 | (when (rl/has-dom?) 45 | (d/display state opts))) 46 | 47 | (defn connect 48 | ([url] (connect url nil)) 49 | ([url opts] 50 | (when (and (not (alive?)) 51 | goog/DEPENDENCIES_ENABLED) 52 | (let [conn (ws/websocket-connection)] 53 | (patch-goog-base!) 54 | (reset! ws-conn conn) 55 | 56 | (event/listen conn :opened 57 | (fn [evt] 58 | (.info js/console "Reload websocket connected."))) 59 | 60 | (event/listen conn :message 61 | (fn [evt] 62 | (let [msg (reader/read-string (.-message evt))] 63 | (handle msg opts)))) 64 | 65 | (event/listen conn :closed 66 | (fn [evt] 67 | (reset! ws-conn nil) 68 | (.info js/console "Reload websocket connection closed."))) 69 | 70 | (event/listen conn :error 71 | (fn [evt] 72 | (.error js/console "Reload websocket error:" evt))) 73 | 74 | (net/connect conn (resolve-url url (:ws-host opts))))))) 75 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/connection.cljs: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.connection 2 | (:require 3 | [clojure.browser.net :as net])) 4 | 5 | (def ws-conn (atom nil)) 6 | 7 | (defn alive? [] (not (nil? @ws-conn))) 8 | 9 | (defn send-message! [message] 10 | (when-let [conn @ws-conn] 11 | (net/transmit conn (pr-str message)))) 12 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/display.cljs: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.display 2 | (:require [clojure.string :as string] 3 | [goog.Timer :as timer] 4 | [goog.dom :as dom] 5 | [goog.events :as events] 6 | [adzerk.boot-reload.connection :refer [send-message!]]) 7 | (:import [goog.events EventType])) 8 | 9 | (def transition-duration 250) 10 | 11 | (def cljs-logo 12 | " 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ") 26 | 27 | (defn ->css [rules] 28 | (if-not (even? (count rules)) 29 | (js/console.log (pr-str rules))) 30 | (assert (even? (count rules))) 31 | (let [rules (partition 2 rules) 32 | render-rule (fn [[attr v]] (str (name attr) ":" v ";"))] 33 | (apply str (map render-rule rules)))) 34 | 35 | (defn mk-node 36 | ([type] (mk-node type {} nil)) 37 | ([type attrs] (mk-node type attrs nil)) 38 | ([type attrs content] 39 | (dom/createDom (name type) 40 | (clj->js (update attrs :style ->css)) 41 | (clj->js content)))) 42 | 43 | (defn style [& types] 44 | (let [s {:logo [:width "40px" :height "40px"] 45 | :flex [:display "-webkit-box" :display "-webkit-flex" :display "-ms-flexbox" :display "flex"] 46 | :flex-c [:-webkit-box-align "center" :-webkit-align-items "center" :-ms-flex-align "center" :align-items "center"] 47 | :bg-red [:background-color "rgba(255, 161, 161, 0.952941)" 48 | :box-shadow "rgb(170, 170, 170) 0px 0px 1px"] 49 | :bg-yellow [:background-color "rgba(255, 220, 110, 0.952941)" 50 | :box-shadow "rgb(170, 170, 170) 0px 0px 1px"] 51 | :bg-clear [:background-color "none"] 52 | :mr10 [:margin-right "10px"] 53 | :pad [:padding "12px"] 54 | :container [:color "black" 55 | :max-height "320px" 56 | :overflow "auto" 57 | :transition (str transition-duration "ms") 58 | :font-family "sans-serif" 59 | :position "fixed" 60 | :left "0px" 61 | :right "0px" 62 | :bottom "0px" 63 | :z-index "999999"] 64 | :hide [:opacity "0" 65 | :bottom "-100px"]}] 66 | {:style (apply concat (map s types))})) 67 | 68 | (defn open-file [data] 69 | (send-message! (merge {:type :open-file} 70 | (select-keys data [:file :line :column])))) 71 | 72 | (defn exception-node [{:keys [message line file] :as data}] 73 | (doto (mk-node :div nil message) 74 | (events/listen EventType.CLICK #(open-file data)))) 75 | 76 | (defn warning-node [{:keys [message line file] :as data}] 77 | (doto (mk-node :div nil 78 | [(mk-node :span (style :mr10) message) 79 | (mk-node :span (style :mr10) (str "at line " line)) 80 | (mk-node :span (style :mr10) file)]) 81 | (events/listen EventType.CLICK #(open-file data)))) 82 | 83 | (defn reloaded-node [] 84 | (mk-node :div nil nil)) 85 | 86 | (defn logo-node [logo] 87 | (let [element (js/document.createElement "div")] 88 | (set! (. element -innerHTML) logo) 89 | (aget (. element -childNodes) 0))) 90 | 91 | (defn warnings-node [warnings] 92 | (mk-node :div nil (map warning-node warnings))) 93 | 94 | (defn construct-hud-node [{:keys [warnings exception] :as messages}] 95 | (doto (mk-node :div (style :pad :flex :flex-c (cond exception :bg-red 96 | (seq warnings) :bg-yellow 97 | :else :bg-clear))) 98 | (dom/append (mk-node :div (style :logo :mr10) (logo-node cljs-logo))) 99 | (dom/append (cond exception (exception-node exception) 100 | (seq warnings) (warnings-node warnings) 101 | :else (reloaded-node))))) 102 | 103 | (defn remove-container! [id] 104 | (let [el (dom/getElement id)] 105 | (dom/setProperties el (clj->js (update (style :container :hide) :style ->css))) 106 | (timer/callOnce #(dom/removeNode el) transition-duration))) 107 | 108 | (defn insert-container! [id {:keys [warnings exception] :as messages}] 109 | (let [hud (construct-hud-node messages) 110 | el (mk-node :div (merge (style :container :hide) {:id id}) hud) 111 | show! #(dom/setProperties el (clj->js (update (style :container) :style ->css))) 112 | no-prob? (and (not exception) (not (seq warnings))) 113 | hide! #(dom/setProperties el (clj->js (update (style :container :hide) :style ->css)))] 114 | (dom/appendChild js/document.body el) 115 | (timer/callOnce show! transition-duration) 116 | (when no-prob? 117 | (timer/callOnce hide! (* transition-duration 5))))) 118 | 119 | (defn gen-id [] 120 | (str "boot-reload-hud-" (name (gensym)))) 121 | 122 | (defonce current-container (atom nil)) 123 | 124 | (defn display [messages opts] 125 | (swap! current-container 126 | (fn [container] 127 | (when container 128 | (try (remove-container! container) 129 | (catch js/Error _))) 130 | (let [id (gen-id)] 131 | (insert-container! id messages) 132 | id)))) 133 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/reload.cljs: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.reload 2 | (:require 3 | [clojure.string :as string] 4 | [goog.Uri :as guri] 5 | [goog.async.DeferredList :as deferred-list] 6 | [goog.html.legacyconversions :as conv] 7 | [goog.net.jsloader :as jsloader])) 8 | 9 | (defn- page-uri [] 10 | (goog.Uri. (.. js/window -location -href))) 11 | 12 | (defn- ends-with? [s pat] 13 | (= pat (subs s (- (count s) (count pat))))) 14 | 15 | (defn- reload-page! [] 16 | (.reload (.-location js/window))) 17 | 18 | (defn- normalize-href-or-uri [href-or-uri] 19 | (let [uri (goog.Uri. href-or-uri)] 20 | (.getPath (.resolve (page-uri) uri)))) 21 | 22 | (defn- changed-uri 23 | "Produce the changed goog.Uri iff the (relative) path is different 24 | compared to the content of changed (a string). Return nil otherwise." 25 | [href-or-uri changed] 26 | (when href-or-uri 27 | (let [path (normalize-href-or-uri href-or-uri)] 28 | (when-let [changed (first (filter #(ends-with? (normalize-href-or-uri %) path) changed))] 29 | (guri/parse changed))))) 30 | 31 | (defn- reload-css [changed] 32 | (let [sheets (.. js/document -styleSheets)] 33 | (doseq [s (range 0 (.-length sheets))] 34 | (when-let [sheet (aget sheets s)] 35 | (when-let [href-uri (changed-uri (.-href sheet) changed)] 36 | (set! (.. sheet -ownerNode -href) (.toString (.makeUnique href-uri)))))))) 37 | 38 | (defn- reload-img [changed] 39 | (let [images (.. js/document -images)] 40 | (doseq [s (range 0 (.-length images))] 41 | (when-let [image (aget images s)] 42 | (when-let [href-uri (changed-uri (.-src image) changed)] 43 | (set! (.-src image) (.toString (.makeUnique href-uri)))))))) 44 | 45 | (defn load-files [urls] 46 | (let [opts #js {:cleanupWhenDone true}] 47 | (cond 48 | (exists? jsloader/safeLoadMany) 49 | (jsloader/safeLoadMany 50 | (clj->js (map #(-> % .toString conv/trustedResourceUrlFromString) urls)) 51 | opts) 52 | 53 | (exists? jsloader/loadMany) (jsloader/loadMany (clj->js urls) opts) 54 | 55 | :else (throw (ex-info "jsloader/safeLoadMany not found" {}))))) 56 | 57 | (defn- reload-js [changed {:keys [on-jsload] 58 | :or {on-jsload identity}}] 59 | (let [js-files (filter #(ends-with? % ".js") changed)] 60 | (when (seq js-files) 61 | (-> (map #(-> % guri/parse .makeUnique) js-files) 62 | load-files 63 | (.addCallbacks 64 | (fn [& _] (on-jsload)) 65 | (fn [e] (.error js/console "Load failed:" (.-message e))))) 66 | (when (aget js/window "jQuery") 67 | (.trigger (js/jQuery js/document) "page-load"))))) 68 | 69 | (defn- reload-html [changed] 70 | (let [page-path (.getPath (page-uri)) 71 | html-path (if (ends-with? page-path "/") 72 | (str page-path "index.html") 73 | page-path)] 74 | (when (changed-uri html-path changed) (reload-page!)))) 75 | 76 | (defn- group-log [title things-to-log] 77 | (.groupCollapsed js/console title) 78 | (doseq [t things-to-log] (.log js/console t)) 79 | (.groupEnd js/console)) 80 | 81 | (defn has-dom? 82 | "Perform heuristics to check if a non-shimmed DOM is available" 83 | [] 84 | (and (exists? js/window) 85 | (exists? js/window.document) 86 | (exists? js/window.document.documentURI))) 87 | 88 | (defn reload [changed opts] 89 | (let [changed* (->> changed 90 | (map (fn [{:keys [canonical-path web-path]}] 91 | (if (= "file:" (some-> js/window .-location .-protocol)) 92 | canonical-path 93 | web-path))) 94 | ;; This should probably be empty if serving from file-system 95 | (map #(str (:asset-host opts) %)))] 96 | (group-log "Reload" changed*) 97 | (reload-js changed* opts) 98 | (when (has-dom?) 99 | (doto changed* 100 | reload-html 101 | reload-css 102 | reload-img)))) 103 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/server.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.server 2 | (:require 3 | [clojure.java.io :as io] 4 | [boot.util :as util] 5 | [org.httpkit.server :as http] 6 | [clojure.string :as string]) 7 | (:import 8 | [java.io IOException])) 9 | 10 | (def options (atom {:open-file nil})) 11 | (def clients (atom #{})) 12 | (def stop-fn (atom nil)) 13 | 14 | (defn set-options [opts] 15 | (reset! options opts)) 16 | 17 | (defn web-path 18 | ([rel-path] (web-path {} rel-path)) 19 | ([opts rel-path] 20 | ; windows fix, convert \ characters to / in rel-path 21 | (let [rel-path (string/replace rel-path #"\\" "/") 22 | {:keys [target-path asset-path cljs-asset-path]} opts] 23 | {:canonical-path (.getCanonicalPath (io/file target-path rel-path)) 24 | :web-path (str 25 | cljs-asset-path "/" 26 | (string/replace rel-path 27 | (re-pattern (str "^" (string/replace (or asset-path "") #"^/" "") "/")) 28 | ""))}))) 29 | 30 | (defn send-visual! [messages] 31 | (doseq [channel @clients] 32 | (http/send! channel (pr-str (merge {:type :visual} 33 | messages))))) 34 | 35 | (defn send-changed! 36 | ([changed] (send-changed! {} changed)) 37 | ([opts changed] 38 | (doseq [channel @clients] 39 | (http/send! channel 40 | (pr-str {:type :reload 41 | :files (map #(web-path opts %) changed)}))))) 42 | 43 | (defmulti handle-message (fn [channel message] (:type message))) 44 | 45 | (defmethod handle-message :open-file [channel {:keys [file line column]}] 46 | (when-let [open-file (:open-file @options)] 47 | (let [cmd (format open-file (or line 0) (or column 0) (or file ""))] 48 | (util/dbug "Open-file call: %s\n" cmd) 49 | (try 50 | (.exec (Runtime/getRuntime) cmd) 51 | (catch Exception e 52 | (util/fail "There was a problem running open-file command: %s\n" cmd)))))) 53 | 54 | (defn connect! [channel] 55 | (swap! clients conj channel) 56 | (http/on-close channel (fn [_] (swap! clients disj channel))) 57 | (http/on-receive channel #(handle-message channel (read-string %)))) 58 | 59 | (defn handler [request] 60 | (if-not (:websocket? request) 61 | {:status 501 :body "Websocket connections only."} 62 | (http/with-channel request channel (connect! channel)))) 63 | 64 | (defn start 65 | [{:keys [ip port] :as opts}] 66 | (let [o {:ip (or ip "0.0.0.0") :port (or port 0)} 67 | stop-fn* (http/run-server handler o)] 68 | (reset! stop-fn stop-fn*) 69 | (assoc o :port (-> stop-fn* meta :local-port)))) 70 | 71 | (defn stop [] 72 | (when @stop-fn 73 | (@stop-fn) 74 | (reset! stop-fn nil))) 75 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/util.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.util 2 | (:require [clojure.string :as string] 3 | [clojure.java.io :as io])) 4 | 5 | ;; From cljs/analyzer.cljc 6 | (def js-reserved 7 | #{"arguments" "abstract" "boolean" "break" "byte" "case" 8 | "catch" "char" "class" "const" "continue" 9 | "debugger" "default" "delete" "do" "double" 10 | "else" "enum" "export" "extends" "final" 11 | "finally" "float" "for" "function" "goto" "if" 12 | "implements" "import" "in" "instanceof" "int" 13 | "interface" "let" "long" "native" "new" 14 | "package" "private" "protected" "public" 15 | "return" "short" "static" "super" "switch" 16 | "synchronized" "this" "throw" "throws" 17 | "transient" "try" "typeof" "var" "void" 18 | "volatile" "while" "with" "yield" "methods" 19 | "null" "constructor"}) 20 | 21 | (defn path->ns 22 | [path] 23 | (let [parts (-> path 24 | (string/replace #"\..+$" "") 25 | (string/replace #"_" "-") 26 | (string/replace #"[/\\]" ".") 27 | (string/split #"\."))] 28 | (->> parts 29 | (map #(if (js-reserved %) (str % "$") %)) 30 | (string/join ".")))) 31 | 32 | (defn ns->file 33 | ([ns-name ext] 34 | (let [ns-name (string/replace ns-name #"-" "_") 35 | parts (string/split ns-name #"\.")] 36 | (apply io/file (conj (vec (butlast parts)) (str (last parts) "." ext))))) 37 | ([parent ns-name ext] 38 | (let [ns-name (string/replace ns-name #"-" "_") 39 | parts (string/split ns-name #"\.")] 40 | (apply io/file parent (conj (vec (butlast parts)) (str (last parts) "." ext)))))) 41 | -------------------------------------------------------------------------------- /src/adzerk/boot_reload/websocket.cljs: -------------------------------------------------------------------------------- 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 adzerk.boot-reload.websocket 10 | (:require [clojure.browser.net :as net :refer [IConnection connect transmit]] 11 | [clojure.browser.event :as event :refer [event-types]] 12 | [goog.net.WebSocket :as gwebsocket])) 13 | 14 | (defprotocol IWebSocket 15 | (open? [this])) 16 | 17 | (defn websocket-connection 18 | ([] 19 | (websocket-connection nil nil)) 20 | ([auto-reconnect?] 21 | (websocket-connection auto-reconnect? nil)) 22 | ([auto-reconnect? next-reconnect-fn] 23 | (goog.net.WebSocket. #js {:autoReconnect auto-reconnect? 24 | :getNextReconnect next-reconnect-fn}))) 25 | 26 | (extend-type goog.net.WebSocket 27 | IWebSocket 28 | (open? [this] 29 | (.isOpen this ())) 30 | 31 | net/IConnection 32 | (connect 33 | ([this url] 34 | (connect this url nil)) 35 | ([this url protocol] 36 | (.open this url protocol))) 37 | 38 | (transmit [this message] 39 | (when (open? this) 40 | (.send this message))) 41 | 42 | (close [this] 43 | (.close this ())) 44 | 45 | event/IEventType 46 | (event-types [this] 47 | (into {} 48 | (map 49 | (fn [[k v]] 50 | [(keyword (. k (toLowerCase))) 51 | v]) 52 | (merge 53 | (js->clj goog.net.WebSocket/EventType)))))) 54 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "1.9.0-alpha17" 6 | BOOT_CLOJURE_VERSION=1.9.0 boot test 7 | 8 | echo 9 | echo "1.8.0" 10 | BOOT_CLOJURE_VERSION=1.8.0 boot test 11 | 12 | echo 13 | echo "1.7.0" 14 | BOOT_CLOJURE_VERSION=1.7.0 boot test 15 | 16 | echo 17 | echo "1.6.0" 18 | BOOT_CLOJURE_VERSION=1.6.0 boot test 19 | -------------------------------------------------------------------------------- /test/adzerk/boot_reload/server_test.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.server-test 2 | (:require [clojure.test :refer :all] 3 | [adzerk.boot-reload.server :refer :all] 4 | [clojure.java.io :as io])) 5 | 6 | (deftest web-path-test 7 | (testing "basic" 8 | (is (= {:web-path "/js/out/saapas/core.js" 9 | :canonical-path (.getCanonicalPath (io/file "target" "js" "out" "saapas" "core.js"))} 10 | (web-path {:target-path "target"} "js/out/saapas/core.js")))) 11 | 12 | (testing "asset-path" 13 | (is (= {:web-path "/js/out/saapas/core.js" 14 | :canonical-path (.getCanonicalPath (io/file "target" "public" "js" "out" "saapas" "core.js"))} 15 | (web-path {:asset-path "public" 16 | :target-path "target"} 17 | "public/js/out/saapas/core.js"))) 18 | 19 | (is (= {:web-path "/js/out/saapas/core.js" 20 | :canonical-path (.getCanonicalPath (io/file "target" "public" "js" "out" "saapas" "core.js"))} 21 | (web-path {:asset-path "/public" 22 | :target-path "target"} 23 | "public/js/out/saapas/core.js"))) 24 | 25 | (is (= {:web-path "/public/js/out/saapas/core.js" 26 | :canonical-path (.getCanonicalPath (io/file "target" "public" "js" "out" "saapas" "core.js"))} 27 | (web-path {:asset-path "foobar" 28 | :target-path "target"} 29 | "public/js/out/saapas/core.js")))) 30 | 31 | (testing "cljs-asset-path" 32 | (is (= {:web-path "js/saapas.out/saapas/core.js" 33 | :canonical-path (.getCanonicalPath (io/file "target" "resources" "public" "js" "saapas.out" "saapas" "core.js"))} 34 | (web-path {:asset-path "resources/public/js/saapas.out" 35 | :cljs-asset-path "js/saapas.out" 36 | :target-path "target"} 37 | "resources/public/js/saapas.out/saapas/core.js")))) 38 | 39 | (testing "windows style paths" 40 | (is (= {:web-path "js/saapas.out/saapas/core.js" 41 | :canonical-path (.getCanonicalPath (io/file "target" "resources" "public" "js" "saapas.out" "saapas" "core.js"))} 42 | (web-path {:asset-path "resources/public/js/saapas.out" 43 | :cljs-asset-path "js/saapas.out" 44 | :target-path "target"} 45 | "resources\\public\\js\\saapas.out\\saapas\\core.js"))))) 46 | -------------------------------------------------------------------------------- /test/adzerk/boot_reload/util_test.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-reload.util-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.java.io :as io] 4 | [adzerk.boot-reload.util :as util])) 5 | 6 | (deftest path->ns-test 7 | (is (= "foo.bar" (util/path->ns "foo/bar.clj"))) 8 | (is (= "foo-bar" (util/path->ns "foo_bar.clj"))) 9 | (is (= "foo-bar" (util/path->ns "foo-bar.clj"))) 10 | 11 | (is (= "foo.bar" (util/path->ns "foo/bar.cljs.edn"))) 12 | (is (= "foo.bar" (util/path->ns "foo\\bar.cljs.edn"))) 13 | 14 | (is (= "public$.js.oc" (util/path->ns "public/js/oc.cljs.edn")))) 15 | 16 | (deftest ns->file-test 17 | (is (= (io/file "foo" "bar.cljs") (util/ns->file "foo.bar" "cljs"))) 18 | (is (= (io/file "foo_bar.cljs") (util/ns->file "foo-bar" "cljs"))) 19 | 20 | (is (= (io/file "adzerk" "boot_reload" "public$" "js" "oc.cljs") 21 | (util/ns->file "adzerk.boot-reload.public$.js.oc" "cljs")))) 22 | --------------------------------------------------------------------------------