├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── boot.properties
├── build.boot
├── src
└── powerlaces
│ ├── boot_figreload.clj
│ └── boot_figreload
│ ├── figwheel.clj
│ ├── messages.clj
│ ├── server.clj
│ └── util.clj
├── test.sh
└── test
└── powerlaces
└── boot_figreload
└── 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 | .dir-locals.el
33 |
34 | ### VIM ########################################################################
35 |
36 | *.swn
37 | *.swo
38 | *.swp
39 |
40 | ### PROJECT ####################################################################
41 |
42 | /zzz/
43 | /target/
44 |
45 | ### TAGS ####################################################################
46 |
47 | GPATH
48 | GRTAGS
49 | GSYMS
50 | GTAGS
51 | TAGS
52 |
--------------------------------------------------------------------------------
/.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 | - openjdk7
11 | - oraclejdk8
12 | cache:
13 | directories:
14 | - $HOME/.m2
15 | - $HOME/.boot/cache
16 | - $HOME/bin
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 0.5.15 - to be released
2 |
3 | - New release aligned with Figwheel `0.5.15`.
4 |
5 | **[compare](https://github.com/boot-clj/boot-fig reload/compare/0.5.14...master)**
6 |
7 | ### 0.5.14
8 |
9 | - New release aligned with Figwheel `0.5.14`.
10 |
11 | **[compare](https://github.com/boot-clj/boot-fig reload/compare/0.5.13...0.5.14)**
12 |
13 | ### [0.5.13](https://github.com/boot-clj/boot-figreload/releases/tag/0.5.13)
14 |
15 | - New release aligned with Figwheel `0.5.13`.
16 |
17 | **[compare](https://github.com/boot-clj/boot-fig reload/compare/0.5.9...0.5.13)**
18 |
19 | ## [0.5.9](https://github.com/boot-clj/boot-figreload/tag/0.5.9)
20 |
21 | #### First release matching lein-figwheel's version
22 |
23 | Handled server to client messages:
24 |
25 | - [x] `:files-changed`
26 | - [x] `:compile-warning`
27 | - [x] `:compile-failed`
28 | - [x] `:css-files-changed`
29 |
30 | Implemented [client to server](https://github.com/arichiardi/lein-figwheel/blob/boot-reload-changes/sidecar/src/figwheel_sidecar/components/figwheel_server.clj#L75) messages:
31 |
32 | - [ ] `"file-selected"`
33 | - [ ] `"callback"`
34 |
35 | Misc tasks:
36 |
37 | - [x] Inject the Figwheel bootstrap script
38 | - [x] Handle individual `js-onload` per build id (untested but there)
39 | - [x] Figwheel version
40 | - [x] Use Figwheel [init code](https://github.com/bhauman/lein-figwheel/blob/cc2d188ab041fc92551d3c4a8201729c47fe5846/sidecar/src/figwheel_sidecar/build_middleware/injection.clj#L171) (?)
41 | - [ ] Handle `boot-reload`'s `:asset-host` in Figwheel ([link to comments](https://github.com/adzerk-oss/boot-reload/commit/e27e330d9f688875ba19d56e825cd9e81013e58e#commitcomment-20350456))
42 | - [ ] Pass the right `:open-file` option to Figwheel
43 | - [ ] Solve the "first message lost" problem with a message queue (?)
44 | - [x] Assert needed dependencies
45 | - [ ] Repl integration (at the moment supported via `boot-cljs-repl`)
46 |
47 | To be thorougly tested:
48 |
49 | - [x] Node client
50 | - [ ] Web-worker client
51 | - [ ] Trigger of multiple `js-onload`s
52 |
--------------------------------------------------------------------------------
/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-figreload
2 | [](https://clojars.org/powerlaces/boot-figreload)
3 |
4 | [Boot][1] task to automatically reload resources in the browser when files in
5 | the project change. Featuring [lein-figwheel][2].
6 |
7 | * Provides the `reload` task
8 | * Reload client can show warnings and exceptions from ClojureScript build on **heads-up display**.
9 | * Requires `adzerk/boot-cljs` >= `2.0.0`
10 |
11 | ## Usage
12 |
13 | Add dependency to `build.boot` and `require` the task:
14 |
15 | ```clj
16 | (set-env! :dependencies '[[adzerk/boot-cljs "LATEST" :scope "test"]
17 | [powerlaces/boot-figreload "LATEST" :scope "test"]
18 | [pandeiro/boot-http "0.7.6" :scope "test"]
19 |
20 | [adzerk/boot-cljs-repl "0.3.3" :scope "test"]
21 | [com.cemerick/piggieback "0.2.1" :scope "test"]
22 | [weasel "0.7.0" :scope "test"]
23 | [org.clojure/tools.nrepl "0.2.12" :scope "test"]])
24 |
25 | (require '[adzerk.boot-cljs :refer [cljs]]
26 | '[adzerk.boot-cljs-repl :refer [cljs-repl]]
27 | '[powerlaces.boot-figreload :refer [reload]]
28 | '[pandeiro.boot-http :refer [serve]])
29 | ```
30 |
31 | Add the task to your development pipeline **before `(cljs ...)`**:
32 |
33 | ```clj
34 | (deftask dev []
35 | (comp (serve)
36 | (watch)
37 | (reload)
38 | (cljs-repl)
39 | (cljs :source-map true
40 | :optimizations :none)))
41 | ```
42 |
43 | ### Dirac
44 |
45 | Boot-figreload is compatible with Dirac, enabling REPL evaluation in-browser on top of Figwheel's reloading.
46 |
47 | Your `dev` task could therefore become:
48 |
49 | ```clj
50 | (set-env! :dependencies '[[adzerk/boot-cljs "LATEST" :scope "test"]
51 | [powerlaces/boot-figreload "LATEST" :scope "test"]
52 | [pandeiro/boot-http "0.7.6" :scope "test"]
53 |
54 | ;; Dirac and cljs-devtoos
55 | [binaryage/dirac "RELEASE" :scope "test"]
56 | [binaryage/devtools "RELEASE" :scope "test"]
57 | [powerlaces/boot-cljs-devtools "0.2.0" :scope "test"]
58 |
59 | [adzerk/boot-cljs-repl "0.3.3" :scope "test"]
60 | [com.cemerick/piggieback "0.2.1" :scope "test"]
61 | [weasel "0.7.0" :scope "test"]
62 |
63 | ;; Has to be `0.2.13`
64 | [org.clojure/tools.nrepl "0.2.13" :scope "test"]])
65 |
66 | (require '[adzerk.boot-cljs :refer [cljs]]
67 | '[adzerk.boot-cljs-repl :refer [cljs-repl]]
68 | '[powerlaces.boot-figreload :refer [reload]]
69 | '[powerlaces.boot-cljs-devtools :refer [dirac cljs-devtools]]
70 | '[pandeiro.boot-http :refer [serve]])
71 |
72 | ...
73 |
74 | (deftask dev [D with-dirac bool "Enable Dirac Devtools."]
75 | (comp (serve)
76 | (watch)
77 | (cljs-devtools)
78 | (reload)
79 | (if-not with-dirac
80 | (cljs-repl)
81 | (dirac))
82 | (cljs :source-map true
83 | :optimizations :none
84 | :compiler-options {:external-config
85 | {:devtools/config {:features-to-install [:formatters :hints]
86 | :fn-symbol "λ"
87 | :print-config-overrides true}}})))
88 |
89 | ```
90 |
91 | ## Node.js
92 |
93 | It should work out of the box. Two things to be aware of:
94 |
95 | * you need to have a `main.cljs.edn` like:
96 |
97 | ```clojure
98 | {:compiler-options {:target :nodejs}
99 | :init-fns [server.core/main]}
100 | ```
101 |
102 | * you need to launch your built artifact using node:
103 |
104 | ```shell
105 | $ cd target
106 | $ node main.js
107 | ```
108 |
109 | ## Figwheel Integration Status
110 |
111 | Ok this is a super alpha of the figwheel client in `boot-reload`.
112 |
113 | At the moment the implemented server to client messages are:
114 |
115 | - [x] `:files-changed`
116 | - [x] `:compile-warning`
117 | - [x] `:compile-failed`
118 | - [x] `:css-files-changed`
119 |
120 | Whereas the implemented [client to server](https://github.com/arichiardi/lein-figwheel/blob/boot-reload-changes/sidecar/src/figwheel_sidecar/components/figwheel_server.clj#L75) messages are:
121 |
122 | - [ ] `"file-selected"`
123 | - [ ] `"callback"`
124 |
125 | ### Other tasks to complete:
126 |
127 | - [x] Inject the Figwheel bootstrap script
128 | - [x] Handle individual `js-onload` per build id (untested but there)
129 | - [x] Figwheel version
130 | - [x] Use Figwheel [init code](https://github.com/bhauman/lein-figwheel/blob/cc2d188ab041fc92551d3c4a8201729c47fe5846/sidecar/src/figwheel_sidecar/build_middleware/injection.clj#L171) (?)
131 | - [ ] Handle `boot-reload`'s `:asset-host` in Figwheel ([link to comments](https://github.com/adzerk-oss/boot-reload/commit/e27e330d9f688875ba19d56e825cd9e81013e58e#commitcomment-20350456))
132 | - [ ] Pass the right `:open-file` option to Figwheel
133 | - [ ] Solve the "first message lost" problem with a message queue (?)
134 | - [x] Assert needed dependencies
135 | - [ ] Repl integration (at the moment supported via [boot-cljs-repl][3])
136 |
137 | ### To be thorougly tested:
138 |
139 | - [x] Node client
140 | - [ ] Web-worker client
141 | - [ ] Trigger of multiple `js-onload`s
142 |
143 | ## Additional Info
144 |
145 | You can see the options available on the command line:
146 |
147 | ```bash
148 | boot reload --help
149 | ```
150 |
151 | or in the REPL:
152 |
153 | ```clj
154 | boot.user=> (doc reload)
155 | ```
156 |
157 | ## Examples
158 |
159 | For an up-to-date demo project check [figreload-demo][4].
160 |
161 | Legacy examples of how to use `reload` in development can be useful as well. See
162 | [Boot templates and example projects][5] in the ClojureScript wiki.
163 |
164 | ## License
165 |
166 | Copyright © 2014 Adzerk
167 | Copyright © 2015-2016 Juho Teperi
168 | Copyright © 2017 Juho Teperi and Andrea Richiardi
169 |
170 | Distributed under the Eclipse Public License either version 1.0 or (at
171 | your option) any later version.
172 |
173 | [1]: https://github.com/boot-clj/boot
174 | [2]: https://github.com/bhauman/lein-figwheel
175 | [3]: https://github.com/adzerk-oss/boot-cljs-repl
176 | [4]: https://github.com/arichiardi/figreload-demo
177 | [5]: https://github.com/clojure/clojurescript/wiki#boot
178 |
--------------------------------------------------------------------------------
/boot.properties:
--------------------------------------------------------------------------------
1 | #http://boot-clj.com
2 | #Thu Dec 24 12:31:47 EET 2015
3 | BOOT_VERSION=2.7.2
4 | BOOT_CLOJURE_VERSION=1.8.0
5 |
--------------------------------------------------------------------------------
/build.boot:
--------------------------------------------------------------------------------
1 | (def figwheel-dependency '[figwheel-sidecar "0.5.15-SNAPSHOT"])
2 |
3 | (set-env!
4 | :source-paths #{"src"}
5 | :dependencies (conj '[[org.clojure/clojure "1.8.0" :scope "provided"]
6 | [adzerk/bootlaces "0.1.13" :scope "test"]
7 | [adzerk/boot-test "1.1.0" :scope "test"]
8 | [metosin/boot-alt-test "0.3.0" :scope "test"]]
9 | figwheel-dependency))
10 |
11 | (require '[adzerk.boot-test :refer [test]]
12 | '[adzerk.bootlaces :refer [bootlaces! build-jar push-snapshot push-release]]
13 | '[metosin.boot-alt-test :refer [alt-test]])
14 |
15 | (def +version+ "0.5.15-SNAPSHOT")
16 | (bootlaces! +version+)
17 |
18 | (task-options!
19 | pom {:project 'powerlaces/boot-figreload
20 | :version +version+
21 | :description "Boot task to automatically reload page resources in the browser (featuring Figwheel)."
22 | :url "https://github.com/boot-clj/boot-figreload"
23 | :scm {:url "git@github.com:boot-clj/boot-figreload.git"}
24 | :license {"Eclipse Public License"
25 | "http://www.eclipse.org/legal/epl-v10.html"}})
26 |
27 | (deftask build []
28 | (comp
29 | (pom)
30 | (jar)
31 | (install)))
32 |
33 | (deftask dev []
34 | (comp
35 | (watch)
36 | (repl :server true)
37 | (build)
38 | (target)))
39 |
40 | (def snapshot? #(.endsWith +version+ "-SNAPSHOT"))
41 |
42 | (deftask deploy []
43 | (comp
44 | (build-jar)
45 | (if (snapshot?)
46 | (push-snapshot)
47 | (push-release))))
48 |
49 | (ns-unmap *ns* 'test)
50 |
51 | (deftask test
52 | "Run the tests once"
53 | []
54 | (set-env! :source-paths #(conj % "test")
55 | :dependencies #(conj % figwheel-dependency))
56 | (alt-test))
57 |
--------------------------------------------------------------------------------
/src/powerlaces/boot_figreload.clj:
--------------------------------------------------------------------------------
1 | (ns powerlaces.boot-figreload
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 butil]
11 | [boot.core :refer :all]
12 | [boot.from.digest :as digest]
13 | [powerlaces.boot-figreload.util :as util]))
14 |
15 | (def ^:private deps '[[http-kit "2.1.18"]
16 | [figwheel-sidecar "0.5.15-SNAPSHOT"]])
17 |
18 | (defn- make-pod []
19 | (future (-> (get-env) (update-in [:dependencies] into deps) pod/make-pod)))
20 |
21 | (defn- changed [before after only-by-re static-files]
22 | (letfn [(maybe-filter-by-re [files]
23 | (if only-by-re
24 | (by-re only-by-re files)
25 | files))]
26 | (when before
27 | (->> (fileset-diff before after :hash)
28 | output-files
29 | maybe-filter-by-re
30 | (not-by-path static-files)
31 | (sort-by :dependency-order)
32 | (reduce #(conj %1 {:relative-path (-> %2 tmp-path)
33 | :dir-path (-> %2 tmp-dir .getCanonicalPath)
34 | :full-path (-> %2 tmp-file .getCanonicalPath)})
35 | #{})))))
36 |
37 | (defn- start-server [pod {:keys [ip port ws-host ws-port secure?] :as opts}]
38 | (let [{:keys [ip port]} (pod/with-call-in pod (powerlaces.boot-figreload.server/start ~opts))
39 | listen-host (cond (= ip "0.0.0.0") "localhost" :else ip)
40 | client-host (cond ws-host ws-host (= ip "0.0.0.0") "localhost" :else ip)
41 | proto (if secure? "wss" "ws")]
42 | (butil/info "Starting reload server on %s\n" (format "%s://%s:%d" proto listen-host port))
43 | (format "%s://%s:%d" proto client-host (or ws-port port))))
44 |
45 | (defn- write-bootstrap-ns! [pod parent-path build-config]
46 | (let [[ns-sym ns-path ns-content] (pod/with-call-in pod
47 | (powerlaces.boot-figreload.server/bootstrap-ns
48 | ~build-config))
49 | f (io/file parent-path ns-path)]
50 | (io/make-parents f)
51 | (butil/info "Writing %s namespace to %s...\n" ns-sym (.getName f))
52 | (butil/dbug "%s\n" ns-content)
53 | (spit f ns-content)))
54 |
55 | (defn- send-visual! [pod client-opts visual-map]
56 | (pod/with-call-in pod
57 | (powerlaces.boot-figreload.server/send-visual!
58 | ~client-opts
59 | ~visual-map)))
60 |
61 | (defn- send-changed! [pod client-opts change-map]
62 | (pod/with-call-in pod
63 | (powerlaces.boot-figreload.server/send-changed!
64 | ~client-opts
65 | ~change-map)))
66 |
67 | (defn- add-init!
68 | [pod build-config old-spec out-file]
69 | (io/make-parents out-file)
70 | (let [new-spec (pod/with-call-in pod
71 | (powerlaces.boot-figreload.server/add-cljs-edn-init
72 | ~build-config
73 | ~old-spec))]
74 | (butil/info "Adding :require(s) to %s...\n" (.getName out-file))
75 | (butil/dbug* "%s\n" (butil/pp-str new-spec))
76 | (->> new-spec
77 | pr-str
78 | (spit out-file))))
79 |
80 | (defn- relevant-cljs-edns [fileset ids]
81 | (let [relevant (map #(str % ".cljs.edn") ids)
82 | f (if ids
83 | #(b/by-path relevant %)
84 | #(b/by-ext [".cljs.edn"] %))]
85 | (-> fileset b/input-files f)))
86 |
87 | (defn cljs-opts-seq
88 | "Return a sequence of compiler options given boot's .cljs.edn files on
89 | the fileset. If no :adzerk.boot-cljs/opts key is found on the tmpfile,
90 | no entry is added to the output sequence."
91 | [tmpfiles]
92 | (->> tmpfiles
93 | (map #(when-let [opts (:adzerk.boot-cljs/opts %)]
94 | (assoc opts :build-id (-> % b/tmp-file .getPath util/build-id))))
95 | (remove nil?)))
96 |
97 | (defn make-build-config
98 | "Return an ClojureScript build configuration map akin to leinengen and
99 | figwheel:
100 | https://github.com/bhauman/lein-figwheel#client-side-configuration-options
101 |
102 | The id is last because it is the one arguably more likely to vary."
103 | [client-opts build-id]
104 | ;; See https://github.com/bhauman/lein-figwheel/blob/0283f6d79af630854c1d3071eeb6f0ff8fd41676/sidecar/src/figwheel_sidecar/schemas/config.clj#L602
105 | (-> {:id build-id :figwheel client-opts}
106 | util/remove-nils))
107 |
108 | (defn read-cljs-edn-spec!
109 | [cljs-edn-tmpfile]
110 | (let [file (tmp-file cljs-edn-tmpfile)
111 | path (tmp-path cljs-edn-tmpfile)]
112 | (if (.exists file)
113 | (try (-> file slurp read-string)
114 | (catch Exception e
115 | (let []
116 | (throw (ex-info
117 | (format "Cannot read %s" path)
118 | {:cljs-edn {:path path
119 | :content (slurp file)}}
120 | e)))))
121 | (throw (ex-info (format "The file %s does not exist. This might be a bug." (.getAbsolutePath file))
122 | {:cljs-edn path})))))
123 |
124 | (deftask reload
125 | "Live reload of page resources in browser via websocket.
126 |
127 | The default configuration starts a websocket server on a random available
128 | port on localhost.
129 |
130 | Open-file option takes three arguments: line number, column number, relative
131 | file path. You can use positional arguments if you need different order.
132 | Arguments shouldn't have spaces.
133 | Examples:
134 | vim --remote +norm%sG%s| %s
135 | emacsclient -n +%s:%s %s
136 |
137 | Client options can also be set in .cljs.edn file, using property :boot-reload, e.g.
138 | :boot-reload {:on-jsload frontend.core/reload}"
139 |
140 | [b ids BUILD_IDS #{str} "Only inject reloading into these builds (= .cljs.edn files)"
141 | ;; Websocket Server
142 | i ip ADDR str "The IP address for the websocket server to listen on. (optional)"
143 | p port PORT int "The port the websocket server listens on. (optional)"
144 | _ ws-port PORT int "The port the websocket will connect to. (optional)"
145 | w ws-host WSADDR str "The websocket host clients connect to. Defaults to current host. (optional)"
146 | s secure bool "Flag to indicate whether the client should connect via wss. Defaults to false."
147 | ;; Client Configuration
148 | ^{:deprecated "--j/--on-jsload should go in the :boot-reload key of your .cljs.edn(s)."}
149 | j on-jsload SYM sym "The callback to call when JS files are reloaded. (deprecated)"
150 |
151 | _ client-opts OPTS edn "Options passed to the client directly (overrides the others)."
152 | v disable-hud bool "Toggle to disable HUD. Defaults to false (visible)."
153 | o open-file CMD str "The command to run when warning or exception is clicked on HUD. Passed to format. (optional)"
154 | ;; Other Configuration - AR - (?)
155 | _ asset-host HOST str "The asset-host where to load files from. Defaults to host of opened page. (optional)"
156 |
157 | ^{:deprecated "--a/--asset-path should go in the boot-cljs :compiler-options key of your .cljs.edn(s)."}
158 | a asset-path PATH str "Sets the output directory for temporary files used during compilation. (deprecated)"
159 |
160 | c cljs-asset-path PATH str "The actual asset path. This is added to the start of reloaded urls. (optional)"
161 | t target-path VAL str "Target path to load files from, used WHEN serving files using file: protocol. (optional)"
162 | _ only-by-re REGEX [regex] "Vector of path regexes (for `boot.core/by-re`) to restrict reloads to only files within these paths (optional)."]
163 |
164 | (let [pod (make-pod)
165 | tmp (tmp-dir!)
166 | prev-pre (atom nil)
167 | prev (atom nil)
168 | first-run? #(empty? @prev-pre)
169 | url (start-server @pod {:ip ip :port port :ws-host ws-host
170 | :ws-port ws-port :secure? secure
171 | :open-file open-file})]
172 | (b/cleanup (pod/with-call-in @pod (powerlaces.boot-figreload.server/stop)))
173 | (fn [next-task]
174 | (fn [fileset]
175 | ;; AR - TODO - pass the :open-file option down to figwheel and figure
176 | ;; out how to handle it
177 | (pod/with-call-in @pod
178 | (powerlaces.boot-figreload.server/set-options {:open-file ~open-file}))
179 |
180 | (let [changed-cljs-edns (relevant-cljs-edns (b/fileset-diff @prev-pre fileset :hash) ids)
181 | client-opts (merge {:project-id (-> (b/get-env) :directories string/join digest/md5)
182 | :websocket-host ws-host
183 | :websocket-url url
184 | :asset-host asset-host}
185 | client-opts)]
186 | (when (> @butil/*verbosity* 2)
187 | (butil/dbug "Client-opts:\n%s\n" (butil/pp-str client-opts)))
188 | (if-not (empty? changed-cljs-edns)
189 | (doseq [f changed-cljs-edns]
190 | (let [spec (read-cljs-edn-spec! f)
191 | path (tmp-path f) ;; this is relative to boot's fileset cache
192 | file (tmp-file f)
193 | build-config (make-build-config (merge client-opts (:boot-reload spec))
194 | (-> file .getPath util/build-id))]
195 | (when (> @butil/*verbosity* 2)
196 | (butil/dbug "Content of %s:\n%s\n" path (butil/pp-str spec)))
197 | (butil/dbug "Build config:\n%s\n" (butil/pp-str build-config))
198 | ;; Writing the connection script to the tmp folder
199 | (write-bootstrap-ns! @pod tmp build-config)
200 | ;; Add the init files to the fileset
201 | (add-init! @pod build-config spec (io/file tmp path))))
202 |
203 | (do (when (first-run?)
204 | (butil/warn "WARNING: No .cljs.edn file found.\n")
205 | (butil/warn "A .cljs.edn file is necessary for advanced features, boot-figreload might misbehave if missing.\n")
206 | (butil/warn "This is especially true for {:boot-reload {:on-jsload ...}}.\n"))
207 | ;; Special case: boot-cljs used without .cljs.edn
208 | ;; in that case we can just create a bootstrap namespace for
209 | ;; the "main" build id.
210 | ;; We do it only in case the diffing with the previous run did
211 | ;; not contain added/changed .cljs.edn itself.
212 | (when (empty? (relevant-cljs-edns fileset ids))
213 | (butil/dbug "No other .cljs.edn found on the fileset.")
214 | (let [build-config (make-build-config client-opts "main")]
215 | (butil/dbug* "Build config:\n%s\n" (butil/pp-str build-config))
216 | (write-bootstrap-ns! @pod tmp build-config))))))
217 |
218 | (reset! prev-pre fileset)
219 | (let [fileset (-> fileset (b/add-resource tmp) commit!)
220 | fileset (try
221 | (next-task fileset)
222 | (catch Exception e
223 | ;; FIXME: Supports only single error, e.g. less compiler
224 | ;; can give multiple errors.
225 | (if (and (not disable-hud)
226 | (or (= :boot-cljs (:from (ex-data e)))
227 | (:powerlaces.boot-figreload/exception (ex-data e))))
228 | (send-visual! @pod client-opts {:exception (util/serialize-exception e)}))
229 | (throw e)))]
230 | (let [cljs-edns (relevant-cljs-edns fileset ids)
231 | ;; cljs uses specific key for now
232 | ;; but any other file can contain warnings for boot-reload
233 | warnings (concat (mapcat :adzerk.boot-cljs/warnings cljs-edns)
234 | (mapcat :adzerk.boot-reload/warnings (b/input-files fileset)))
235 | cljs-opts-seq (cljs-opts-seq cljs-edns)
236 | static-files (->> cljs-edns
237 | (map b/tmp-path)
238 | (map(fn [x] (string/replace x #"\.cljs\.edn$" ".js")))
239 | set)]
240 | (butil/dbug* "Compile opts:\n%s\n" (butil/pp-str cljs-opts-seq))
241 | (when (> @butil/*verbosity* 2)
242 | (butil/dbug "Warnings:\n%s\n" (butil/pp-str warnings))
243 | (doseq [edn cljs-edns]
244 | (butil/dbug "Meta on %s:\n%s\n" (:path edn) (butil/pp-str (select-keys edn [])))))
245 | (if-not disable-hud
246 | (send-visual! @pod client-opts {:warnings warnings}))
247 | ;; Only send changed files when there are no warnings. As prev is
248 | ;; updated only when changes are sent, changes are queued until
249 | ;; they can be sent
250 | ;;
251 | ;; AR - the above assumption changes when using figwheel's client,
252 | ;; where the event order (unfortunately) matters.
253 | (send-changed! @pod
254 | client-opts
255 | {:target-path target-path
256 | :project-dirs (map str (util/project-dirs))
257 | :cljs-asset-path cljs-asset-path
258 | :cljs-opts-seq cljs-opts-seq
259 | :change-set (changed @prev fileset only-by-re static-files)})
260 | (reset! prev fileset)
261 | fileset))))))
262 |
--------------------------------------------------------------------------------
/src/powerlaces/boot_figreload/figwheel.clj:
--------------------------------------------------------------------------------
1 | (ns ^{:doc "Figwheel bindings and/or functions copied over."
2 | :author "Andrea Richiardi"}
3 | powerlaces.boot-figreload.figwheel
4 | (:require [boot.pod :as pod]
5 | [boot.util :as butil]
6 | [clojure.string :as str]
7 | [clojure.walk :as walk]
8 | [clojure.java.io :as io]
9 | [figwheel-sidecar.config :as config]
10 | [figwheel-sidecar.build-middleware.javascript-reloading :as js-reload]
11 | [figwheel-sidecar.cljs-utils.exception-parsing :as ex-parsing]
12 | [figwheel-sidecar.build-middleware.injection :as injection]
13 | [cljs.compiler]
14 | [cljs.closure]
15 | [powerlaces.boot-figreload.util :as util]
16 | [powerlaces.boot-figreload.messages :as msgs]))
17 |
18 | ;;;;;;;;;;;;;;;;;;;;
19 | ;; DIRECT IMPORTS ;;
20 | ;;;;;;;;;;;;;;;;;;;;
21 |
22 | (def ^{:doc "Generate the bootstrap namespace path string"
23 | :arglists '([build-config])}
24 | bootstrap-ns-name
25 | (var-get #'figwheel-sidecar.build-middleware.injection/figwheel-connect-ns-name))
26 |
27 | (def ^{:doc "Generate the bootstrap namespace path string"
28 | :arglists '([build-config])}
29 | bootstrap-ns-path
30 | (var-get #'figwheel-sidecar.build-middleware.injection/figwheel-connect-ns-path))
31 |
32 | (def ^{:doc "Generate the bootstrap namespace content"
33 | :arglists '([build-config])}
34 | bootstrap-ns-content
35 | (var-get #'figwheel-sidecar.build-middleware.injection/generate-connect-script))
36 |
37 | (defn wrap-msg
38 | "Add common fields to the message
39 |
40 | Note that msg keys always override in case of conflict."
41 | ([msg] (wrap-msg msg nil))
42 | ([msg opts]
43 | (-> opts
44 | (select-keys [:project-id :build-id])
45 | (assoc :figwheel-version config/_figwheel-version_)
46 | (merge msg)
47 | util/remove-nils)))
48 |
49 | ;; Reusing figwheel's make-sendable-file is not possible at the moment
50 | ;; https://github.com/bhauman/lein-figwheel/blob/e47da1658a716f83888e5a5164ee88e59b2d8c1e/sidecar/src/figwheel_sidecar/build_middleware/notifications.clj#L78
51 | ;; (intern *ns* 'make-sendable-file (var-get #'notifications/make-sendable-file))
52 |
53 | (defn- client-path
54 | "Return the path that the client uses (relative to :asset-path)
55 |
56 | The input file map "
57 | [compile-opts file-map]
58 | (assert (:relative-path file-map) "The file-map is missing some fields, this is a bug.")
59 | (-> (str/replace (:relative-path file-map)
60 | (or (:asset-path file-map) "")
61 | "")
62 | (str/replace #"^/" "")))
63 |
64 | (defn- guess-namespace
65 | [compile-opts file-map]
66 | (assert (:relative-path file-map) "The file-map is missing some fields, this is a bug.")
67 | (assert (:full-path file-map) "The file-map is missing some fields, this is a bug.")
68 | ;; Workaround for: https://github.com/bhauman/lein-figwheel/pull/537
69 | ;;
70 | ;; No figwheel-sidecar.build-middleware.javascript-reloading/guess-namespace
71 | ;; because deemed broken (sorry Bruce, it will be fixed I am sure).
72 | ;; We try to create the absolute path relative to the project root first.
73 | (let [js-file-attempts (conj (->> (util/project-dirs)
74 | (mapv #(io/file % (:relative-path file-map)))
75 | (filter #(.exists %))
76 | (mapv str))
77 | (:full-path file-map))]
78 | (butil/dbug* "Guessing namespace for %s\n" (butil/pp-str js-file-attempts))
79 | (assert (every? #(re-find #"\.js$" %) js-file-attempts)
80 | "Cannot guess namespace for files that are not Javascript. This is an bug, please report it.")
81 | (->> js-file-attempts
82 | (mapv cljs.closure/parse-js-ns)
83 | first
84 | :provides
85 | first
86 | cljs.compiler/munge)))
87 |
88 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
89 | ;; :msg-name :files-changed ;;
90 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
91 |
92 | (defn- sendable-js-map
93 | "Make a (Javascript) file map sendable according to figwheel protocol.
94 |
95 | Mimicking the function to
96 | figwheel-sidecar.build-middleware.notifications/make-sendable-file
97 |
98 | Sample return map:
99 | {:file \"resources/public/js/compiled/out/test_fig/core.js\"
100 | :namespace \"test_fig.core\"
101 | :type :namespace}"
102 | [js-and-opts]
103 | {:namespace (guess-namespace (:cljs-opts js-and-opts) js-and-opts)
104 | :file (client-path (:cljs-opts js-and-opts) js-and-opts)
105 | :type :namespace})
106 |
107 | (defn- sendable-css-map
108 | "Make a (Javascript) file map sendable according to figwheel protocol.
109 |
110 | Mimicking the function to
111 | figwheel-sidecar.build-middleware.notifications/make-sendable-file
112 |
113 | Sample return map:
114 | {:file \"resources/public/js/compiled/out/test_fig/core.js\"
115 | :namespace \"test_fig.core\"
116 | :type :namespace}"
117 | [js-and-opts]
118 | {:file (client-path (:cljs-opts js-and-opts) js-and-opts)
119 | :type :css})
120 |
121 | (defmethod msgs/file-payload-by-extension :js
122 | [opts [ext change-maps]]
123 | (when (seq change-maps)
124 | (-> {:msg-name :files-changed
125 | :files (mapv sendable-js-map change-maps)
126 | :figwheel-meta {"figwheel.client.utils" {:figwheel-no-load true}}}
127 | (wrap-msg opts))))
128 |
129 | (defmethod msgs/file-payload-by-extension :css
130 | [opts [ext change-maps]]
131 | (when (seq change-maps)
132 | (-> {:msg-name :css-files-changed
133 | :files (mapv sendable-css-map change-maps)}
134 | (wrap-msg opts))))
135 |
136 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
137 | ;; :msg-name :compile-warning ;;
138 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
139 |
140 | ;; AR - TODO handle multiple warnings, now we return only one
141 | (defmethod msgs/visual-payload-by-type :warnings
142 | [opts [type warning-maps]]
143 | (when (seq warning-maps)
144 | (-> {:msg-name :compile-warning
145 | :message (ex-parsing/parse-warning (first warning-maps))}
146 | (wrap-msg opts))))
147 |
148 | (defn trim-source-paths
149 | "Remove any :source-paths or :resource-paths parent from path"
150 | [path]
151 | (->> (:source-paths pod/env)
152 | (filter #(str/index-of path %))
153 | (map #(-> path
154 | (str/replace % "")
155 | (str/replace-first #"^\/" "")))))
156 |
157 | (defn- clean-class-form
158 | [form]
159 | (update form 1 #(-> % symbol resolve)))
160 |
161 | (defn- relativize-data-form
162 | "Replace absolute paths with relative ones"
163 | [relative-path form]
164 | (let [file (get-in form [1 :file])]
165 | (cond
166 | (or (not form)
167 | (not (string? relative-path))
168 | (not (string? file))) form
169 |
170 | ;; We replace only if it is a substring of the original
171 | (some (partial str/includes? file) (trim-source-paths relative-path))
172 | (assoc-in form [1 :file] relative-path)
173 |
174 | :else form)))
175 |
176 | (defn- relativize-cause-form
177 | "Replace absolute paths with relative ones where necessary"
178 | [relative-path form]
179 | (let [file (get-in form [1 :data :file])
180 | message (get-in form [1 :message])]
181 | (cond
182 | (or (not form)
183 | (not (string? file))
184 | (not (string? message))) form
185 |
186 | ;; We replace only if it is there is a substring in the original msg
187 | (str/includes? message file)
188 | (assoc-in form [1 :message] (-> message
189 | (str/replace file relative-path)
190 | (str/replace "file:" "")))
191 |
192 | :else form)))
193 |
194 | (defn figwheelify-exception
195 | "Boot-cljs to figwheel exception map
196 |
197 | Just to make it super clear, we receive a serialized exception map
198 | from boot-cljs and we want to convert it to a format that figwheel can
199 | parse."
200 | [ex]
201 | (let [relative-file (when (= :boot-cljs (get-in ex [:data :from]))
202 | (get-in ex [:data :file]))]
203 | (walk/prewalk #(cond
204 | ;; Convert string in :class to a java.lang.Class obj
205 | (util/map-entry-with-key? % :class) (clean-class-form %)
206 | ;; Make all the :data :file entries relative
207 | ;; I assume (!) the first exception contains the relative
208 | ;; path
209 | (and relative-file (util/map-entry-with-key? % :cause)) (relativize-cause-form relative-file %)
210 | (and relative-file (util/map-entry-with-key? % :data)) (relativize-data-form relative-file %)
211 | :else %)
212 | ex)))
213 |
214 | (defmethod msgs/visual-payload-by-type :exception
215 | [opts [type ex-map]]
216 | ;; AR - this is real hammering
217 | (-> {:msg-name :compile-failed
218 | :exception-data (-> ex-map
219 | figwheelify-exception
220 | (ex-parsing/parse-inspected-exception opts))}
221 | (wrap-msg opts)))
222 |
223 | (comment
224 | (def ex-map {:class "clojure.lang.ExceptionInfo", :message "Parameter declaration \".info\" should be a vector at line 10, column 1 in file src/figreload_demo/core.cljs\n", :data {:file "src/figreload_demo/core.cljs", :line 10, :column 1, :tag :cljs/analysis-error, :from :boot-cljs, :boot.util/omit-stacktrace? true}, :cause {:class "clojure.lang.ExceptionInfo", :message "failed compiling file:/home/arichiardi/.boot/cache/tmp/home/arichiardi/git/figreload-demo/mbk/7of19k/figreload_demo/core.cljs", :data {:file "/home/arichiardi/.boot/cache/tmp/home/arichiardi/git/figreload-demo/mbk/7of19k/figreload_demo/core.cljs"}, :cause {:class "clojure.lang.ExceptionInfo", :message "Parameter declaration \".info\" should be a vector at line 10 /home/arichiardi/.boot/cache/tmp/home/arichiardi/git/figreload-demo/mbk/7of19k/figreload_demo/core.cljs", :data {:file "/home/arichiardi/.boot/cache/tmp/home/arichiardi/git/figreload-demo/mbk/7of19k/figreload_demo/core.cljs", :line 10, :column 1, :tag :cljs/analysis-error}, :cause {:class "java.lang.IllegalArgumentException", :message "Parameter declaration \".info\" should be a vector", :data nil, :cause nil}}}})
225 | )
226 |
--------------------------------------------------------------------------------
/src/powerlaces/boot_figreload/messages.clj:
--------------------------------------------------------------------------------
1 | (ns ^{:doc "Message management."
2 | :author "Andrea Richiardi"}
3 | powerlaces.boot-figreload.messages)
4 |
5 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6 | ;; FILE CHANGED NOTIFICATIONS ;;
7 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
8 |
9 | (defn- file-map-matches?
10 | [file-map compile-opts]
11 | (assert (:asset-path compile-opts) "The compiler options :asset-path cannot be nil. This might be a bug.")
12 | (assert (:relative-path file-map) "The relative path in the file-map cannot be nil. This might be a bug.")
13 | (re-find (re-pattern (:asset-path compile-opts)) (:relative-path file-map)))
14 |
15 | (defn- assign-cljs-opts
16 | "Assign the correct cljs options to the file-map based on pred.
17 |
18 | The (fn [data compile-opts] ...) predicate decides what \"correct\"
19 | means, returning true or false according to the input data and the
20 | candidate compile-opts.
21 |
22 | The correct options will be assigned under the :cljs-opts key."
23 | [pred cljs-opts-seq file-maps]
24 | (mapv #(if-let [opts (->> cljs-opts-seq
25 | (filter (partial pred %))
26 | first)]
27 | (assoc % :cljs-opts opts)
28 | %)
29 | file-maps))
30 |
31 | (def ^{:private true
32 | :doc "Assign the correct cljs options to the correct file-map based on its path"
33 | :arglists '([cljs-opts-seq file-maps])}
34 | assign-cljs-opts-by-path
35 | (partial assign-cljs-opts file-map-matches?))
36 |
37 | (defmulti file-payload-by-extension
38 | "Calculate payloads of change-maps based on extension.
39 |
40 | The first parameter is obviously the client option map, while the
41 | second is a vector containing extension first and then a seq of file
42 | maps:
43 |
44 | [:css [[file-map1] [file-map2]]]"
45 | (fn [client-opts [ext change-maps]] ext))
46 |
47 | (defmethod file-payload-by-extension :default
48 | [_ _]
49 | (assert false "This should never happen, did you forget to handle a file extension?"))
50 |
51 | (defn changed-messages
52 | "Return (file) change-related messages"
53 | [client-opts watch-map]
54 | (let [change-maps-by-ext (select-keys
55 | (->> watch-map
56 | :change-set
57 | (assign-cljs-opts-by-path (:cljs-opts-seq watch-map))
58 | (group-by #(->> (:relative-path %)
59 | (re-find #"\.(\p{Alnum}+$)")
60 | second
61 | keyword)))
62 | [:css :js])]
63 | (map (partial file-payload-by-extension client-opts) change-maps-by-ext)))
64 |
65 | ;;;;;;;;;;;;;;;;;;;;;;;;;;
66 | ;; VISUAL NOTIFICATIONS ;;
67 | ;;;;;;;;;;;;;;;;;;;;;;;;;;
68 |
69 | (defmulti visual-payload-by-type
70 | "Calculate payloads of a visual-map based on it type
71 |
72 | The first parameter is obviously the client option map, while the
73 | second is a vector [type data]. The dispatch is on the type."
74 | (fn [client-opts [type data]] type))
75 |
76 | (defmethod visual-payload-by-type :default
77 | [_ _]
78 | (assert false "This should never happen, did you forget to handle a visual notification type?"))
79 |
80 | (defn visual-message
81 | "Return a visual message.
82 |
83 | It expects something like:
84 | {:warnings [{:k1 :v1} {:k2 :v2} ...]}
85 | ^- type ^- data (not only seqs, it depends on the the type)"
86 | [client-opts visual-map]
87 | ;; AR - I know I know we need clojure.spec
88 | (visual-payload-by-type client-opts (first (seq visual-map))))
89 |
--------------------------------------------------------------------------------
/src/powerlaces/boot_figreload/server.clj:
--------------------------------------------------------------------------------
1 | (ns powerlaces.boot-figreload.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 | [powerlaces.boot-figreload.figwheel :as figwheel]
8 | [powerlaces.boot-figreload.messages :as msgs])
9 | (:import
10 | [java.io IOException]))
11 |
12 | (def options (atom {:open-file nil}))
13 | (def clients (atom #{}))
14 | (def stop-fn (atom nil))
15 |
16 | (defn set-options [opts]
17 | (reset! options opts))
18 |
19 | (defn bootstrap-ns
20 | "Computes the bootstrap namespace
21 |
22 | Return [ns-sym ns-path ns-content]."
23 | [build-config]
24 | [(figwheel/bootstrap-ns-name build-config)
25 | (figwheel/bootstrap-ns-path build-config)
26 | (->> build-config
27 | figwheel/bootstrap-ns-content
28 | (map util/pp-str)
29 | (interpose "\n")
30 | (apply str))])
31 |
32 | ;; AR The cousin of:
33 | ;; https://github.com/bhauman/lein-figwheel/blob/cc2d188ab041fc92551d3c4a8201729c47fe5846/sidecar/src/figwheel_sidecar/build_middleware/injection.clj#L171
34 | (defn- add-cljs-edn-init
35 | "Add requires and other init stuff to the .cljs.edn spec
36 |
37 | Return the new cljs.edn spec as data (not a string)."
38 | [build-config cljs-spec]
39 | (update cljs-spec :require #(->> (conj %
40 | (-> build-config
41 | figwheel/bootstrap-ns-name
42 | symbol)
43 | (when (get-in build-config [:figwheel :devcards])
44 | 'devcards.core))
45 | (into #{})
46 | (remove nil?)
47 | vec)))
48 |
49 | ;;;;;;;;;;;;;;;
50 | ;; WEBSOCKET ;;
51 | ;;;;;;;;;;;;;;;
52 |
53 | (defn send-changed!
54 | ([change-map] (send-changed! {} change-map))
55 | ([opts change-map]
56 | (when (> @boot.util/*verbosity* 2)
57 | (util/dbug "Watch received:\n%s\n" (util/pp-str change-map)))
58 | (doseq [channel @clients]
59 | (let [payloads (->> change-map
60 | (msgs/changed-messages opts)
61 | (mapv #(merge % (select-keys opts [:figwheel-version]))))]
62 | (run! #(let [payload (util/pp-str %)]
63 | (util/dbug "Sending:\n%s\n" payload)
64 | (http/send! channel payload))
65 | payloads)))))
66 |
67 | (defn send-visual!
68 | "Send a visual notification to the connected clients.
69 |
70 | A visual map is a map that has its key equal to the type (:warning,
71 | exception, ...) of the notification and its value equal to a sequence
72 | of payloads."
73 | ([visual-map] (send-visual! {} visual-map))
74 | ([opts visual-map]
75 | (when (> @boot.util/*verbosity* 2)
76 | (util/dbug "Watch received:\n%s\n" visual-map))
77 | (doseq [channel @clients]
78 | ;; AR - TODO handle multiple warnings and exceptions
79 | (let [payload (->> visual-map
80 | (msgs/visual-message opts)
81 | (merge (select-keys opts [:figwheel-version])))]
82 | (when-not (empty? payload)
83 | (util/dbug "Sending:\n%s\n" payload)
84 | (http/send! channel (util/pp-str payload)))))))
85 |
86 | ;;;;;;;;;;;;;;;
87 | ;; WEBSOCKET ;;
88 | ;;;;;;;;;;;;;;;
89 |
90 | (defmulti handle-message (fn [channel message] (:figwheel-event message)))
91 |
92 | (defmethod handle-message :default [channel message]
93 | (util/warn "Received Figwheel message %s: not supported at this time\n" (:figwheel-event message))
94 | (util/dbug* "Figwheel Message:\n%s\n" (util/pp-str message)))
95 |
96 | (defn connect! [channel]
97 | (util/dbug "Channel \"%s\" opened...\n" (str channel))
98 |
99 | (swap! clients conj channel)
100 | (when (> @boot.util/*verbosity* 2)
101 | (util/dbug "Connected clients %s\n" (mapv str @clients)))
102 |
103 | (http/on-close channel (fn [_] (swap! clients disj channel)))
104 | (http/on-receive channel (fn [data]
105 | (when (> @boot.util/*verbosity* 2)
106 | (util/dbug "Websocket received:\n%s\n" (util/pp-str data)))
107 | (handle-message channel (read-string data))))
108 |
109 | (util/info "New websocket client connected!\n"))
110 |
111 | (defn handler [request]
112 | (if-not (:websocket? request)
113 | {:status 501 :body "Websocket connections only."}
114 | (do (when (> @boot.util/*verbosity* 2)
115 | (util/dbug* "Websocket received:\n%s\n" (util/pp-str request)))
116 | (http/with-channel request channel (connect! channel)))))
117 |
118 | (defn start
119 | [{:keys [ip port] :as opts}]
120 | (let [o {:ip (or ip "0.0.0.0") :port (or port 0)}
121 | stop-fn* (http/run-server handler o)]
122 | (reset! stop-fn stop-fn*)
123 | (assoc o :port (-> stop-fn* meta :local-port))))
124 |
125 | (defn stop []
126 | (when @stop-fn
127 | (@stop-fn)
128 | (reset! stop-fn nil)))
129 |
--------------------------------------------------------------------------------
/src/powerlaces/boot_figreload/util.clj:
--------------------------------------------------------------------------------
1 | (ns powerlaces.boot-figreload.util
2 | (:require [clojure.string :as str]
3 | [clojure.java.io :as io]
4 | [clojure.walk :as walk]
5 | [boot.file :as file]
6 | [boot.from.digest :as digest])
7 | (:import [java.io File]))
8 |
9 | ;; From cljs/analyzer.cljc
10 | (def js-reserved
11 | #{"arguments" "abstract" "boolean" "break" "byte" "case"
12 | "catch" "char" "class" "const" "continue"
13 | "debugger" "default" "delete" "do" "double"
14 | "else" "enum" "export" "extends" "final"
15 | "finally" "float" "for" "function" "goto" "if"
16 | "implements" "import" "in" "instanceof" "int"
17 | "interface" "let" "long" "native" "new"
18 | "package" "private" "protected" "public"
19 | "return" "short" "static" "super" "switch"
20 | "synchronized" "this" "throw" "throws"
21 | "transient" "try" "typeof" "var" "void"
22 | "volatile" "while" "with" "yield" "methods"
23 | "null" "constructor"})
24 |
25 | (defn build-id
26 | "Return the build id from the .cljs.edn file path (as string)."
27 | [cljs-edn-path]
28 | (assert (and (re-find #"\.cljs\.edn$" cljs-edn-path)
29 | (re-find #"(\/|\\)" cljs-edn-path))
30 | (format "Build-id expects a .cljs.edn file path. Received %s, this might be a bug." cljs-edn-path))
31 | (let [bid (str/replace cljs-edn-path #"(.*)(\/|\\)(\w+)(\.cljs\.edn)$" "$3")]
32 | (cond-> bid
33 | (contains? js-reserved bid) (str "$")
34 | true (str "_" (->> cljs-edn-path digest/sha-1 (take 8) (str/join))))))
35 |
36 | ;;
37 | ;; Exception serialization
38 | ;; Also see: https://github.com/boot-clj/boot/issues/553
39 | ;;
40 |
41 | (defn safe-data [data]
42 | (walk/postwalk
43 | (fn [x]
44 | (cond
45 | (instance? File x) (.getPath x)
46 | :else x))
47 | data))
48 |
49 | (defn serialize-exception
50 | "Serializes given exception keeping original message, stack-trace, cause stack
51 | and ex-data for ExceptionInfo.
52 |
53 | Certain types in ex-data are converted to strings. Currently this includes
54 | Files."
55 | [e]
56 | {:class (-> e type .getName) ;; AR - this is ignored by the deserializer
57 | :message (.getMessage e)
58 | :data (safe-data (ex-data e)) ;; AR - no :ex-data, figwheel likes :data
59 | :cause (when-let [cause (.getCause e)]
60 | (serialize-exception cause))})
61 |
62 | (defn remove-nils [m]
63 | (let [f (fn [x]
64 | (if (map? x)
65 | (let [kvs (filter (comp not nil? second) x)]
66 | (if (empty? kvs) nil (into {} kvs)))
67 | x))]
68 | (walk/postwalk f m)))
69 |
70 | (defn map-entry-with-key?
71 | [form k]
72 | (and (vector? form) (= k (first form))))
73 |
74 | (defn project-root
75 | "Return the project root as string."
76 | [] ^String
77 | ;; AR - consider taking a more rigorous approach, for instance:
78 | ;; https://github.com/clojure-emacs/refactor-nrepl/blob/v2.3.1/src/refactor_nrepl/core.clj#L61
79 | (System/getProperty "user.dir"))
80 |
81 | (defn project-dirs
82 | "Return all project dirs added to either source, resources or assets.
83 |
84 | The output is a vector of java.io.File objects and uses
85 | `fake.class.path` + `user.dir` for doing its job."
86 | []
87 | (let [files-on-cp (map io/file (str/split (System/getProperty "fake.class.path") #":"))
88 | project-root-path (.toPath (io/file (project-root)))]
89 | (->> files-on-cp
90 | (filter #(.isDirectory %))
91 | (filter #(.startsWith (.toPath %) project-root-path)))))
92 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | echo "1.8.0"
6 | BOOT_CLOJURE_VERSION=1.8.0 boot test
7 |
8 | echo
9 | echo "1.7.0"
10 | BOOT_CLOJURE_VERSION=1.7.0 boot test
11 |
12 | echo
13 | echo "1.6.0"
14 | BOOT_CLOJURE_VERSION=1.6.0 boot test
15 |
--------------------------------------------------------------------------------
/test/powerlaces/boot_figreload/util_test.clj:
--------------------------------------------------------------------------------
1 | (ns powerlaces.boot-figreload.util-test
2 | (:require [powerlaces.boot-figreload.util :as util]
3 | [clojure.java.io :as io]
4 | [clojure.test :as test :refer [deftest is]]))
5 |
6 | (deftest build-id
7 | (is (= (re-find #"^main-.*" (util/build-id "/my/project/src/my_namespace/main.cljs.edn"))) "Should correctly take the part before .cljs.edn as build-id")
8 | (is (= (re-find #"^transient$-.*" (util/build-id "/my/project/src/my_namespace/transient.cljs.edn"))) "Should correctly escape JS reserved words as build-id")
9 | (is (= (re-find #"^main-.*" (util/build-id "/my/project/src/scripts/main.cljs.edn"))) "Should correctly get only the .cljs.edn filename as build-id")
10 | (is (thrown? java.lang.AssertionError (util/build-id "transient.edn")) "Should throw if we are not passing a .cljs.edn file")
11 | (is (thrown? java.lang.AssertionError (util/build-id "transient.cljs.edn")) "Should throw if we are not passing a path"))
12 |
--------------------------------------------------------------------------------