bhauman / lein-figwheel

Figwheel builds your ClojureScript code and hot loads it into the browser as you are coding!
Eclipse Public License 1.0
2.88k stars 210 forks source link

Figwheel + cljs-repl has weird compilation clashes #593

Open gtrak opened 7 years ago

gtrak commented 7 years ago

Steps to reproduce:

  1. Create a figwheel project from the lein template
  2. Cider-jack-in or lein repl + cider-connect
  3. (fig-start) + (cljs-repl)
  4. M-x cider-load-buffer or C-c C-k

Evidence 1: I get nested build directories and duplicate files. resources/public/js/compiled/out/resources/public/js/compiled/out/ happens with boot as well, and I saw it in the expo-cljs-template

Evidence 2: When doing cider-load-file, old JS state persists until a browser refresh. Running load-file again on a changed buffer will change to the previous (not yet loaded) state, but not sync it to where it should be.

deps: figwheel-sidecar 0.5.12 cider-nrepl 0.14.0 figwheel 0.5.12 com.cemerick.piggieback 0.2.2 clojure 1.8.0 clojurescript 1.9.854

gtrak commented 7 years ago

Actually turning autobuild off with figwheel-sidecar.repl-api doesn't improve the situation.

The first cider-load-file creates the nested resources directory.

gtrak commented 7 years ago

This seems to be a problem with CLJS itself, I'm working on it.

I think it has to do with cljs.repl/load-file -> cljs.util/to-target-file

gtrak commented 7 years ago

I have a proof of concept workaround that fixes the issue locally.

Monkey-patching cljs.closure/compile-file:

(defn compile-file
  "Compile a single cljs file. If no output-file is specified, returns
  a string of compiled JavaScript. With an output-file option, the
  compiled JavaScript will written to this location and the function
  returns a JavaScriptFile. In either case the return value satisfies
  IJavaScript."
  [^File file {:keys [output-file] :as opts}]
    (if output-file
      (let [out-file (.toFile (.resolve (.toPath (io/file (util/output-directory opts)))
                                        (.toPath (io/file output-file))))
            ;out-file (io/file (util/output-directory opts) output-file)
            ]
        (compiled-file (comp/compile-file file out-file opts)))
      (let [path (.getPath ^File file)]
        (binding [ana/*cljs-file* path]
          (with-open [rdr (io/reader file)]
            (compile-form-seq (ana/forms-seq* rdr path)))))))

And forcing absolute paths in project.clj works around it.

;;; works around a CLJS repl bug
(def $PWD (System/getenv "PWD"))

(def output-to (str $PWD "/resources/public/js/compiled/my_project.js"))
(def output-dir (str $PWD "/resources/public/js/compiled/out"))
...                :compiler {:main my_project.core
                           :asset-path "js/compiled/out"
                           :output-to ~output-to
                           :output-dir ~output-dir
                           :source-map-timestamp true
                           ;; To console.log CLJS data-structures make sure you enable devtools in Chrome
                           ;; https://github.com/binaryage/cljs-devtools

It seems like cljs.closure shouldn't have a problem supporting absolute paths in principle.

This fixes the weird app state and doubly-nested file state issue. I'm also noticing a cider-load-file restarts the app twice, but that's less annoying. Does the file-watcher watch resources/public/js/compiled?

gtrak commented 7 years ago

I'd also like to point out, that while this is a CLJS problem, figwheel seems to exacerbate it.

I think a potential remedy here is to allow for splitting CLJS repl compiler options from the figwheel compiler options. I think being able to pass a different target directory to the repl compiler may also prevent a conflict.

Alternatively, is it possible to use the same CLJS compiler env for both REPL and watcher-compiler? That might require extra interactions with piggieback, but I think it would save memory and cpu time on the second 'initial' compile.