Giter VIP home page Giter VIP logo

lein-figwheel's Introduction

lein-figwheel

CircleCI

Figwheel builds your ClojureScript code and hot loads it into the browser as you are coding!

A new Figwheel!!

There is a new Figwheel in town!

Figwheel Main is a complete re-write of Figwheel and represents the latest and greatest version of Figwheel. It works great with Leiningen or the new Clojure CLI Tools.

So head over to Figwheel Main to give it a try.

lein-figwheel

Get a quick idea of what figwheel does by watching the 6 minute flappy bird demo of figwheel.

Learn even more by watching a 45 minute talk on Figwheel given at ClojureWest 2015.

Read the introductory blog post.

Support Figwheel

If Figwheel has fundamentally redefined the way you do front-end work please take a moment and support it:

Donated so far:     2015: $73     2016: $2752     2017(through October): $1979

Current version:

Clojars Project

Figwheel heads up example

Features

Live code reloading

If you write reloadable code, figwheel can facilitate automated live interactive programming. Every time you save your ClojureScript source file, the changes are sent to the browser so that you can see the effects of modifying your code in real time.

Supports Node.js

You can use figwheel to live code ClojureScript in Node.js!

Static file server

The inclusion of a static file server allows you to get a decent ClojureScript development environment up and running quickly. For convenience there is a :ring-handler option so you can load a ring handler into the figwheel server.

Live CSS reloading

Figwheel will reload your CSS live as well.

Live JavaScript reloading

Figwheel can live reload your JavaScript source files.

Heads up display

Figwheel has a non-intrusive heads up display that gives you feedback on how well your project is compiling. By writing a shell script you can click on files in the heads up display and they will open in your editor!

Descriptive Errors with Code Context

Figwheel provides descriptive compiler errors that point to where the error is in your code. These errors appear in the REPL as well as the heads up display.

First Class Configuration Error Reporting

It can be quite daunting, when you are configuring a tool for the first time. Figwheel currently offers best-of-class configuration error reporting that will help you if you happen to misconfigure something.

Built-in ClojureScript REPL

When you launch Figwheel it not only starts a live building/reloading process but it also optionally launches a CLJS REPL into your running application. This REPL shares compilation information with the figwheel builder, so as you change your code the REPL is also aware of the code changes. The REPL also has some special built-in control functions that allow you to control the auto-building process and execute various build tasks without having to stop and rerun lein-figwheel.

Robust connection

Figwheel's connection is fairly robust. I have experienced figwheel sessions that have lasted for days through multiple OS sleeps. You can also use figwheel like a REPL if you are OK with using print to output the evaluation results to the browser console.

Message broadcast

Figwheel broadcasts changes to all connected clients. This means you can see code and CSS changes take place in real time on your phone and in your laptop browser simultaneously.

Respects dependencies

Figwheel will not load a file that has not been required. It will also respond well to new requirements and dependency tree changes.

Calculates minimal reload set

Figwheel does its best to only reload what needs to be reloaded. This minimizes the surface area of dynamically reloaded code, which in turn should increase the stability of the client environment.

Doesn't load code that is generating warnings

If your ClojureScript code is generating compiler warnings Figwheel won't load it. This, again, is very helpful in keeping the client environment stable. This behavior is optional and can be turned off.

Try Figwheel

Make sure you have the latest version of leiningen installed.

You can try figwheel out quickly with the flappy bird demo:

git clone https://github.com/bhauman/flappy-bird-demo.git

then cd into flappy-bird-demo and type

lein figwheel

You can now goto localhost:3449/index.html and open up src/flappy_bird_demo/core.cljs with your favorite editor and start coding. Make sure you open your browser's development console so you can get feedback about code reloads.

If you would prefer to greenfield a new project you can use the figwheel leiningen template.

lein new figwheel hello-world

Or optionally:

    lein new figwheel hello-world -- --om       ;; for an om based project
    lein new figwheel hello-world -- --reagent  ;; for a reagent based project 

Learning ClojureScript

If you are brand new to ClojureScript it is highly recommended that you do the ClojureScript Quick Start first. If you skip this you will probably suffer.

There is a lot to learn when you are first learning ClojureScript, I recommend that you bite off very small pieces at first. Smaller bites than you would take when learning other languages like JavaScript and Ruby.

Please don't invest too much time trying to set up a sweet development environment, there is a diverse set of tools that is constantly in flux and it's very difficult to suss out which ones will actually help you. If you spend a lot of time evaluating all these options it can become very frustrating. If you wait a while, and use simple tools you will have much more fun actually using the language itself.

Quick Start

If you are new to Figwheel here is a Quick Start tutorial. Working through this Quick Start will probably save you a tremendous amount of time.

Getting Help

You can get help at both the ClojureScript Google Group

and on the #clojurescript, #lein-figwheel and #beginners Clojurians Slack Channels

Usage

Make sure you have the latest version of leiningen installed.

Then include the following :dependencies in your project.clj file.

[org.clojure/clojure "1.9.0"]
[org.clojure/clojurescript "1.10.238"]

Then include lein-figwheel in the :plugins section of your project.clj.

[lein-figwheel "0.5.18"]

Configure your builds

You also need to have your :cljsbuild configuration set up in your project.clj.

Here is an example:

:cljsbuild {
  :builds [ { :id "example" 
              :source-paths ["src/"]
              :figwheel true
              :compiler {  :main "example.core"
                           :asset-path "js/out"
                           :output-to "resources/public/js/example.js"
                           :output-dir "resources/public/js/out" } } ]
}

The important part here is that you have to have at least one build that has :optimizations set to :none or nil.

If you leave out the :optimizations key the ClojureScript compiler will default to :none.

Setting :figwheel true or :figwheel { :on-jsload "example.core/reload-hook" } will automagically insert the figwheel client code into your application. If you supply :on-jsload the name of a function, that function will be called after new code gets reloaded.

If you want to serve the HTML file that will host your application from figwheel's built in server, then the output directory has to be in a directory that can be served by the static webserver. The default for the webserver root is "resources/public" so your output files need to be in a subdirectory of "resources/public" unless you change the webserver root. For now the webserver root has to be in a subdirectory of resources.

If you are serving your application HTML from your own server you can configure :output-to and :output-dir as you like.

Start the figwheel server. (This will get the first :optimizations :none build)

$ lein figwheel

You also have the option to specify one or more builds

$ lein figwheel example
$ lein figwheel example example-devcards

This will start a server at http://localhost:3449 with your resources being served via the compojure resources ring handler.

So you can load the HTML file that's hosting your ClojureScript app by going to http://localhost:3449/<yourfilename>.html

If you are using your own server please load your app from that server.

Figwheel server side configuration

This is not necessary but you can configure the figwheel system. At the root level of your project.clj you can add the following server side configuration parameters:

:figwheel {
   :http-server-root "public" ;; this will be in resources/
   :server-port 5309          ;; default is 3449
   :server-ip   "0.0.0.0"     ;; default is "localhost"

   ;; CSS reloading (optional)
   ;; :css-dirs has no default value 
   ;; if :css-dirs is set figwheel will detect css file changes and
   ;; send them to the browser
   :css-dirs ["resources/public/css"]

   ;; Server Ring Handler (optional)
   ;; if you want to embed a ring handler into the figwheel http-kit
   ;; server
   :ring-handler example.server/handler

   ;; Clojure Macro reloading
   ;; disable clj file reloading
   ; :reload-clj-files false
   ;; or specify which suffixes will cause the reloading
   ; :reload-clj-files {:clj true :cljc false}

   ;; To be able to open files in your editor from the heads up display
   ;; you will need to put a script on your path.
   ;; that script will have to take a file path, a line number and a column
   ;; ie. in  ~/bin/myfile-opener
   ;; #! /bin/sh
   ;; emacsclient -n +$2:$3 $1 
   ;;
   :open-file-command "myfile-opener"

   ;; if you want to disable the REPL
   ;; :repl false

   ;; to configure a different figwheel logfile path
   ;; :server-logfile "tmp/logs/figwheel-logfile.log" 

   ;; Start an nREPL server into the running figwheel process
   ;; :nrepl-port 7888

   ;; Load CIDER, refactor-nrepl and piggieback middleware
   ;;  :nrepl-middleware ["cider.nrepl/cider-middleware"
   ;;                     "refactor-nrepl.middleware/wrap-refactor"
   ;;                     "cemerick.piggieback/wrap-cljs-repl"]

   ;; if you need to watch files with polling instead of FS events
   ;; :hawk-options {:watcher :polling}     
   ;; ^ this can be useful in Docker environments

   ;; if your project.clj contains conflicting builds,
   ;; you can choose to only load the builds specified
   ;; on the command line
   ;; :load-all-builds false ; default is true
} 

