├── .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 [](https://jarkeeper.com/adzerk/boot-reload) [](https://travis-ci.org/adzerk-oss/boot-reload) [](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 | "")
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 |
--------------------------------------------------------------------------------