Client side usage

Make sure you have setup an html file to host your cljs. For example you can create this resources/public/index.html file:

<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <div id="main-area">
    </div>
    <script src="js/example.js" type="text/javascript"></script>   
  </body>
</html>

CSS Precompilers

Using SASS or LESS and still want to have the benefits of live CSS reloading?

Simply run your sass or less watcher/compiler on the command line and make sure the final output CSS files land in one of the directories that you have listed in your :css-dirs configuration option (mentioned above).

See lein-cooper for a familiar way to launch processes from lein.

Client side configuration options

Instead of setting :figwheel true in your cljsbuild configuration you can pass a map of options as below:

:cljsbuild {
  :builds [ { :id "example" 
              :source-paths ["src/"]

              ;; put client config options in :figwheel
              :figwheel { :websocket-host "localhost" 
                          :on-jsload "example.core/fig-reload"}
                          
              :compiler {  :main "example.core"
                           :asset-path "js/out"
                           :output-to "resources/public/js/example.js"
                           :output-dir "resources/public/js/out"
                           :optimizations :none } } ]
}

The following configuration options are available:

;; Configure :websocket-host for the figwheel js client to connect to.
;; (Don't specify the port; figwheel already knows it).
;; Defaults to "localhost".  Valid values are:
;; 
;;   <any-string>      Uses that exact string as hostname.
;;
;;   :js-client-host   Uses window.location.hostname from JS.  This is useful when connecting
;;                     from a different device/computer on your LAN, e.g. testing mobile
;;                     safari.
;;
;;   :server-ip        Uses the IP address of the figwheel server.  This is Useful in special
;;                     situations like an iOS (WK)WebView.  Be sure to check your CORS headers.
;;
;;   :server-hostname  Like :server-ip, but uses hostname string rather than IP address.
;;                     (On unix, check that `hostname` outputs the right string in shell).
;;
:websocket-host :js-client-host

;; optional callback
:on-jsload "example.core/fig-reload"

;; if you want to do REPL based development and not have
;; have compiled files autoloaded into the client env
:autoload false

;; The heads up display is enabled by default; to disable it: 
:heads-up-display false

;; when the compiler emits warnings figwheel blocks the loading of files.
;; To disable this behavior:
:load-warninged-code true

;; You can override the websocket url that is used by the figwheel client
;; by specifying a :websocket-url
;;
;; The value of :websocket-url is usually
;; :websocket-url "ws://localhost:3449/figwheel-ws"
;;
;; The :websocket-url is normally derived from the :websocket-host option.
;; If you supply a :websocket-url the :websocket-host option will be ignored.
;;
;; The :websocket-url allows you to use tags for common dynamic values.
;; For example in:
;; :websocket-url "ws://[[client-hostname]]:[[server-port]]/figwheel-ws"
;; Figwheel will fill in the [[client-hostname]] and [[server-port]] tags
;;
;; Available tags are
;; [[server-hostname]]
;; [[server-ip]]
;; [[server-port]]
;; [[client-hostname]]
;; [[client-port]]

More Figwheel Configuration Information

All Figwheel configuration options are fully specified in sidecar/src/figwheel_sidecar/schemas/config.clj.

This is currently the ultimate configuration reference. (I'm planning on generating an official config reference from this file.)

Preventing and forcing file reloads

Figwheel normally reloads any file that has changed. If you want to prevent certain files from being reloaded by figwheel, you can add meta-data to the namespace declaration like so:

(ns ^:figwheel-no-load example.core)

Figwheel will not load or reload files that haven't been required by your application. If you want to force a file to be loaded when it changes add the follwoing meta-data the namespace declaration of the file:

(ns ^:figwheel-load example.core)

It can be very helpful to have a file reload every time a file changes in your ClojureScript source tree. This can facilitate reloading your main app and running tests on change.

To force a file to reload on every change:

(ns ^:figwheel-always example.test-runner)

Using the ClojureScript REPL

When you run lein figwheel a REPL will be launched into your application.

You will need to open your application in a browser in order for the REPL to connect and show its prompt.

This REPL is a little different than other REPLs in that it has live compile information from the build process. This effectively means that you will not have to call (require or (load-namespace unless it is a namespace that isn't in your loaded application's required dependencies. In many cases you can just (in-ns 'my.namespace) and everything you need to access will be there already.

The REPL get's its syntax highlighting and other features from the rebel-readline library.

You can type :repl/help to learn more about how to use it.

For Windows: Rebel-readline will not be automatically included on windows you will have to use lein trampoline figwheel in order to get rebel-readline. And this should be done with the knowledge that the classpath may be corrupted if it gets too long and thus things will stop working. See Scripting Figwheel below if you want to use rebel-readline in a stable manner

REPL Figwheel control functions.

The Figwheel REPL has the following control functions:

Figwheel Controls:
 (stop-autobuild)            ;; stops Figwheel autobuilder
 (start-autobuild [id ...])  ;; starts autobuilder focused on optional ids
 (switch-to-build id ...)    ;; switches autobuilder to different build
 (reset-autobuild)           ;; stops, cleans, and starts autobuilder
 (build-once [id ...])       ;; builds source one time
 (clean-builds [id ..])      ;; deletes compiled cljs target files
 (fig-status)                ;; displays current state of system

These functions are special functions that poke through the ClojureScript env into the underlying Clojure process. As such you can't compose them.

You can think of these functions having an implicit set of build ids that they operate on.

If you call (reset-autobuild) it will stop the figwheel autobuilder, clean the builds, reload the build configuration from your project.clj and then restart the autobuild process.

If you call (stop-autobuild) it will stop the figwheel autobuilder.

If you call (start-autobuild) it will start the figwheel autobuilder with the current implicit build ids.

If you call (start-autobuild example) it will start the figwheel autobuilder on the provided build id example. It will also make [example] the implicit set of build ids.

start-autobuild and switch-to-build are the only functions that update the build-id set.

clean-builds and build-once both allow you to do one off builds and cleans. They do not alter the implicit build ids.

fig-status displays information on the current Figwheel system state, including whether the autobuilder is running, which build ids are in focus, and the number of client connections.

Editor REPLs and nREPL

You may want a REPL in your editor. This makes it much easier to ship code from your buffer to be evaluated.

If you use lein repl or something that invokes it like CIDER, you are using nREPL. A ClojureScript REPL will not just run over an nREPL connection without Piggieback.

If you are just starting out I would use the Figwheel console REPL because it's aready set up and ready to go, complexity conquered!

If you want to integrate a REPL into your editor, here are my top recommendations:

Emacs:

  • use inf-clojure as described on the wiki page
  • alternatively use Cider and nREPL. Using the ClojureScript REPL over an nREPL connection is considered advanced

Cursive: use the instructions on the wiki page

Vi: use tmux mode to interact with the figwheel REPL, still trying to get a wiki page for this if you can help that would be great

If you are going to use nREPL with Figwheel please see:

Using Figwheel within NRepl

Scripting Figwheel

As your development workflow grows in complexity, the declarative approach of lein can be limiting when you want to launch and control different services (ie. SASS compilation). It is really helpful to use Clojure itself to script whatever workflow services you want.

Figwheel has a Clojure API that makes it easy to start, stop and control Figwheel from Clojure.

In order for the following examples to work, you will need to have [figwheel-sidecar "0.5.18"] and [com.bhauman/rebel-readline "0.1.4"] in your dependencies.

To start Figwheel from a script, you will need to require the figwheel-sidecar.repl-api and provide your build configuration to figwheel-sidecar.repl-api/start-figwheel! like so:

(require '[figwheel-sidecar.repl-api :as ra])

;; this will start figwheel and will start autocompiling the builds specified in `:builds-ids`
(ra/start-figwheel!
  {:figwheel-options {} ;; <-- figwheel server config goes here 
   :build-ids ["dev"]   ;; <-- a vector of build ids to start autobuilding
   :all-builds          ;; <-- supply your build configs here
   [{:id "dev"
     :figwheel true
     :source-paths ["src"]
     :compiler {:main "example.core"
                :asset-path "out"
                :output-to "resources/public/main.js"
                :output-dir "resources/public/out"
                :verbose true}}]})
                
;; you can also just call (ra/start-figwheel!)
;; and figwheel will do its best to get your config from the
;; project.clj or a figwheel.edn file

;; start a ClojureScript REPL
(ra/cljs-repl)
;; you can optionally supply a build id
;; (ra/cljs-repl "dev")

Build config notes

It's important to remember that figwheel can autobuild and reload multiple builds at the same time. It can also switch between builds and focus on autobuilding one at a time. For this reason you need to supply the initial :build-ids to tell figwheel which builds you want to start building. It's also really helpful to supply your :advanced builds because while you can't autobuild them you can call build-once on them

Assuming the above script is in script/figwheel.clj you can invoke it as follows:

$ lein trampoline run -m clojure.main script/figwheel.clj

The above command will start figwheel and it will behave just like running lein figwheel.

Please note that the above command is not running the script in the same environment as lein repl or cider-jack-in. Both of these start an nREPL session. I am intentionally not using nREPL in order to remove a lot of complexity from ClojureScript REPL communication.

If you are using nREPL, launching the ClojureScript REPL requires that you have Piggieback installed. Please see the section above titled "Editor REPLs and nREPL"

Let's make a small helper library and then initialize a Clojure REPL with it:

(require
 '[figwheel-sidecar.repl-api :as ra])

(defn start []
  (ra/start-figwheel!
    {:figwheel-options {} ;; <-- figwheel server config goes here 
     :build-ids ["dev"]   ;; <-- a vector of build ids to start autobuilding
     :all-builds          ;; <-- supply your build configs here
     [{:id "dev"
       :figwheel true
       :source-paths ["src"]
       :compiler {:main "example.core"
                  :asset-path "out"
                  :output-to "resources/public/main.js"
                  :output-dir "resources/public/out"
                  :verbose true}}]}))

;; Please note that when you stop the Figwheel Server http-kit throws
;; a java.util.concurrent.RejectedExecutionException, this is expected

(defn stop []
  (ra/stop-figwheel!))

(defn repl []
  (ra/cljs-repl))

The next line will call clojure.main and initialize it with our script and then continue on to launch a REPL.

$ lein trampoline run -m clojure.main --init script/figwheel.clj -m rebel-readline.main

After the Clojure REPL has launched, you will now have the ability to call (start), (repl) and (stop) as you need.

You can also call all of the functions in the figwheel-sidecar.repl-api.

This is a powerful way to work, as you now have the interactivity and generality of the Clojure programming language available.

Need to start a server? Go for it.
Need to watch and compile SASS files? No problem.

Tips and Support

Figwheel was created out of the pure desire to make programming more fun. While I have been lucky to receive a couple spontaneous donations, it is not currently sponsored in any way.

If you like Figwheel and want to support its development:

Not Magic, just plain old file reloading

This plugin starts a ClojureScript auto builder, opens a websocket and starts static file server. When you save a ClojureScript file, Figwheel will detect that and compile it and other affected files. It will then pass a list of those changed files off to the figwheel server. The figwheel server will in turn push the paths of the relevant compiled javascript files through a websocket so that the browser can reload them.

The main motivation for lein-figwheel is to allow for the interactive development of ClojureScript. Figwheel doesn't provide this out of the box, the developer has to take care to make their code reloadable.

Writing reloadable code

Figwheel relies on having files that can be reloaded.

Reloading works beautifully on referentially transparent code and code that only defines behavior without bundling state with the behavior.

If you are using React or Om it's not hard to write reloadable code, in fact you might be doing it already.

There are several coding patterns to look out for when writing reloadable code.

One problematic pattern is top level definitions that have local state.

(def state (atom {}))

The state definition above is holding an atom that has local state. Every time the file that holds this definition gets reloaded the state definition will be redefined and the state it holds will be reset back to the original state. But with figwheel we are wanting to change our programs while maintaining the state of the running program.

The way to fix this is to use defonce

(defonce state (atom {}))

This will fix most situations where you have code that is relying on a definition that has local state. Keep in mind though that if you change the code that is wrapped in a defonce you won't see the changes, because the identifier won't be redefined.

Complicated object networks wired together with callbacks (Backbone, Ember, etc.) are also problematic. Instantiating these object callback networks and then storing them in a global var is yet another version of this problem.

Functions that maintain local state like counters and such are also definitions with local state, and as such are problematic.

You also need to look out for common setup code that hooks into the browser.

Often you will see statements like this at the bottom of a file.

(.click ($ "a.button") (fn [e] (print "clicked button")))

Every time this file gets loaded a new listener will get added to all the anchor tags with a "button" class. This is obviously not what we want to happen.

This code is very problematic and points to the why using the browser APIs directly has always been really difficult. For instance if we make it so that these hooks are only executed once, like so:

(defonce setup-stuff 
  (do 
     (.click ($ "a.button") (fn [e] (print "clicked button")))))

When you are live editing code, this doesn't work very well. If you alter your HTML template any new "a.button" elements aren't going to have the listener bound to them.

You can fix this by using an event delegation strategy as so:

(defonce setup-stuff 
  (do 
     (.on ($ "div#app") "click" "a.button" (fn [e] (print "clicked button")))))

But even with the above strategy you won't be able to edit any of the code in the setup up block and see your changes take effect.

If you are not using React and you want to build things this way and have reloadable code we need to create setup and teardown functions to be invoked on code reload.

(defn setup []
   (.on ($ "div#app") "click" "a.button" (fn [e] (print "clicked button"))))

(defn teardown []
   (.off ($ "div#app") "click" "a.button"))

;; define a :on-jsload hook in your :cljsbuild options
(defn fig-reload-hook []
      (teardown)
      (setup))

Now you can edit the code in the setup and teardown functions and see the resulting changes in your application.

In a way you can think of the previous definitions of setup-stuff as functions that have local state of sorts. They are altering and storing callbacks in the DOM directly and this is why it is so problematic.

This is one of the reasons React is so damn brilliant. You never end up storing things directly in the DOM. State is mediated and managed for you. You just describe what should be there and then React takes care of making the appropriate changes. For this reason React is a prime candidate for writing reloadable code. React components already have a lifecycle protocol that embeds setup and teardown in each component and invokes them when neccessary.

It is worth repeating that React components don't have local state, it just looks like they do. You have to ask for the local state and React in turn looks this state up in a larger state context and returns it, very similar to a State Monad.

Reloadable code is easy to write if we are very conscious and careful about the storage of state, state transitions and side effects. Since a great deal of programming complexity stems from complex interactions (side effecting events) between things that have local state, it is my belief that reloadable code is often simply better code.

License

Copyright © 2018 Bruce Hauman

Distributed under the Eclipse Public License either version 1.0 or any later version.

lein-figwheel's People

Contributors

aiba avatar ajchemist avatar ajpierce avatar alexsugak avatar arichiardi avatar bensu avatar bhb avatar boogie666 avatar burma-shave avatar cichli avatar cloojure avatar danielcompton avatar darwin avatar deraen avatar featheredtoast avatar honzabrecka avatar ivan avatar mfikes avatar milt avatar neverov avatar o-i avatar otijhuis avatar peterschwarz avatar piranha avatar plexus avatar severeoverfl0w avatar tonsky avatar verma avatar wavejumper avatar yatesco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lein-figwheel's Issues

Exits with "No such file or directory"

Sometimes while I'm working the lein figwheel process will just exit with:

java.io.FileNotFoundException: /Users/mossity/code/clojure/verse-invaders/src/verse_invaders/core.cljs (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at clojure.java.io$fn__8638.invoke(io.clj:233)
    at clojure.java.io$fn__8577$G__8542__8584.invoke(io.clj:73)
    at clojure.java.io$fn__8650.invoke(io.clj:262)
    at clojure.java.io$fn__8577$G__8542__8584.invoke(io.clj:73)
    at clojure.java.io$fn__8612.invoke(io.clj:169)
    at clojure.java.io$fn__8551$G__8546__8558.invoke(io.clj:73)
    at clojure.java.io$reader.doInvoke(io.clj:106)
    at clojure.lang.RestFn.invoke(RestFn.java:410)
    at figwheel.core$get_ns_from_source_file_path.invoke(core.clj:82)
    at clojure.core$mapv$fn__6258.invoke(core.clj:6241)
    at clojure.lang.ArrayChunk.reduce(ArrayChunk.java:58)
    at clojure.core.protocols$fn__6041.invoke(protocols.clj:98)
    at clojure.core.protocols$fn__6005$G__6000__6014.invoke(protocols.clj:19)
    at clojure.core.protocols$seq_reduce.invoke(protocols.clj:31)
    at clojure.core.protocols$fn__6024.invoke(protocols.clj:60)
    at clojure.core.protocols$fn__5979$G__5974__5992.invoke(protocols.clj:13)
    at clojure.core$reduce.invoke(core.clj:6177)
    at clojure.core$mapv.invoke(core.clj:6241)
    at figwheel.core$check_for_changes.invoke(core.clj:172)
    at user$eval7653.invoke(form-init1750915115218861197.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6619)
    at clojure.lang.Compiler.eval(Compiler.java:6609)
    at clojure.lang.Compiler.load(Compiler.java:7064)
    at clojure.lang.Compiler.loadFile(Compiler.java:7020)
    at clojure.main$load_script.invoke(main.clj:294)
    at clojure.main$init_opt.invoke(main.clj:299)
    at clojure.main$initialize.invoke(main.clj:327)
    at clojure.main$null_opt.invoke(main.clj:362)
    at clojure.main$main.doInvoke(main.clj:440)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:419)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.main.main(main.java:37)
Subprocess failed

I think it corresponds to me saving the file, but it's not happening every time. Do you have any ideas what this might be?

CSS isn't reloading properly

I've got the following in the project.clj file that I'm trying to use figwheel with.

:figwheel {:css-dirs ["resources/assets/css"]}

This is what I've got in the cljs app:

(fw/watch-and-reload
  :websocket-url "ws://localhost:3449/figwheel-ws"
  :jsload-callback (fn [] (do
                            (update-ui!))))

Whenever CSS files are changed, it tries to reload the CSS with the following URL:

<link rel="stylesheet" media="" href="//localhost:3449resources/assets/css/app.css?rel=1414087417074">

The issue appears to be that there's not a / after 3449 in the URL.

Figwheel is caching project.clj somehow

When I change my project.clj, figwheel does not pick up the changes. It is caching the old project.clj somewhere! The old source paths are reported.

This applies to 0.2.0-SNAPSHOT. If I go back to 0.1.7-SNAPSHOT, the problem goes away and figwheel sees my changes.

Include listening to HTML file changes

Would be nice when I make changes to my .html (or even .css) files that this would be detected and reloaded. Although Im not sure if this would mean clobbering any existing state?

Doc: Incompatible with clojurescript r2173

When starting lein figwheel:

Compiling ClojureScript.
Figwheel: Starting server at http://localhost:3449
Figwheel: Serving files from 'resources/public'
clojure.lang.ArityException: Wrong number of args (1) passed to: env$default-compiler-env
    at clojure.lang.AFn.throwArity(AFn.java:437)
    at clojure.lang.AFn.invoke(AFn.java:39)
    at user$eval7560$iter__7563__7567$fn__7568.invoke(form-init5205944675201977042.clj:1)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:60)
    at clojure.lang.RT.seq(RT.java:484)
    at clojure.lang.RT.countFrom(RT.java:537)
    at clojure.lang.RT.count(RT.java:530)
    at user$eval7560.invoke(form-init5205944675201977042.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6619)
    at clojure.lang.Compiler.eval(Compiler.java:6609)
    at clojure.lang.Compiler.load(Compiler.java:7064)
    at clojure.lang.Compiler.loadFile(Compiler.java:7020)
    at clojure.main$load_script.invoke(main.clj:294)
    at clojure.main$init_opt.invoke(main.clj:299)
    at clojure.main$initialize.invoke(main.clj:327)
    at clojure.main$null_opt.invoke(main.clj:362)
    at clojure.main$main.doInvoke(main.clj:440)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:419)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.main.main(main.java:37)
Subprocess failed

Which makes sense when you look at the defn at https://github.com/clojure/clojurescript/blob/r2173/src/clj/cljs/env.clj#L37

defonce already in cljs core

It looks like defonce has been in cljs core since 0.0-2156.

Here is a verifying sample core.cljs after creating the project with lein new figwheel hellofig:

(ns hellofig.core
  (:require
    [figwheel.client :as fw]))

(enable-console-print!)

;; define your app data so that it doesn't get over written on reload
;; (fw/defonce app-data (atom {}))

(defonce my-name "Shaun")
(println "My name is" my-name)

(fw/watch-and-reload
 :jsload-callback (fn []
                    ;; (stop-and-start-my app)
                    ))

After loading the page, changing my-name to "Bob" didn't do a redef.

Could not locate figwheel/client__init.class or figwheel/client.clj on classpath

After upgrading figwheel/lein-figwheel from 0.1.6-SNAPSHOT to 0.1.7-SNAPSHOT, I get the following exception in my console when figwheel attempts to compile my clojurescript changes.

Compiling "resources/public/js/app.js" failed.
clojure.lang.ExceptionInfo: Could not locate figwheel/client__init.class or figwheel/client.clj on classpath:  at line 1
                core.clj:4403 clojure.core/ex-info
             analyzer.clj:287 cljs.analyzer/error
            analyzer.clj:1488 cljs.analyzer/analyze-seq
            analyzer.clj:1577 cljs.analyzer/analyze[fn]
            analyzer.clj:1570 cljs.analyzer/analyze
            analyzer.clj:1639 cljs.analyzer/parse-ns[fn]
            analyzer.clj:1639 cljs.analyzer/parse-ns[fn]
            analyzer.clj:1634 cljs.analyzer/parse-ns
            analyzer.clj:1629 cljs.analyzer/parse-ns
            compiler.clj:1006 cljs.compiler/compile-root
              closure.clj:353 cljs.closure/compile-dir
              closure.clj:393 cljs.closure/eval3045[fn]
              closure.clj:303 cljs.closure/eval2980[fn]
              closure.clj:407 cljs.closure/eval3032[fn]
              closure.clj:303 cljs.closure/eval2980[fn]
              compiler.clj:44 cljsbuild.compiler.SourcePaths/fn
                core.clj:2559 clojure.core/map[fn]
              LazySeq.java:40 clojure.lang.LazySeq.sval
              LazySeq.java:49 clojure.lang.LazySeq.seq
                 Cons.java:39 clojure.lang.Cons.next
                 RT.java:1654 clojure.lang.RT.boundedLength
              RestFn.java:130 clojure.lang.RestFn.applyTo
                 core.clj:624 clojure.core/apply
                core.clj:2586 clojure.core/mapcat
              RestFn.java:423 clojure.lang.RestFn.invoke
              compiler.clj:44 cljsbuild.compiler/cljsbuild.compiler.SourcePaths
              closure.clj:977 cljs.closure/build
              closure.clj:942 cljs.closure/build
              compiler.clj:58 cljsbuild.compiler/compile-cljs[fn]
              compiler.clj:57 cljsbuild.compiler/compile-cljs
             compiler.clj:159 cljsbuild.compiler/run-compiler
form-init4549935726379961433.clj:1 user/eval12317[fn]
form-init4549935726379961433.clj:1 user/eval12317[fn]
form-init4549935726379961433.clj:1 user/eval12317
           Compiler.java:6703 clojure.lang.Compiler.eval
           Compiler.java:6693 clojure.lang.Compiler.eval
           Compiler.java:7130 clojure.lang.Compiler.load
           Compiler.java:7086 clojure.lang.Compiler.loadFile
                 main.clj:274 clojure.main/load-script
                 main.clj:279 clojure.main/init-opt
                 main.clj:307 clojure.main/initialize
                 main.clj:342 clojure.main/null-opt
                 main.clj:420 clojure.main/main
              RestFn.java:421 clojure.lang.RestFn.invoke
                 Var.java:383 clojure.lang.Var.invoke
                 AFn.java:156 clojure.lang.AFn.applyToHelper
                 Var.java:700 clojure.lang.Var.applyTo
                 main.java:37 clojure.main.main
Caused by: java.io.FileNotFoundException: Could not locate figwheel/client__init.class or figwheel/client.clj on classpath:
                  RT.java:443 clojure.lang.RT.load
                  RT.java:411 clojure.lang.RT.load
                core.clj:5641 clojure.core/load[fn]
                core.clj:5640 clojure.core/load
              RestFn.java:408 clojure.lang.RestFn.invoke
                core.clj:5446 clojure.core/load-one
                core.clj:5486 clojure.core/load-lib[fn]
                core.clj:5485 clojure.core/load-lib
              RestFn.java:142 clojure.lang.RestFn.applyTo
                 core.clj:626 clojure.core/apply
                core.clj:5524 clojure.core/load-libs
              RestFn.java:137 clojure.lang.RestFn.applyTo
                 core.clj:626 clojure.core/apply
                core.clj:5607 clojure.core/require
              RestFn.java:408 clojure.lang.RestFn.invoke
            analyzer.clj:1190 cljs.analyzer/eval1481[fn]
             MultiFn.java:249 clojure.lang.MultiFn.invoke
            analyzer.clj:1490 cljs.analyzer/analyze-seq

lein figwheel creates xxxxx-init.clj files

I noticed lot of fancy named init.clj files (eg. 3d476a7fdfd1f84c15886ef54ec96ce3f8f9966f-init.clj) created in project directory during figwheel work. is that normal? :)

hyphen-warn complains about "src-cljs"

I see

Please use underscores instead of hyphens in your directory and file names
The following source paths have hyphens in them:
("/src-cljs/figsample/core.cljs")
sending changed file: /js/compiled/out/figsample/core.js

but the "src-cljs" directory should be fine because it's my source path root and not part of a package name.

Lein checkouts

Not sure if possible but would be cool if changes to projects in checkouts would be evaluated too and not need a F5.

Doesn't update javascript correctly if :output-dir ends in '/'

Thanks for Figwheel! Little problem though. Example:

(defproject my-proj "0.1.0-SNAPSHOT"
  :cljsbuild {:builds
              [{:compiler {:output-dir "resources/public/js/"}}]})

In the console output, you see things like: sending changed file: /js//goog/deps.js which fails because of the double slashes.

Append timestamps to sourcemap paths

I've been running into this a bit, as I edit my code, new JS gets loaded, but they all point to the same source map path, which gets cached early and then ends up pointing to the wrong locations.

Is this something that could be in scope of figwheel?

Resource path issue

My output dir is under my resources path and yet with snapshot I still receive this error.

In addition why it required to use resoures at all?

xcdoffice git:(master) ✗ lein figwheel
Figwheel Config Error - your build :output-dir is not in a resources directory.
It should match this pattern: (dev-resources|build/client)/public/client
➜ xcdoffice git:(master) ✗ more project.clj
(defproject xcdoffice "0.0.1-SNAPSHOT"
:description "XCD Office System"
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2322"]
[om "0.7.3"]
[kioo "0.4.0"]
[figwheel "0.1.4-SNAPSHOT"]]
:plugins [[lein-cljsbuild "1.0.3"]
[lein-figwheel "0.1.4-SNAPSHOT"]]
:figwheel {:http-server-root "public/client"
:port 3449
:css-dirs ["build/client/css"]}
:source-paths ["src/app"]
:resource-paths ["build/client"]
:cljsbuild {
:builds { :dev {:source-paths ["src/app"]
:resource-paths ["build/client"]
:compiler {
:output-to "build/client/js/appboot.js"
:output-dir "build/client/js/app"
:optimizations :none
:source-map true
:pretty-print true
}}

Allow figwheel to be optional at runtime

Loving figwheel, really loving it. However, because I need to verify the app in IE8 and because of the lack of WebSockets the application won't work.

Unfortunately (this is an om app if that makes any difference?) it stops the whole application from rendering in IE8 so I get a blank white page.

At the moment I have to uncomment out the fw/watch-and-reload in the main.cljs everytime I need to view the app in IE, which is quite painful.

Any ideas or suggestions? Preferably getting this working in IE :), but is there a smoother workflow I can use if I need to develop on IE8 as well as the other working browsers?

Thanks!

run-compiler seems to ignore "java-sources"

There is a single Java file used (it has observable state and transient things that are not easily translated to Clojure), that lives under src/java.

The normal flow: lein, compile, repl, cljsbuild, uberjar, ring server, etc.. works fine. But figwheel does not seem to find it (even if the namespace that uses it is AOTed).

Here is an example:

checking out

$ g clone https://github.com/tolitius/hface
$ cd hface
$ g co figwheel-issue      # checkout to a figwheel-issue branch                                                                                                   

normal things work (including repl, uberjar and friends)

$ lein do compile, cljsbuild once                                                                                           
Compiling ClojureScript.
Compiling "resources/public/js/app.js" from ("src/cljs" "env/dev/cljs")...
Successfully compiled "resources/public/js/app.js" in 13.565 seconds.

figwheel does not seem to be able to compile sources

$ lein figwheel
Figwheel: focusing on build-ids (app)
Compiling ClojureScript.
java.lang.ClassNotFoundException: org.hface.InstanceStatsTask, compiling:(hface/stats.clj:1:1)
    ...
    at figwheel_sidecar.core$resolve_ring_handler.invoke(core.clj:368)
    at figwheel_sidecar.core$start_server.invoke(core.clj:376)
    at user$eval12493.invoke(form-init2945378863219041657.clj:1)
    ...
    at clojure.main.main(main.java:37)
Caused by: java.lang.ClassNotFoundException: org.hface.InstanceStatsTask
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    ...
    at hface.stats$eval12506$loading__4958__auto____12507.invoke(stats.clj:1)
    at hface.stats$eval12506.invoke(stats.clj:1)
    ... 56 more

hface.stats is the namespace that uses that Java class (InstanceStatsTask)


The Java file is in target/classes, all the standard pathing, nothing fancy. Almost feels like figwheel just needs to care for :java-source-paths ["src/java"]

Complains about output-dir from different build

I have some different (cljs)builds that I don't use with figwheel, having their output-dir set outside resources. When I run lein figwheel app, where app has output-dir set under resource/public, figwheel complains about it quits. When I remove my other (non figwheel compatible) build from project.clj, everything works.

error-parsing

are you planning on doing error-parsing for this tool like you did with warnings?

figwheel dependency breaks Light Table

Whenever I add figwheel as a dependency Light Table cannot connect anymore to my project. The only thing I can provide you is this big unhelpful error message:

We couldn't connect.

Looks like there was an issue trying to connect to the project. Here's what we got:
final project:  {:compile-path /Users/frankie/Documents/clojure/hello-world/target/classes, :group hello-world, :license {:name Eclipse Public License, :url http://www.eclipse.org/legal/epl-v10.html}, :global-vars {}, :checkout-deps-shares [:source-paths :test-paths :resource-paths :compile-path #'leiningen.core.classpath/checkout-deps-paths], :repl-options {:nrepl-middleware [lighttable.nrepl.handler/lighttable-ops], :init (clojure.core/swap! lighttable.nrepl.core/my-settings clojure.core/merge {:name hello-world 0.1.0-SNAPSHOT, :project (quote {:compile-path /Users/frankie/Documents/clojure/hello-world/target/classes, :group hello-world, :license {:name Eclipse Public License, :url http://www.eclipse.org/legal/epl-v10.html}, :global-vars {}, :checkout-deps-shares [:source-paths :test-paths :resource-paths :compile-path #'leiningen.core.classpath/checkout-deps-paths], :dependencies ([org.clojure/clojure 1.5.1] [org.clojure/clojurescript 0.0-2173] [figwheel/figwheel 0.1.1] [org.clojure/tools.nrepl 0.2.3 :exclusions ([org.clojure/clojure])] [clojure-complete/clojure-complete 0.2.3 :exclusions ([org.clojure/clojure])]), :plugin-repositories [[central {:snapshots false, :url http://repo1.maven.org/maven2/}] [clojars {:url https://clojars.org/repo/}]], :test-selectors {:default (constantly true)}, :target-path /Users/frankie/Documents/clojure/hello-world/target, :name hello-world, :deploy-repositories [[clojars {:username :gpg, :url https://clojars.org/repo/, :password :gpg}]], :root /Users/frankie/Documents/clojure/hello-world, :offline? false, :source-paths (/Users/frankie/Documents/clojure/hello-world/src), :certificates [clojars.pem], :version 0.1.0-SNAPSHOT, :jar-exclusions [#"^\."], :prep-tasks [javac compile], :url http://example.com/FIXME, :repositories [[central {:snapshots false, :url http://repo1.maven.org/maven2/}] [clojars {:url https://clojars.org/repo/}]], :android {:sdk-path /usr/local/opt/android-sdk}, :resource-paths (/Users/frankie/Documents/clojure/hello-world/dev-resources /Users/frankie/Documents/clojure/hello-world/resources), :uberjar-exclusions [#"(?i)^META-INF/[^/]*\.(SF|RSA|DSA)$"], :jvm-opts [], :eval-in :subprocess, :plugins ([lein-droid/lein-droid 0.2.0]), :native-path /Users/frankie/Documents/clojure/hello-world/target/native, :description FIXME: write description, :uberjar-merge-with {META-INF/plexus/components.xml leiningen.uberjar/components-merger, data_readers.clj leiningen.uberjar/clj-map-merger}, :test-paths (/Users/frankie/Documents/clojure/hello-world/test), :clean-targets [:target-path]})})}, :dependencies ([org.clojure/clojure 1.5.1] [org.clojure/clojurescript 0.0-2173] [figwheel/figwheel 0.1.1] [org.clojure/tools.nrepl 0.2.3 :exclusions ([org.clojure/clojure])] [clojure-complete/clojure-complete 0.2.3 :exclusions ([org.clojure/clojure])] [lein-light-nrepl/lein-light-nrepl 0.0.17] [org.clojure/tools.reader 0.8.3]), :plugin-repositories [[central {:snapshots false, :url http://repo1.maven.org/maven2/}] [clojars {:url https://clojars.org/repo/}]], :test-selectors {:default (constantly true)}, :target-path /Users/frankie/Documents/clojure/hello-world/target, :name hello-world, :deploy-repositories [[clojars {:username :gpg, :url https://clojars.org/repo/, :password :gpg}]], :root /Users/frankie/Documents/clojure/hello-world, :offline? false, :source-paths (/Users/frankie/Documents/clojure/hello-world/src), :certificates [clojars.pem], :version 0.1.0-SNAPSHOT, :jar-exclusions [#"^\."], :prep-tasks [javac compile], :url http://example.com/FIXME, :repositories [[central {:snapshots false, :url http://repo1.maven.org/maven2/}] [clojars {:url https://clojars.org/repo/}]], :android {:sdk-path /usr/local/opt/android-sdk}, :resource-paths (/Users/frankie/Documents/clojure/hello-world/dev-resources /Users/frankie/Documents/clojure/hello-world/resources), :uberjar-exclusions [#"(?i)^META-INF/[^/]*\.(SF|RSA|DSA)$"], :jvm-opts [], :eval-in :subprocess, :plugins ([lein-droid/lein-droid 0.2.0]), :native-path /Users/frankie/Documents/clojure/hello-world/target/native, :description FIXME: write description, :uberjar-merge-with {META-INF/plexus/components.xml leiningen.uberjar/components-merger, data_readers.clj leiningen.uberjar/clj-map-merger}, :test-paths (/Users/frankie/Documents/clojure/hello-world/test), :clean-targets [:target-path]}
Error loading lighttable.nrepl.handler: java.lang.RuntimeException: No such var: fs/*cwd*, compiling:(lighttable/nrepl/core.clj:21:30)
Exception in thread "main" java.lang.ClassNotFoundException: lighttable.nrepl.core, compiling:(/private/var/folders/fd/x2bnc_m15gn6d58ggq3wl3500000gn/T/form-init2007115277012065966.clj:1:1023)
    at clojure.lang.Compiler.analyze(Compiler.java:6380)
    at clojure.lang.Compiler.analyze(Compiler.java:6322)
    at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3624)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6562)
    at clojure.lang.Compiler.analyze(Compiler.java:6361)
    at clojure.lang.Compiler.analyze(Compiler.java:6322)
    at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5708)
    at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5139)
    at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3751)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6558)
    at clojure.lang.Compiler.analyze(Compiler.java:6361)
    at clojure.lang.Compiler.eval(Compiler.java:6616)
    at clojure.lang.Compiler.eval(Compiler.java:6609)
    at clojure.lang.Compiler.eval(Compiler.java:6608)
    at clojure.lang.Compiler.load(Compiler.java:7064)
    at clojure.lang.Compiler.loadFile(Compiler.java:7020)
    at clojure.main$load_script.invoke(main.clj:294)
    at clojure.main$init_opt.invoke(main.clj:299)
    at clojure.main$initialize.invoke(main.clj:327)
    at clojure.main$null_opt.invoke(main.clj:362)
    at clojure.main$main.doInvoke(main.clj:440)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:419)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.main.main(main.java:37)
Caused by: java.lang.ClassNotFoundException: lighttable.nrepl.core
    at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at clojure.lang.DynamicClassLoader.findClass(DynamicClassLoader.java:61)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:340)
    at clojure.lang.RT.classForName(RT.java:2070)
    at clojure.lang.Compiler$HostExpr.maybeClass(Compiler.java:969)
    at clojure.lang.Compiler$HostExpr.access$400(Compiler.java:747)
    at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6766)
    at clojure.lang.Compiler.analyze(Compiler.java:6343)
    ... 25 more
clojure.lang.ExceptionInfo: Subprocess failed {:exit-code 1}
    at clojure.core$ex_info.invoke(core.clj:4327)
    at leiningen.core.eval$fn__3532.invoke(eval.clj:226)
    at clojure.lang.MultiFn.invoke(MultiFn.java:231)
    at leiningen.core.eval$eval_in_project.invoke(eval.clj:326)
    at clojure.lang.AFn.applyToHelper(AFn.java:167)
    at clojure.lang.AFn.applyTo(AFn.java:151)
    at clojure.core$apply.invoke(core.clj:619)
    at leiningen.repl$repl.doInvoke(repl.clj:261)
    at clojure.lang.RestFn.invoke(RestFn.java:425)
    at leiningen.light_nrepl$light.doInvoke(light_nrepl.clj:56)
    at clojure.lang.RestFn.invoke(RestFn.java:423)
    at leiningen.light_nrepl$_main.doInvoke(light_nrepl.clj:68)
    at clojure.lang.RestFn.invoke(RestFn.java:397)
    at clojure.lang.AFn.applyToHelper(AFn.java:159)
    at clojure.lang.RestFn.applyTo(RestFn.java:132)
    at leiningen.light_nrepl.main(Unknown Source)

Create a figwheel-noop version for production

I'd like to be able to compile releases without having to strip figwheel out of my sources beforehand. I think it would be possible to create a separate project to depend on for the release build that contains the same figwheel namespaces and public functions, but which simply does nothing. I'm not completely certain that would work, but I can't think of why it wouldn't.

Configuration for cache busting URL

I ran into a small issue today where the urls figwheel.client/add-cache-buster was generating was resulting in 404s. Our output-dir is /resources/public/out/js and figwheel emits urls of the form /out/js/yadda/yadda which is fine if you're using the vanilla server to work on your application. We use Nginx during development and in order to get figwheel to work for us we need to prefix those urls. Fortunately I was able to hack it:

(when-dev?
 (set! js/window.figwheel.client.add_cache_buster
       (fn [url]
         (str "/our-prefix/" url "?rel=" (.getTime (js/Date.))))))

This is obviously pretty crappy so I'm wondering if there's a solution we can come up with here such that we can get around this in future projects without a hack.

Would you accept a patch that adds another configuration option to fw/watch-and-reload perhaps called :url-handler which allows us to pass a function for this case?

notify browser problem

The figwheel notifies the file according to the name space, seems to me it should notifies the file according to the :output-to configuration

:notify-command output hidden

when upgrading from 1.7->2.0 the output of :notify-command is no longer show in the console when using lein figwheel.

location.host = "" in watch-and-reload

Hey, in trying to get this to work I copied this from the README:

(fw/watch-and-reload
  ;; :websocket-url "ws:localhost:3449/figwheel-ws" default
  :jsload-callback (fn [] (print "reloaded"))) ;; optional callback

Then I removed the commented out stuff, figuring it indicated that this was the default setting (the port matches the default copied into project.clj. After doing that Fighwheel was unable to connect to the socket.

Looking at the source for watch-and-reload I found this being merged into the opts map:

:websocket-url (str "ws:" js/location.host "/figwheel-ws")

When I evaluate location.host in the Chrome repl I get "". Fighweel works if I explicitly send in the ws url, like in the commented out line from the readme.

Doesn't work on IE8 due to use of websockets

Hi,

Unfortunately IE8 doesn't support websockets which this seems to use.

I am sure this is of no great surprise, but I thought I would optimistically raise it, just in case you fancied doing something about it :).

Works fantastically well on Chrome and IE9 though!

figwheel not working

When I clone flappy-bird-demo repository and run lein figwheel there, I get an error:

clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to resolve var: reader/*alias-map* in this context, compiling:(cljs/analyzer.clj:1499:11)

I obviously can provide full one, it's really long though. I found out that this could be old version of tools.reader, but as far as I see I have 0.8.3 required by cljs and nothing else, so everything should be ok. Any ideas?

Default watch-and-reload doesn't work in windows environments

Disclojure: I understand most people use *nix environments to run clojure stuff, but I don't have that liberty, so there.

I followed the tutorial for setting up the "Perfect ClojureScript Development Environment" tutorial from http://astashov.github.io/blog/2014/07/30/perfect-clojurescript-development-environment-with-vim/ , and even though I'll actually be using cursive, the initial figwheel setup is editor-agnostic.

After the setup, the tutorial indicates that changing a CSS line changes it in the browser - but this does not work in Windows environments. The path that it tries to get the css file from is resources\\public\\css\style.css , instead of /css/style.css .

The workaround is the following: in src/figwheel/perfecton_figwheel.cljs , change the (fw/watch-and-reload) line to the following:

(fw/watch-and-reload :websocket-url "ws://localhost:3449/figwheel-ws"
                     :url-rewriter (fn [url]
                                     (-> url
                                         (string/replace #"\\" "/")
                                         (string/replace #"//localhost:3449" "")
                                         (string/replace #"^/" "")
                                         (string/replace #"resources/public" ""))))

Anyway the rare race of windows devs could get a fix? ;)

Can't pass arguments to watch-and-reload

watch-and-reload takes :key value varargs and watch-and-reload-with-opts tries to merge a map with a list of those varargs. Essentially, it's doing something like ((fn [& opts] (merge {} opts)) :a 1). This doesn't work in ClojureScript, though (or Clojure, for that matter.)

To reproduce this, try (fw/watch-and-reload :jsload-callback (fn [])); ClojureScript 2202 will throw an error with is not ISeqable and ClojureScript 2227 will throw something with conj on a map takes map entries or seqables of map entries.

This regressed in 6b313e4

Flappy Bird Demo is broken

Following the README instructions on running the flappy bird demo, I get this exception:

java.lang.RuntimeException: Unable to resolve var: reader/*alias-map* in this context, compiling:(cljs/analyzer.clj:1499:11)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6567)
    at clojure.lang.Compiler.analyze(Compiler.java:6361)
    at clojure.lang.Compiler.analyze(Compiler.java:6322)
    at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3624)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6562)
    at clojure.lang.Compiler.analyze(Compiler.java:6361)
    at clojure.lang.Compiler.analyze(Compiler.java:6322)
    at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3624)

Add a sample ring handler to the example application

Hello,
I haven't been very successful in making my application's routings to work with the figwheel http-kit server.
So, maybe it'll be reasonable to add a simple handler (e.g. json response handler) to the example application to demonstrate
how figwheel behaves with the specified :ring-handler parameter.

Thanks!

support for cljx and configurable everything!

so cljx runs its own auto process, which can dump cljs anywhere. i configure cljsbuild out of its way and then they can coexist. to test i can repl cljx files or i use the browser for this i use a ring/compojure and actually keep all code in hiccup. i would love it it figwheel could reuse all of that existing configuration - right now the ring/compojure part seems to be missing semi hardcoded to its own usage pattern, but i thought i would detail my complete workflow just in case.

in a lein world my main alias currently looks like:

lein pdo cljx auto, cljsbuild auto, ring server-headless 8080
i suppose with figwheel it would be something like
lein pdo cljx auto, figwheel (everything else taken from ring configs)

anyway, great project, looking forward to it growing.

Changing output path

Would it be possible to change the output folder from resources/public/js/compiled/ to something else?

My build places the output inside another browser extension project and I'm not sure if it will work If I manualy copy the generated files to the other project.

Basically I will run the application inside an iframe of a browser extension. And I would love if I could do interactive development of the browser extension like this.

Cross origin requests will work from the iframe.

Figwheel depends on an old version of core.async

Even if I add a dependency in my project on a newer version, it still uses the old one for some reason.

Also, If I add an exclusion to figwheel for core.async in both the dependency and the plugin and then pull in the current version, I can get cljsbuild to use the right version, but then figwheel itself blows up with an odd exception, apparently related to core.cache and/or core.memoize which are depended on by core.async.

Exits with "does not provide a namespace"

Create a new empty .cljs file and lein-figwheel exits with:

Compiling "resources/public/js/compiled/tableview.js" from ["src"]...
Compiling "resources/public/js/compiled/tableview.js" failed.
java.lang.AssertionError: Assert failed: file:/mnt/ExpClj/tableview/src/tableview/date.cljs does not provide a namespace
(first provides)
          closure.clj:265 cljs.closure/javascript-file
          closure.clj:278 cljs.closure/map->javascript-file
          closure.clj:314 cljs.closure/compiled-file
            core.clj:2557 clojure.core/map[fn]
          LazySeq.java:40 clojure.lang.LazySeq.sval
          LazySeq.java:56 clojure.lang.LazySeq.seq
              RT.java:484 clojure.lang.RT.seq
             core.clj:133 clojure.core/seq
             core.clj:685 clojure.core/concat[fn]
          LazySeq.java:40 clojure.lang.LazySeq.sval
          LazySeq.java:49 clojure.lang.LazySeq.seq
             Cons.java:39 clojure.lang.Cons.next
             RT.java:1654 clojure.lang.RT.boundedLength
          RestFn.java:130 clojure.lang.RestFn.applyTo
             core.clj:626 clojure.core/apply
          closure.clj:954 cljs.closure/build
          closure.clj:912 cljs.closure/build
          compiler.clj:58 cljsbuild.compiler/compile-cljs[fn]
          compiler.clj:57 cljsbuild.compiler/compile-cljs
         compiler.clj:159 cljsbuild.compiler/run-compiler
form-init335034820192754353.clj:1 user/eval7701[fn]
form-init335034820192754353.clj:1 user/eval7701[fn]
          LazySeq.java:40 clojure.lang.LazySeq.sval
          LazySeq.java:49 clojure.lang.LazySeq.seq
              RT.java:484 clojure.lang.RT.seq
             core.clj:133 clojure.core/seq
            core.clj:2855 clojure.core/dorun
            core.clj:2871 clojure.core/doall
form-init335034820192754353.clj:1 user/eval7701
       Compiler.java:6703 clojure.lang.Compiler.eval
       Compiler.java:6693 clojure.lang.Compiler.eval
       Compiler.java:7130 clojure.lang.Compiler.load
       Compiler.java:7086 clojure.lang.Compiler.loadFile
             main.clj:274 clojure.main/load-script
             main.clj:279 clojure.main/init-opt
             main.clj:307 clojure.main/initialize
             main.clj:342 clojure.main/null-opt
             main.clj:420 clojure.main/main
          RestFn.java:421 clojure.lang.RestFn.invoke
             Var.java:383 clojure.lang.Var.invoke
             AFn.java:156 clojure.lang.AFn.applyToHelper
             Var.java:700 clojure.lang.Var.applyTo
             main.java:37 clojure.main.main
java.lang.RuntimeException: EOF while reading
        at clojure.lang.Util.runtimeException(Util.java:221)
        at clojure.lang.LispReader.read(LispReader.java:168)
        at clojure.core$read.invoke(core.clj:3477)
        at clojure.core$read.invoke(core.clj:3475)
        at clojure.core$read.invoke(core.clj:3473)
        at figwheel.core$get_ns_from_source_file_path.invoke(core.clj:85)
        at clojure.core$keep$fn__6402.invoke(core.clj:6709)
        at clojure.lang.LazySeq.sval(LazySeq.java:40)
        at clojure.lang.LazySeq.seq(LazySeq.java:49)
        at clojure.lang.RT.seq(RT.java:484)
        at clojure.core$seq.invoke(core.clj:133)
        at clojure.core$set.invoke(core.clj:3782)
        at figwheel.core$check_for_changes.invoke(core.clj:175)
        at user$eval7701.invoke(form-init335034820192754353.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6703)
        at clojure.lang.Compiler.eval(Compiler.java:6693)
        at clojure.lang.Compiler.load(Compiler.java:7130)
        at clojure.lang.Compiler.loadFile(Compiler.java:7086)
        at clojure.main$load_script.invoke(main.clj:274)
        at clojure.main$init_opt.invoke(main.clj:279)
        at clojure.main$initialize.invoke(main.clj:307)
        at clojure.main$null_opt.invoke(main.clj:342)
        at clojure.main$main.doInvoke(main.clj:420)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:383)
        at clojure.lang.AFn.applyToHelper(AFn.java:156)
        at clojure.lang.Var.applyTo(Var.java:700)
        at clojure.main.main(main.java:37)
Subprocess failed

recompile and send dependent files

for example, util.cljs is required in core.cljs. when editing util.cljs, it should recompile and send any other source files which require util.cljs.

Configurable resources directory

Currently, all generated javascript must be served from the hard-coded resources/ directory. Is there anyway to make this configurable? Perhaps use the existing :resource-paths lein configuration option.

I don't have a strong use case for this, I just prefer to keep my generated resources (js) separate from my version controlled resources (images, css, etc...). For example, I have the following lein configuration:

:resource-paths ["src/main/resources" "target/cljsbuild"]
:cljsbuild {:compiler {:output-to "target/cljsbuild/public/js/my.js"
                                :output-dir "target/cljsbuild/public/js/out"}}
:figwheel {:http-server-root "public"}

With this setup I get the following error:

Figwheel Config Error - your build :output-dir is not below the 'resources/public' directory.

I can easily rearrange things to work, but it's not my preferred directory structure. I apologize for my Mavenitis 😷.

allow for ring-routes to be specified in `:figwheel` project options

figwheel is great, thanks for sharing!

Similar to how lein-ring allows you to specify a :handler in the project.clj it would be nice if figwheel also supported this so other routes can be served up in addition to the websocket and resources.

BTW, my immediate use case is to be able to add browser REPL (bREPL) support easily. The nicest ways to add bREPL support (austin or weasel) both require that you dynamically generate a script tag. Often enlive is used to modify the static pages as they get served up to include the script tag that will allow for bREPL clients to connect. Having bREPL support in figwheel proper would be nice as well but allowing ring-routes to be specified would at least enable people to add it themselves.

Another way of allowing people to have more control over how figwheel is ran is to extract the figwheel or run-compiler fns out of the plugin and into core. That way people could start and stop the compilation process in their own apps/REPLs. (I'm assuming that run-compiler could provide a stopping mechanism as well, perhaps with component's Lifecycle.) The run-compiler could return a map that contains the routes that people could then pass on to their ring-server of choice.

Sorry for rambling. If you have any thoughts or plans on allowing bREPL support I'd be interested in hearing them. For now I think allowing ring-routes to be passed is a good stop-gap solution and would be good to have anyways. If you agree I can look into doing a PR for it.

Only works if builds defined as array.

Although builds can be defined (by cljsbuild standards) as maps or arrays. If the builds are defined as maps lein figwheel fails if the builds are defined as maps. e.g.:

:builds {:develop {...}}

With error:

java.lang.NullPointerException
    at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
    at java.util.regex.Matcher.reset(Matcher.java:309)
    at java.util.regex.Matcher.<init>(Matcher.java:229)
    at java.util.regex.Pattern.matcher(Pattern.java:1093)
    at clojure.core$re_matcher.invoke(core.clj:4386)
    at clojure.core$re_matches.invoke(core.clj:4423)
    at leiningen.figwheel$output_dir_in_resources_root_QMARK_.invoke(figwheel.clj:138)
    at leiningen.figwheel$check_for_valid_options.invoke(figwheel.clj:157)
    at leiningen.figwheel$figwheel.doInvoke(figwheel.clj:201)
    at clojure.lang.RestFn.invoke(RestFn.java:410)
    at clojure.lang.Var.invoke(Var.java:415)
    at clojure.lang.AFn.applyToHelper(AFn.java:161)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.core$apply.invoke(core.clj:619)
    at leiningen.core.main$resolve_task$fn__3029.doInvoke(main.clj:189)
    at clojure.lang.RestFn.invoke(RestFn.java:410)
    at clojure.lang.AFn.applyToHelper(AFn.java:161)
    at clojure.lang.RestFn.applyTo(RestFn.java:132)
    at clojure.lang.AFunction$1.doInvoke(AFunction.java:29)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:619)
    at leiningen.core.main$apply_task.invoke(main.clj:230)
    at leiningen.core.main$resolve_and_apply.invoke(main.clj:234)
    at leiningen.core.main$_main$fn__3092.invoke(main.clj:303)
    at leiningen.core.main$_main.doInvoke(main.clj:290)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.lang.Var.invoke(Var.java:415)
    at clojure.lang.AFn.applyToHelper(AFn.java:161)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.core$apply.invoke(core.clj:617)
    at clojure.main$main_opt.invoke(main.clj:335)
    at clojure.main$main.doInvoke(main.clj:440)
    at clojure.lang.RestFn.invoke(RestFn.java:436)
    at clojure.lang.Var.invoke(Var.java:423)
    at clojure.lang.AFn.applyToHelper(AFn.java:167)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.main.main(main.java:37)

Issues when Closure is not generated to js/goog

I had a cljsbuild setup where Closure was being generated to js/compiled/goog. With that setup, I'd get weird javascript errors where half the Google Closure functions (such as goog.define) where not defined. Took me a while to figure out that (If I read it correctly) per
https://github.com/bhauman/lein-figwheel/blob/master/support/src/figwheel/client.cljs#L47

Figwheel requires the Closure JS to be in js/goog and nothing else. I think this should at least be mentioned in the Readme.

Apart from that, great project! And if CSS reloading comes it, it will be pure awesomeness!

NullPointerException when :output-dir not specified

Hey guys, I'm getting this exception when I don't specify :output-dir in :compiler options. Might be better to have a friendly error message?

java.lang.NullPointerException: null
 at java.util.regex.Matcher.getTextLength (Matcher.java:1234)
    java.util.regex.Matcher.reset (Matcher.java:308)
    java.util.regex.Matcher.<init> (Matcher.java:228)
    java.util.regex.Pattern.matcher (Pattern.java:1088)
    clojure.core$re_matcher.invoke (core.clj:4460)
    clojure.core$re_matches.invoke (core.clj:4497)
    leiningen.figwheel$output_dir_in_resources_root_QMARK_.invoke (figwheel.clj:138)
    leiningen.figwheel$check_for_valid_options.invoke (figwheel.clj:164)
    leiningen.figwheel$figwheel.doInvoke (figwheel.clj:208)
(defproject vr "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:optimizations :none
                                   :output-to "resources/public/js/main.js"
                                   }}]}
  :figwheel {
     :http-server-root "public"
     :server-port 3449
  }
  :plugins [[lein-cljsbuild "1.0.3"]
            [lein-figwheel "0.1.5-SNAPSHOT"]]
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [figwheel "0.1.5-SNAPSHOT"]
                 [org.clojure/clojurescript "0.0-2371"]])

Request for a new release on Clojars

To get lein- and figwheel working with a current-ish stack of other things, I needed the current code; although I understand all these things are far from being called "1.0", the current snapshot is a big improvement over the 0.1.3-snapshot in Clojars.

For a quick workaround I grabbed [com.vitalreactor/lein-figwheel "0.1.4-SNAPSHOT"] instead, but it would be nicer to see an official push of the recent progress to Clojars.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.