Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
parindent on figwheel-main
diff --git a/dev/user.clj b/dev/user.clj
index 63a1ebe..7a0b0b4 100644
--- a/dev/user.clj
+++ b/dev/user.clj
@@ -1,23 +1,23 @@
(ns user
(:require [figwheel.server.ring]
- [figwheel.main.schema.config]
- [figwheel.main.api :as api]
- [clojure.string :as string]))
+ [figwheel.main.schema.config]
+ [figwheel.main.api :as api]
+ [clojure.string :as string]))
(defn strip-leading-whitspace [s]
(string/join "\n"
- (map
- #(string/replace % #"^\s\s(.*)" "$1")
- (string/split-lines s))))
+ (map
+ #(string/replace % #"^\s\s(.*)" "$1")
+ (string/split-lines s))))
(def doc-string #(-> % resolve meta :doc strip-leading-whitspace))
(def args #(-> % resolve meta :arglists))
(defn sym->markdown-doc [sym]
(str
- "## `" sym "`\n\n"
- "Args: `"(pr-str (args sym)) "`\n\n"
- (doc-string sym) "\n\n"))
+ "## `" sym "`\n\n"
+ "Args: `"(pr-str (args sym)) "`\n\n"
+ (doc-string sym) "\n\n"))
(defn api-docs []
(let [syms ['figwheel.main.api/start
diff --git a/docs-cljs/figwheel_main_docs/main.cljs b/docs-cljs/figwheel_main_docs/main.cljs
index 31ef847..fbcec5a 100644
--- a/docs-cljs/figwheel_main_docs/main.cljs
+++ b/docs-cljs/figwheel_main_docs/main.cljs
@@ -1,9 +1,9 @@
(ns figwheel-main-docs.main
(:require [goog.dom :as dom]
- [goog.dom.classlist :as cl]
- [goog.string :as gstring]
- [goog.fx.css3 :as fxcss3]
- [goog.style :as style]))
+ [goog.dom.classlist :as cl]
+ [goog.string :as gstring]
+ [goog.fx.css3 :as fxcss3]
+ [goog.style :as style]))
(defn itemable->coll [itemable]
(map #(.item itemable %) (range (.-length itemable))))
@@ -105,7 +105,7 @@
#_(insert-options-toc)
#_(log (create-toc (->> (get-main-headers)
- (map extract-heading)
+ (map extract-heading)
- (map create-toc-link))))
+ (map create-toc-link))))
diff --git a/src/figwheel/main.cljc b/src/figwheel/main.cljc
index 36e0426..2a854d4 100644
--- a/src/figwheel/main.cljc
+++ b/src/figwheel/main.cljc
@@ -1,32 +1,32 @@
(ns figwheel.main
#?(:clj
- (:require
- [cljs.analyzer :as ana]
- [cljs.analyzer.api :as ana-api]
- [cljs.build.api :as bapi]
- [cljs.cli :as cli]
- [cljs.env]
- [cljs.main :as cm]
- [cljs.repl]
- [cljs.repl.figwheel]
- [cljs.util]
- [clojure.java.io :as io]
- [clojure.pprint :refer [pprint]]
- [clojure.string :as string]
- [clojure.edn :as edn]
- [clojure.tools.reader.edn :as redn]
- [clojure.tools.reader.reader-types :as rtypes]
- [figwheel.core :as fw-core]
- [figwheel.main.ansi-party :as ansip]
- [figwheel.main.logging :as log]
- [figwheel.main.util :as fw-util]
- [figwheel.main.watching :as fww]
- [figwheel.main.helper :as helper]
- [figwheel.main.npm :as npm]
- [figwheel.main.async-result :as async-result]
- [figwheel.main.testing :as testing]
- [figwheel.repl :as fw-repl]
- [figwheel.tools.exceptions :as fig-ex]))
+ (:require
+ [cljs.analyzer :as ana]
+ [cljs.analyzer.api :as ana-api]
+ [cljs.build.api :as bapi]
+ [cljs.cli :as cli]
+ [cljs.env]
+ [cljs.main :as cm]
+ [cljs.repl]
+ [cljs.repl.figwheel]
+ [cljs.util]
+ [clojure.java.io :as io]
+ [clojure.pprint :refer [pprint]]
+ [clojure.string :as string]
+ [clojure.edn :as edn]
+ [clojure.tools.reader.edn :as redn]
+ [clojure.tools.reader.reader-types :as rtypes]
+ [figwheel.core :as fw-core]
+ [figwheel.main.ansi-party :as ansip]
+ [figwheel.main.logging :as log]
+ [figwheel.main.util :as fw-util]
+ [figwheel.main.watching :as fww]
+ [figwheel.main.helper :as helper]
+ [figwheel.main.npm :as npm]
+ [figwheel.main.async-result :as async-result]
+ [figwheel.main.testing :as testing]
+ [figwheel.repl :as fw-repl]
+ [figwheel.tools.exceptions :as fig-ex]))
#?(:clj
(:import
[java.io StringReader]
@@ -40,199 +40,199 @@
#?(:clj
(do
-(def ^:dynamic *base-config*)
-(def ^:dynamic *config*)
-
-(def default-target-dir "target")
-
-(defonce process-unique (subs (str (java.util.UUID/randomUUID)) 0 6))
-
-(defn- time-elapsed [started-at]
- (let [elapsed-us (- (System/currentTimeMillis) started-at)]
- (with-precision 2
- (str (/ (double elapsed-us) 1000) " seconds"))))
-
-(defn- wrap-with-build-logging [build-fn]
- (fn [id? & args]
- (let [started-at (System/currentTimeMillis)
- {:keys [output-to output-dir]} (second args)]
- ;; print start message
- (log/info (str "Compiling build"
- (when id? (str " " id?))
- " to \""
- (or output-to output-dir)
- "\""))
- (try
- (let [warnings (volatile! [])
- out *out*
- warning-fn (fn [warning-type env extra]
- (when (get cljs.analyzer/*cljs-warnings* warning-type)
- (let [warn {:warning-type warning-type
- :env env
- :extra extra
- :path ana/*cljs-file*}]
- (binding [*out* out]
- (if (<= (count @warnings) 2)
- (log/cljs-syntax-warning warn)
- (binding [log/*syntax-error-style* :concise]
- (log/cljs-syntax-warning warn))))
- (vswap! warnings conj warn))))]
- (binding [cljs.analyzer/*cljs-warning-handlers*
- (conj (remove #{cljs.analyzer/default-warning-handler}
- cljs.analyzer/*cljs-warning-handlers*)
- warning-fn)]
- (apply build-fn args)))
- (log/succeed (str "Successfully compiled build"
- (when id? (str " " id?))
- " to \""
- (or output-to output-dir)
- "\" in " (time-elapsed started-at) "."))
- (catch Throwable e
- (log/failure (str
- "Failed to compile build" (when id? (str " " id?))
- " in " (time-elapsed started-at) "."))
- (log/syntax-exception e)
- (throw e))))))
-
-(declare resolve-fn-var)
-
-(defn run-hooks [hooks & args]
- (when (not-empty hooks)
- (doseq [h hooks]
- (apply h args))))
-
-(defn- wrap-with-build-hooks [build-fn]
- (fn [& args]
- (run-hooks (::pre-build-hooks *config*) *config*)
- (apply build-fn args)
- (run-hooks (::post-build-hooks *config*) *config*)))
-
-(def build-cljs
- (-> bapi/build wrap-with-build-logging wrap-with-build-hooks))
-
-(def fig-core-build
- (-> figwheel.core/build wrap-with-build-logging wrap-with-build-hooks))
-
-;; TODO the word config is soo abused in this namespace that it's hard to
-;; know what and argument is supposed to be
-(defn config->reload-config [config]
- (select-keys config [:reload-clj-files :wait-time-ms :hawk-options]))
-
-(defn watch-build [id paths inputs opts cenv & [reload-config]]
- (when-let [inputs (not-empty (if (coll? inputs) inputs [inputs]))]
- (let [build-inputs (if (coll? inputs) (apply bapi/inputs inputs) inputs)
- ;; the build-fn needs to be passed in before here?
- build-fn (if (some #{'figwheel.core} (:preloads opts))
- #(fig-core-build id build-inputs opts cenv %)
- (fn [files] (build-cljs id build-inputs opts cenv)))]
- (log/info "Watching paths:" (pr-str paths) "to compile build -" id)
- (log/debug "Build Inputs:" (pr-str inputs))
- (binding [fww/*hawk-options* (:hawk-options reload-config nil)]
- (fww/add-watch!
- [::autobuild id]
- (merge
- {::watch-info (merge
- (:extra-info reload-config)
- {:id id
- :paths paths
- :inputs inputs
- :options opts
- :compiler-env cenv
- :reload-config reload-config})}
- {:paths paths
- :filter (fww/suffix-filter #{"cljc" "cljs" "js" "clj"})
- :handler (fww/throttle
- (:wait-time-ms reload-config 50)
- (bound-fn [evts]
- (binding [cljs.env/*compiler* cenv]
- (let [files (mapv (comp #(.getCanonicalPath %) :file) evts)]
- (try
- (when-let [clj-files
- (->> evts
- (filter
- (partial
- (fww/suffix-filter
- (set
- (cond
- (coll? (:reload-clj-files reload-config))
- (mapv name (:reload-clj-files reload-config))
- (false? (:reload-clj-files reload-config)) []
- :else ["clj" "cljc"]))) nil))
- (mapv (comp #(.getCanonicalPath %) :file))
- not-empty)]
- (log/debug "Reloading clj files: " (pr-str (map str clj-files)))
- (try
- (figwheel.core/reload-clj-files clj-files)
- (catch Throwable t
- (if (-> t ex-data :figwheel.core/internal)
- (log/error (.getMessage t) t)
- (do
- (log/syntax-exception t)
- (figwheel.core/notify-on-exception cenv t {})))
- ;; skip cljs reloading in this case
- (throw t))))
- (log/debug "Detected changed cljs files: " (pr-str (map str files)))
- (build-fn files)
- (catch Throwable t
- (log/error t)
- (log/debug (with-out-str (clojure.pprint/pprint (Throwable->map t))))
- false))))))}))))))
-
-
-(declare read-edn-file)
-
-(defn get-edn-file-key
- ([edn-file key] (get-edn-file-key edn-file key nil))
- ([edn-file key default]
- (try (get (read-string (slurp edn-file)) key default)
- (catch Throwable t default))))
-
-(def validate-config!*
- (when (try
- (require 'clojure.spec.alpha)
- (require 'expound.alpha)
- (require 'expound.ansi)
- (require 'figwheel.main.schema.config)
- (require 'figwheel.main.schema.cljs-options)
- (require 'figwheel.main.schema.cli)
- true
- (catch Throwable t false))
- (resolve 'figwheel.main.schema.core/validate-config!)))
-
-(defn validate-config! [spec edn fail-msg & [succ-msg]]
- (when (and validate-config!*
- (not
- (false?
- (:validate-config
- edn
- (get-edn-file-key "figwheel-main.edn" :validate-config)))))
- (expound.ansi/with-color-when (:ansi-color-output edn true)
- (validate-config!* spec edn fail-msg))
- (when succ-msg
- (log/succeed succ-msg))))
-
-(def validate-cli!*
- (when validate-config!*
- (resolve 'figwheel.main.schema.cli/validate-cli!)))
-
-(defn validate-cli! [cli-args & [succ-msg]]
- (when (and validate-cli!*
- (get-edn-file-key "figwheel-main.edn" :validate-cli true))
- (expound.ansi/with-color-when
- (get-edn-file-key "figwheel-main.edn" :ansi-color-output true)
- (validate-cli!* cli-args "Error in command line args"))
- (when succ-msg
- (log/succeed succ-msg))))
-
-;; ----------------------------------------------------------------------------
-;; Additional cli options
-;; ----------------------------------------------------------------------------
-
-;; Help
-
-
-(def help-template
- "Usage: clojure -m figwheel.main [init-opt*] [main-opt] [arg*]
+ (def ^:dynamic *base-config*)
+ (def ^:dynamic *config*)
+
+ (def default-target-dir "target")
+
+ (defonce process-unique (subs (str (java.util.UUID/randomUUID)) 0 6))
+
+ (defn- time-elapsed [started-at]
+ (let [elapsed-us (- (System/currentTimeMillis) started-at)]
+ (with-precision 2
+ (str (/ (double elapsed-us) 1000) " seconds"))))
+
+ (defn- wrap-with-build-logging [build-fn]
+ (fn [id? & args]
+ (let [started-at (System/currentTimeMillis)
+ {:keys [output-to output-dir]} (second args)]
+ ;; print start message
+ (log/info (str "Compiling build"
+ (when id? (str " " id?))
+ " to \""
+ (or output-to output-dir)
+ "\""))
+ (try
+ (let [warnings (volatile! [])
+ out *out*
+ warning-fn (fn [warning-type env extra]
+ (when (get cljs.analyzer/*cljs-warnings* warning-type)
+ (let [warn {:warning-type warning-type
+ :env env
+ :extra extra
+ :path ana/*cljs-file*}]
+ (binding [*out* out]
+ (if (<= (count @warnings) 2)
+ (log/cljs-syntax-warning warn)
+ (binding [log/*syntax-error-style* :concise]
+ (log/cljs-syntax-warning warn))))
+ (vswap! warnings conj warn))))]
+ (binding [cljs.analyzer/*cljs-warning-handlers*
+ (conj (remove #{cljs.analyzer/default-warning-handler}
+ cljs.analyzer/*cljs-warning-handlers*)
+ warning-fn)]
+ (apply build-fn args)))
+ (log/succeed (str "Successfully compiled build"
+ (when id? (str " " id?))
+ " to \""
+ (or output-to output-dir)
+ "\" in " (time-elapsed started-at) "."))
+ (catch Throwable e
+ (log/failure (str
+ "Failed to compile build" (when id? (str " " id?))
+ " in " (time-elapsed started-at) "."))
+ (log/syntax-exception e)
+ (throw e))))))
+
+ (declare resolve-fn-var)
+
+ (defn run-hooks [hooks & args]
+ (when (not-empty hooks)
+ (doseq [h hooks]
+ (apply h args))))
+
+ (defn- wrap-with-build-hooks [build-fn]
+ (fn [& args]
+ (run-hooks (::pre-build-hooks *config*) *config*)
+ (apply build-fn args)
+ (run-hooks (::post-build-hooks *config*) *config*)))
+
+ (def build-cljs
+ (-> bapi/build wrap-with-build-logging wrap-with-build-hooks))
+
+ (def fig-core-build
+ (-> figwheel.core/build wrap-with-build-logging wrap-with-build-hooks))
+
+ ;; TODO the word config is soo abused in this namespace that it's hard to
+ ;; know what and argument is supposed to be
+ (defn config->reload-config [config]
+ (select-keys config [:reload-clj-files :wait-time-ms :hawk-options]))
+
+ (defn watch-build [id paths inputs opts cenv & [reload-config]]
+ (when-let [inputs (not-empty (if (coll? inputs) inputs [inputs]))]
+ (let [build-inputs (if (coll? inputs) (apply bapi/inputs inputs) inputs)
+ ;; the build-fn needs to be passed in before here?
+ build-fn (if (some #{'figwheel.core} (:preloads opts))
+ #(fig-core-build id build-inputs opts cenv %)
+ (fn [files] (build-cljs id build-inputs opts cenv)))]
+ (log/info "Watching paths:" (pr-str paths) "to compile build -" id)
+ (log/debug "Build Inputs:" (pr-str inputs))
+ (binding [fww/*hawk-options* (:hawk-options reload-config nil)]
+ (fww/add-watch!
+ [::autobuild id]
+ (merge
+ {::watch-info (merge
+ (:extra-info reload-config)
+ {:id id
+ :paths paths
+ :inputs inputs
+ :options opts
+ :compiler-env cenv
+ :reload-config reload-config})}
+ {:paths paths
+ :filter (fww/suffix-filter #{"cljc" "cljs" "js" "clj"})
+ :handler (fww/throttle
+ (:wait-time-ms reload-config 50)
+ (bound-fn [evts]
+ (binding [cljs.env/*compiler* cenv]
+ (let [files (mapv (comp #(.getCanonicalPath %) :file) evts)]
+ (try
+ (when-let [clj-files
+ (->> evts
+ (filter
+ (partial
+ (fww/suffix-filter
+ (set
+ (cond
+ (coll? (:reload-clj-files reload-config))
+ (mapv name (:reload-clj-files reload-config))
+ (false? (:reload-clj-files reload-config)) []
+ :else ["clj" "cljc"]))) nil))
+ (mapv (comp #(.getCanonicalPath %) :file))
+ not-empty)]
+ (log/debug "Reloading clj files: " (pr-str (map str clj-files)))
+ (try
+ (figwheel.core/reload-clj-files clj-files)
+ (catch Throwable t
+ (if (-> t ex-data :figwheel.core/internal)
+ (log/error (.getMessage t) t)
+ (do
+ (log/syntax-exception t)
+ (figwheel.core/notify-on-exception cenv t {})))
+ ;; skip cljs reloading in this case
+ (throw t))))
+ (log/debug "Detected changed cljs files: " (pr-str (map str files)))
+ (build-fn files)
+ (catch Throwable t
+ (log/error t)
+ (log/debug (with-out-str (clojure.pprint/pprint (Throwable->map t))))
+ false))))))}))))))
+
+
+ (declare read-edn-file)
+
+ (defn get-edn-file-key
+ ([edn-file key] (get-edn-file-key edn-file key nil))
+ ([edn-file key default]
+ (try (get (read-string (slurp edn-file)) key default)
+ (catch Throwable t default))))
+
+ (def validate-config!*
+ (when (try
+ (require 'clojure.spec.alpha)
+ (require 'expound.alpha)
+ (require 'expound.ansi)
+ (require 'figwheel.main.schema.config)
+ (require 'figwheel.main.schema.cljs-options)
+ (require 'figwheel.main.schema.cli)
+ true
+ (catch Throwable t false))
+ (resolve 'figwheel.main.schema.core/validate-config!)))
+
+ (defn validate-config! [spec edn fail-msg & [succ-msg]]
+ (when (and validate-config!*
+ (not
+ (false?
+ (:validate-config
+ edn
+ (get-edn-file-key "figwheel-main.edn" :validate-config)))))
+ (expound.ansi/with-color-when (:ansi-color-output edn true)
+ (validate-config!* spec edn fail-msg))
+ (when succ-msg
+ (log/succeed succ-msg))))
+
+ (def validate-cli!*
+ (when validate-config!*
+ (resolve 'figwheel.main.schema.cli/validate-cli!)))
+
+ (defn validate-cli! [cli-args & [succ-msg]]
+ (when (and validate-cli!*
+ (get-edn-file-key "figwheel-main.edn" :validate-cli true))
+ (expound.ansi/with-color-when
+ (get-edn-file-key "figwheel-main.edn" :ansi-color-output true)
+ (validate-cli!* cli-args "Error in command line args"))
+ (when succ-msg
+ (log/succeed succ-msg))))
+
+ ;; ----------------------------------------------------------------------------
+ ;; Additional cli options
+ ;; ----------------------------------------------------------------------------
+
+ ;; Help
+
+
+ (def help-template
+ "Usage: clojure -m figwheel.main [init-opt*] [main-opt] [arg*]
Common usage:
clj -m figwheel.main -b dev -r
@@ -296,1211 +296,1211 @@ options afterwards.
Paths may be absolute or relative in the filesystem or relative to
classpath. Classpath-relative paths have prefix of @ or @/")
-(defn adjust-option-docs [commands]
- (-> commands
- (update-in [:groups :cljs.cli/main&compile :pseudos]
- dissoc ["-re" "--repl-env"])
- (assoc-in [:init ["-d" "--output-dir"] :doc]
- "Set the output directory to use")
- (update-in [:init ["-w" "--watch"] :doc] str
- ". This option can be supplied multiple times.")))
-
-(defn help-str [repl-env]
- (format
- help-template
- (#'cljs.cli/options-str
- (adjust-option-docs
- (#'cljs.cli/merged-commands repl-env)))))
-
-(defn help-opt
- [repl-env _ _]
- (println (help-str repl-env)))
-
-;; safer option reading from files which prints out syntax errors
-
-(defn read-edn-file [f]
- (try (redn/read
- (rtypes/source-logging-push-back-reader (io/reader f) 1 f))
- (catch Throwable t
- (log/syntax-exception t)
- (throw
- (ex-info (str "Couldn't read the file:" f)
- {::error true} t)))))
-
-(defn read-edn-string [s & [fail-msg]]
- (try
- (redn/read
- (rtypes/source-logging-push-back-reader (io/reader (.getBytes s)) 1))
- (catch Throwable t
- (let [except-data (fig-ex/add-excerpt (fig-ex/parse-exception t) s)]
- (log/info (ansip/format-str (log/format-ex except-data)))
- (throw (ex-info (str (or fail-msg "Failed to read EDN string: ")
- (.getMessage t))
- {::error true}
- t))))))
-
-(defn read-edn-opts [str]
- (letfn [(read-rsrc [rsrc-str orig-str]
- (if-let [rsrc (io/resource rsrc-str)]
- (read-edn-string (slurp rsrc))
- (cljs.cli/missing-resource orig-str)))]
- (cond
- (string/starts-with? str "@/") (read-rsrc (subs str 2) str)
- (string/starts-with? str "@") (read-rsrc (subs str 1) str)
- :else
- (let [f (io/file str)]
- (if (.isFile f)
- (read-edn-file f)
- (cljs.cli/missing-file str))))))
-
-(defn merge-meta [m m1] (with-meta (merge m m1) (merge (meta m) (meta m1))))
-
-(defn load-edn-opts [str]
- (reduce merge-meta {} (map read-edn-opts (cljs.util/split-paths str))))
-
-(defn fallback-id [edn]
- (let [m (meta edn)]
- (cond
- (and (:id m) (not (string/blank? (str (:id m)))))
- (:id m)
- ;;(:main edn) (munge (str (:main edn)))
- :else
- (str "build-"
- (.getValue (doto (java.util.zip.CRC32.)
- (.update (.getBytes (pr-str (into (sorted-map) edn))))))))))
-
-(defn compile-opts-opt
- [cfg copts]
- (let [copts (string/trim copts)
- edn (if (or (string/starts-with? copts "{")
- (string/starts-with? copts "^"))
- (read-edn-string copts "Error reading EDN from command line flag: -co ")
- (load-edn-opts copts))
- config (meta edn)
- id
- (and edn
- (if (or (string/starts-with? copts "{")
- (string/starts-with? copts "^"))
- (and (map? edn) (fallback-id edn))
- (->>
- (cljs.util/split-paths copts)
- (filter (complement string/blank?))
- (filter #(not (.startsWith % "@")))
- (map io/file)
- (map (comp first #(string/split % #"\.") #(.getName %)))
- (string/join ""))))]
- (log/debug "Validating options passed to --compile-opts")
- (validate-config!
- :figwheel.main.schema.cljs-options/cljs-options
- edn
- (str "Configuration error in options passed to --compile-opts"))
- (cond-> cfg
- edn (update :options merge edn)
- id (update-in [::build :id] #(if-not % id %))
- config (update-in [::build :config] merge config))))
-
-(defn repl-env-opts-opt
- [cfg ropts]
- (let [ropts (string/trim ropts)
- edn (if (string/starts-with? ropts "{")
- (read-edn-string ropts "Error reading EDN from command line flag: --repl-opts ")
- (load-edn-opts ropts))]
- (update cfg :repl-env-options merge edn)))
-
-(defn figwheel-opts-opt
- [cfg ropts]
- (let [ropts (string/trim ropts)
- edn (if (string/starts-with? ropts "{")
- (read-edn-string ropts "Error reading EDN from command line flag: -fw-opts ")
- (load-edn-opts ropts))]
- (validate-config!
- :figwheel.main.schema.config/edn
- edn "Error validating figwheel options EDN provided to -fwo CLI flag")
- (update cfg ::config merge edn)))
-
-(defn print-config-opt [cfg opt]
- (assoc-in cfg [::config :pprint-config] (not (#{"false"} opt))))
-
-(defn- watch-opt
- [cfg path]
- (when-not (.exists (io/file path))
- (if (or (string/starts-with? path "-")
- (string/blank? path))
- (throw
- (ex-info
- (str "Missing watch path")
- {:cljs.main/error :invalid-arg}))
- (throw
- (ex-info
- (str "Watch path \"" path "\" does not exist")
- {:cljs.main/error :invalid-arg}))))
- (update-in cfg [::extra-config :watch-dirs] (fnil conj []) path))
-
-(defn figwheel-opt [cfg bl]
- (assoc-in cfg [::config :figwheel-core] (not= bl "false")))
-
-(defn get-build [bn]
- (let [fname (if (.contains bn (System/getProperty "path.separator"))
- bn
- (str bn ".cljs.edn"))
- build (->> (cljs.util/split-paths bn)
- (map #(str % ".cljs.edn"))
- (string/join (System/getProperty "path.separator"))
- load-edn-opts)]
- (when build
- (when-not (false? (:validate-config (meta build)))
- (when (meta build)
- (log/debug "Validating metadata in build: " fname)
- (validate-config!
+ (defn adjust-option-docs [commands]
+ (-> commands
+ (update-in [:groups :cljs.cli/main&compile :pseudos]
+ dissoc ["-re" "--repl-env"])
+ (assoc-in [:init ["-d" "--output-dir"] :doc]
+ "Set the output directory to use")
+ (update-in [:init ["-w" "--watch"] :doc] str
+ ". This option can be supplied multiple times.")))
+
+ (defn help-str [repl-env]
+ (format
+ help-template
+ (#'cljs.cli/options-str
+ (adjust-option-docs
+ (#'cljs.cli/merged-commands repl-env)))))
+
+ (defn help-opt
+ [repl-env _ _]
+ (println (help-str repl-env)))
+
+ ;; safer option reading from files which prints out syntax errors
+
+ (defn read-edn-file [f]
+ (try (redn/read
+ (rtypes/source-logging-push-back-reader (io/reader f) 1 f))
+ (catch Throwable t
+ (log/syntax-exception t)
+ (throw
+ (ex-info (str "Couldn't read the file:" f)
+ {::error true} t)))))
+
+ (defn read-edn-string [s & [fail-msg]]
+ (try
+ (redn/read
+ (rtypes/source-logging-push-back-reader (io/reader (.getBytes s)) 1))
+ (catch Throwable t
+ (let [except-data (fig-ex/add-excerpt (fig-ex/parse-exception t) s)]
+ (log/info (ansip/format-str (log/format-ex except-data)))
+ (throw (ex-info (str (or fail-msg "Failed to read EDN string: ")
+ (.getMessage t))
+ {::error true}
+ t))))))
+
+ (defn read-edn-opts [str]
+ (letfn [(read-rsrc [rsrc-str orig-str]
+ (if-let [rsrc (io/resource rsrc-str)]
+ (read-edn-string (slurp rsrc))
+ (cljs.cli/missing-resource orig-str)))]
+ (cond
+ (string/starts-with? str "@/") (read-rsrc (subs str 2) str)
+ (string/starts-with? str "@") (read-rsrc (subs str 1) str)
+ :else
+ (let [f (io/file str)]
+ (if (.isFile f)
+ (read-edn-file f)
+ (cljs.cli/missing-file str))))))
+
+ (defn merge-meta [m m1] (with-meta (merge m m1) (merge (meta m) (meta m1))))
+
+ (defn load-edn-opts [str]
+ (reduce merge-meta {} (map read-edn-opts (cljs.util/split-paths str))))
+
+ (defn fallback-id [edn]
+ (let [m (meta edn)]
+ (cond
+ (and (:id m) (not (string/blank? (str (:id m)))))
+ (:id m)
+ ;;(:main edn) (munge (str (:main edn)))
+ :else
+ (str "build-"
+ (.getValue (doto (java.util.zip.CRC32.)
+ (.update (.getBytes (pr-str (into (sorted-map) edn))))))))))
+
+ (defn compile-opts-opt
+ [cfg copts]
+ (let [copts (string/trim copts)
+ edn (if (or (string/starts-with? copts "{")
+ (string/starts-with? copts "^"))
+ (read-edn-string copts "Error reading EDN from command line flag: -co ")
+ (load-edn-opts copts))
+ config (meta edn)
+ id
+ (and edn
+ (if (or (string/starts-with? copts "{")
+ (string/starts-with? copts "^"))
+ (and (map? edn) (fallback-id edn))
+ (->>
+ (cljs.util/split-paths copts)
+ (filter (complement string/blank?))
+ (filter #(not (.startsWith % "@")))
+ (map io/file)
+ (map (comp first #(string/split % #"\.") #(.getName %)))
+ (string/join ""))))]
+ (log/debug "Validating options passed to --compile-opts")
+ (validate-config!
+ :figwheel.main.schema.cljs-options/cljs-options
+ edn
+ (str "Configuration error in options passed to --compile-opts"))
+ (cond-> cfg
+ edn (update :options merge edn)
+ id (update-in [::build :id] #(if-not % id %))
+ config (update-in [::build :config] merge config))))
+
+ (defn repl-env-opts-opt
+ [cfg ropts]
+ (let [ropts (string/trim ropts)
+ edn (if (string/starts-with? ropts "{")
+ (read-edn-string ropts "Error reading EDN from command line flag: --repl-opts ")
+ (load-edn-opts ropts))]
+ (update cfg :repl-env-options merge edn)))
+
+ (defn figwheel-opts-opt
+ [cfg ropts]
+ (let [ropts (string/trim ropts)
+ edn (if (string/starts-with? ropts "{")
+ (read-edn-string ropts "Error reading EDN from command line flag: -fw-opts ")
+ (load-edn-opts ropts))]
+ (validate-config!
:figwheel.main.schema.config/edn
- (meta build)
- (str "Configuration error in build options meta data: " fname)))
- (log/debug "Validating CLJS compile options for build:" fname)
- (validate-config!
- :figwheel.main.schema.cljs-options/cljs-options
- build
- (str "Configuration error in CLJS compile options: " fname))))
- build))
-
-(defn watch-dir-from-ns [main-ns]
- (let [source (fw-util/ns->location main-ns)]
- (when-let [f (:uri source)]
- (when (= "file" (.getScheme (.toURI f)))
- (let [res (fw-util/relativized-path-parts (.getPath f))
- end-parts (fw-util/path-parts (:relative-path source))]
- (when (= end-parts (take-last (count end-parts) res))
- (str (apply io/file (drop-last (count end-parts) res)))))))))
-
-(def default-main-repl-index-body
- (str
- "<p>Welcome to the Figwheel REPL page.</p>"
- "<p>This page is served when you launch <code>figwheel.main</code> without any command line arguments.</p>"
- "<p>This page is currently hosting your REPL and application evaluation environment. "
- "Validate the connection by typing <code>(js/alert&nbsp;\"Hello&nbsp;Figwheel!\")</code> in the REPL.</p>"))
-
-(defn get-build-with-error [bn]
- (when-not (.exists (io/file (str bn ".cljs.edn")))
- (if (or (string/starts-with? bn "-")
- (string/blank? bn))
- (throw
- (ex-info
- (str "Missing build name")
- {:cljs.main/error :invalid-arg}))
- (throw
- (ex-info
- (str "Build " (str bn ".cljs.edn") " does not exist")
- {:cljs.main/error :invalid-arg}))))
- (get-build bn))
-
-(defn build-opt [cfg bn]
- (let [bns (string/split bn #":")
- id (string/join "" bns)
- options (->> bns
- (map get-build-with-error)
- (reduce merge-meta))]
- (-> cfg
- (update :options merge options)
- (assoc ::build (cond-> {:id id}
- (meta options)
- (assoc :config (meta options)))))))
-
-(defn build-once-opt [cfg bn]
- (let [cfg (build-opt cfg bn)]
- (assoc-in cfg [::config ::build-once] true)))
-
-(defn background-build-opt [cfg bn]
- (let [{:keys [options ::build]} (build-opt {} bn)]
- (update cfg ::background-builds
- (fnil conj [])
- (assoc build :options options))))
-
-;; TODO move these down to main action section
-
-(declare default-compile)
-
-(defn build-main-opt [repl-env-fn [_ build-name & args] cfg]
- ;; serve if no other args
- (let [args (if-not (#{"-s" "-r" "--repl" "--serve"} (first args))
- (cons "-s" args)
- args)]
- (default-compile repl-env-fn
- (merge (build-opt cfg build-name)
- {:args args
- ::build-main-opt true}))))
-
-(defn build-once-main-opt [repl-env-fn [_ build-name & args] cfg]
- (default-compile repl-env-fn
- (merge (build-once-opt cfg build-name)
- {:args args})))
-
-(declare default-output-dir default-output-to)
-
-(defn make-temp-dir []
- (let [tempf (java.io.File/createTempFile "figwheel" "repl")]
- (.delete tempf)
- (.mkdirs tempf)
- (.deleteOnExit (io/file tempf))
- (fw-util/add-classpath! (.toURL tempf))
- tempf))
-
-(defn add-temp-dir [cfg]
- (let [temp-dir (make-temp-dir)
- config-with-target (assoc-in cfg [::config :target-dir] temp-dir)
- output-dir (default-output-dir config-with-target)
- output-to (default-output-to config-with-target)]
- (-> cfg
- (assoc-in [:options :output-dir] output-dir)
- (assoc-in [:options :output-to] output-to)
- (assoc-in [:options :asset-path]
- (str "/cljs-out"
- (when-let [id (-> cfg ::build :id)]
- (str "/" id)))))))
-
-(defn pwd-likely-project-root-dir? []
- (or (some #(.isFile (clojure.java.io/file %))
- ["project.clj" "deps.edn" "figwheel-main.edn"])
- (->> (seq (.listFiles (clojure.java.io/file ".")))
- (map #(.getName %))
- (some #(.endsWith % ".cljs.edn")))))
-
-(defn should-add-temp-dir? [cfg]
- (not (pwd-likely-project-root-dir?)))
-
-(defn helper-ring-app [handler html-body output-to & [force-index?]]
- (figwheel.server.ring/default-index-html
- handler
- (figwheel.server.ring/index-html (cond-> {}
- html-body (assoc :body html-body)
- output-to (assoc :output-to output-to)))
- force-index?))
-
-(defn repl-main-opt [repl-env-fn args cfg]
- (let [cfg (if (should-add-temp-dir? cfg)
- (add-temp-dir cfg)
- cfg)
- cfg (if (get-in cfg [::build :id])
- cfg
- (assoc-in cfg [::build :id] "figwheel-default-repl-build"))
- output-to (get-in cfg [:options :output-to]
- (default-output-to cfg))]
- (default-compile
- repl-env-fn
- (-> cfg
- (assoc :args args)
- (update :options (fn [opt] (merge {:main 'figwheel.repl.preload} opt)))
- (assoc-in [:options :aot-cache] true)
- (assoc-in [::config
- :ring-stack-options
- :figwheel.server.ring/dev
- :figwheel.server.ring/system-app-handler]
- #(helper/middleware
- %
- {:header "REPL Host page"
- :body (slurp (io/resource "public/com/bhauman/figwheel/helper/content/repl_welcome.html"))
- :output-to output-to}))
- (assoc-in [::config :mode] :repl)))))
-
-(declare serve update-config)
-
-(defn print-conf [cfg]
- (println "---------------------- Figwheel options ----------------------")
- (pprint (::config cfg))
- (println "---------------------- Compiler options ----------------------")
- (pprint (:options cfg)))
-
-(defn serve-main-opt [repl-env-fn args b-cfg]
- (let [{:keys [repl-env-options repl-options options] :as cfg}
- (-> b-cfg
- (assoc :args args)
- update-config)
- repl-env-options
- (update-in
- repl-env-options
- [:ring-stack-options
- :figwheel.server.ring/dev
- :figwheel.server.ring/system-app-handler]
- (fn [sah]
- (if sah
- sah
- #(helper/serve-only-middleware % {}))))
- {:keys [pprint-config]} (::config cfg)
- repl-env (apply repl-env-fn (mapcat identity repl-env-options))]
- (log/trace "Verbose config:" (with-out-str (pprint cfg)))
- (if pprint-config
- (do
- (log/info ":pprint-config true - printing config:")
- (print-conf cfg))
- (serve {:repl-env repl-env
- :repl-options repl-options
- :join? true}))))
-
-(def figwheel-commands
- {:init {["-w" "--watch"]
- {:group :cljs.cli/compile :fn watch-opt
- :arg "path"
- :doc "Continuously build, only effective with the --compile and --build main options"}
- ["-fwo" "--fw-opts"]
- {:group :cljs.cli/compile :fn figwheel-opts-opt
- :arg "edn"
- :doc (str "Options to configure figwheel.main, can be an EDN string or "
- "system-dependent path-separated list of EDN files / classpath resources. Options "
- "will be merged left to right.")}
- ["-ro" "--repl-opts"]
- {:group ::main&compile :fn repl-env-opts-opt
- :arg "edn"
- :doc (str "Options to configure the repl-env, can be an EDN string or "
- "system-dependent path-separated list of EDN files / classpath resources. Options "
- "will be merged left to right.")}
- ["-co" "--compile-opts"]
- {:group :cljs.cli/main&compile :fn compile-opts-opt
- :arg "edn"
- :doc (str "Options to configure the build, can be an EDN string or "
- "system-dependent path-separated list of EDN files / classpath resources. Options "
- "will be merged left to right. Any meta data will be merged with the figwheel-options.")}
- ;; TODO uncertain about this
- ["-fw" "--figwheel"]
- {:group :cljs.cli/compile :fn figwheel-opt
- :arg "bool"
- :doc (str "Use Figwheel to auto reload and report compile info. "
- "Only takes effect when watching is happening and the "
- "optimizations level is :none or nil."
- "Defaults to true.")}
- ["-bb" "--background-build"]
- {:group :cljs.cli/compile :fn background-build-opt
- :arg "str"
- :doc "The name of a build config to watch and build in the background."}
- ["-pc" "--print-config"]
- {:group :cljs.cli/main&compile :fn print-config-opt
- :doc "Instead of running the command print out the configuration built up by the command. Useful for debugging."}
- }
- :main {["-b" "--build"]
- {:fn build-main-opt
- :arg "string"
- :doc (str "Run a compile process. The supplied build name or a list of build names "
- "(seperated by \":\") refer to "
- "EDN files of compile options "
- "IE. If you use \"dev\" as a build name it will indicate "
- "that a \"dev.cljs.edn\" will be read for "
- "compile options. "
- "Multiple build names will merged left to right along with their metadata. "
- "The --build option will make an "
- "extra attempt to "
- "initialize a figwheel live reloading workflow. "
- "May be followed buy either --repl or --serve. "
- "If --repl follows, "
- "will launch a REPL (along with a server) after the compile completes. "
- "If --serve follows, will only start a web server according to "
- "current configuration after the compile "
- "completes.")}
- ["-bo" "--build-once"]
- {:fn build-once-main-opt
- :arg "string"
- :doc (str "Compile for the build name one time. "
- "Looks for build EDN files just like the --build command. "
- "This will not inject Figwheel or REPL functionality into your build. "
- "It will still inject devtools if you are using :optimizations :none. "
- "If --serve follows, will start a web server according to "
- "current configuration after the compile "
- "completes.")}
- ["-r" "--repl"]
- {:fn repl-main-opt
- :doc "Run a REPL"}
- ["-s" "--serve"]
- {:fn serve-main-opt
- :arg "host:port"
- :doc "Run a server based on the figwheel-main configuration options."}
- ["-h" "--help" "-?"]
- {:fn help-opt
- :doc "Print this help message and exit"}
- }})
-
-;; ----------------------------------------------------------------------------
-;; Config
-;; ----------------------------------------------------------------------------
-
-(defn default-output-dir* [target & [scope]]
- (->> (cond-> [(or target default-target-dir) "public" "cljs-out"]
- scope (conj scope))
- (apply io/file)
- (.getPath)))
-
-(defmulti default-output-dir (fn [{:keys [options]}]
- (get options :target :browser)))
-
-(defmethod default-output-dir :default [{:keys [::config ::build]}]
- (default-output-dir* (:target-dir config) (:id build)))
-
-(defmethod default-output-dir :nodejs [{:keys [::config ::build]}]
- (let [target (:target-dir config default-target-dir)
- scope (:id build)]
- (->> (cond-> [target "node"]
- scope (conj scope))
- (apply io/file)
- (.getPath))))
-
-(defn default-output-to* [target & [scope]]
- (.getPath (io/file (or target default-target-dir) "public" "cljs-out"
- (cond->> "main.js"
- scope (str scope "-")))))
-
-(defmulti default-output-to (fn [{:keys [options]}]
- (get options :target :browser)))
-
-(defmethod default-output-to :default [{:keys [::config ::build]}]
- (default-output-to* (:target-dir config) (:id build)))
-
-(defmethod default-output-to :nodejs [{:keys [::build] :as cfg}]
- (let [scope (:id build)]
- (.getPath (io/file (default-output-dir cfg)
- (cond->> "main.js"
- scope (str scope "-"))))))
-
-(defn extra-config-merge [a' b']
- (merge-with (fn [a b]
- (cond
- (and (map? a) (map? b)) (merge a b)
- (and (sequential? a)
- (sequential? b))
- (distinct (concat a b))
- (nil? b) a
- :else b))
- a' b'))
-
-(defn resolve-fn-var [prefix handler]
- (if (or (nil? handler) (var? handler))
- handler
- (let [prefix (when prefix (str prefix ": "))]
- (when (and handler (nil? (namespace (symbol handler))))
- (throw
- (ex-info
- (format "%sThe var '%s has the wrong form it must be a namespaced symbol" prefix
- (pr-str handler))
- {::error true :handler handler})))
- (let [handler-res (fw-util/require-resolve-handler-or-error handler)]
- (when (and handler (not handler-res))
- (throw (ex-info
- (format "%sWas able to load namespace '%s but unable to resolve the specific var: '%s"
- prefix
- (namespace (symbol handler))
- (str handler))
- {::error true
- :handler handler})))
- (when (map? handler-res)
- (letfn [(error [s]
- (throw (ex-info s {::error true :handler handler})))]
- (condp = (:stage handler-res)
- :bad-namespaced-symbol
- (do (log/syntax-exception (:exception handler-res))
- (error (format "%sThere was an error while trying to resolve '%s"
- prefix
- (pr-str handler))))
- :unable-to-resolve-handler-fn
- (error (format "%sWas able to load namespace '%s but unable to resolve the specific var: '%s"
- prefix
- (namespace (symbol handler))
- (str handler)))
- :unable-to-load-handler-namespace
- (do
- (log/syntax-exception (:exception handler-res))
- (error (format "%sThere was an exception while requiring the namespace '%s while trying to load the var '%s"
- prefix
- (namespace (symbol handler))
- (str handler)))))))
- handler-res))))
-
-(defn resolve-ring-handler [ring-handler]
- (resolve-fn-var "ring-handler" ring-handler))
-
-(defn process-main-config [{:keys [ring-handler] :as main-config}]
- (let [handler (resolve-ring-handler ring-handler)]
- (cond-> main-config
- handler (assoc :ring-handler handler))))
-
-(defn process-figwheel-main-edn [main-edn]
- (when main-edn
- (when-not (false? (:validate-config main-edn))
- (log/info "Validating figwheel-main.edn")
- (validate-config!
- :figwheel.main.schema.config/edn
- main-edn "Configuration error in figwheel-main.edn"
- "figwheel-main.edn is valid \\(ツ)/"))
- (process-main-config main-edn)))
-
-;; use tools reader read-string for better error messages
-#_(redn/read-string)
-(defn fetch-figwheel-main-edn [cfg]
- (when (.isFile (io/file "figwheel-main.edn"))
- (read-edn-file "figwheel-main.edn")))
-
-(defn- config-figwheel-main-edn [cfg]
- (let [config-edn (process-figwheel-main-edn
- (or (::start-figwheel-options cfg)
- (fetch-figwheel-main-edn cfg)))]
- (cond-> cfg
- config-edn (update ::config #(merge config-edn %)))))
-
-(defn- config-merge-current-build-conf [{:keys [::extra-config ::build] :as cfg}]
- (update cfg
- ::config #(extra-config-merge
- (merge-with (fn [a b] (if b b a)) %
- (process-main-config (:config build)))
- extra-config)))
-
-(defn host-port-arg? [arg]
- (and arg (re-matches #"(.*):(\d*)" arg)))
-
-(defn update-server-host-port [config [f address-port & args]]
- (if (and (#{"-s" "--serve"} f) address-port)
- (let [[_ host port] (host-port-arg? address-port)]
- (cond-> config
- (not (string/blank? host)) (assoc-in [:ring-server-options :host] host)
- (not (string/blank? port)) (assoc-in [:ring-server-options :port] (Integer/parseInt port))))
- config))
-
-;; targets options
-(defn- config-main-ns [{:keys [ns options] :as cfg}]
- (let [main-ns (if (and ns (not (#{"-r" "--repl" "-s" "--serve"} ns)))
- (symbol ns)
- (:main options))]
- (cond-> cfg
- main-ns (assoc :ns main-ns) ;; TODO not needed?
- main-ns (assoc-in [:options :main] main-ns))))
-
-(defn warn-that-dir-not-on-classpath [typ dir]
- (let [[n k] (condp = typ
- :source ["Source directory" :source-paths]
- :target ["Target directory" :resource-paths])]
- (log/warn (ansip/format-str
- [:yellow n " "
- (pr-str (str dir))
- " is not on the classpath"]))
- (log/warn "Please fix this by adding" (pr-str (str dir))
- "to your classpath\n"
- "I.E.\n"
- "For Clojure CLI Tools in your deps.edn file:\n"
- " ensure" (pr-str (str dir))
- "is in your :paths key\n\n"
- (when k
- (format
- (str "For Leiningen in your project.clj:\n"
- " add it to the %s key\n")
- (pr-str k))))))
-
-;; takes a string or file representation of a directory
-(defn- add-classpath! [dir]
- (when-not (fw-util/dir-on-classpath? dir)
- (log/warn (ansip/format-str [:yellow
- (format "Attempting to dynamically add %s to classpath!"
- (pr-str (str dir)))]))
- (fw-util/add-classpath! (->> dir
- io/file
- .getCanonicalPath
- io/file
- .toURL))))
-
-(defn- config-main-source-path-on-classpath [{:keys [options] :as cfg}]
- (when-let [main (:ns cfg)]
- (when-not (fw-util/safe-ns->location main)
- (when-let [src-dir (fw-util/find-source-dir-for-cljs-ns main)]
- (when-not (fw-util/dir-on-classpath? src-dir)
- (if (get-in cfg [::config :helpful-classpaths] true)
- (do
- (add-classpath! src-dir)
- (warn-that-dir-not-on-classpath :source src-dir))
- (log/warn (ansip/format-str
- [:yellow
- "The source directory for the main ns "
- (pr-str (str src-dir))
- " is not the classpath!"])))))))
- cfg)
-
-;; targets local config
-(defn- config-repl-serve? [{:keys [ns args] :as cfg}]
- (let [rfs #{"-r" "--repl"}
- sfs #{"-s" "--serve"}]
- (cond-> cfg
- (boolean (or (rfs ns) (rfs (first args))))
- (assoc-in [::config :mode] :repl)
- (boolean (or (sfs ns) (sfs (first args))))
- (->
- (assoc-in [::config :mode] :serve)
- (update ::config update-server-host-port args))
- (rfs (first args))
- (update :args rest)
- (sfs (first args))
- (update :args rest)
- (and (sfs (first args)) (host-port-arg? (second args)))
- (update :args rest))))
-
-;; targets local config
-(defn- config-update-watch-dirs [{:keys [options ::config] :as cfg}]
- ;; remember we have to fix this for the repl-opt fn as well
- ;; so that it understands multiple watch directories
- (update-in cfg [::config :watch-dirs]
- #(not-empty
- (distinct
- (let [ns-watch-dir (and
+ edn "Error validating figwheel options EDN provided to -fwo CLI flag")
+ (update cfg ::config merge edn)))
+
+ (defn print-config-opt [cfg opt]
+ (assoc-in cfg [::config :pprint-config] (not (#{"false"} opt))))
+
+ (defn- watch-opt
+ [cfg path]
+ (when-not (.exists (io/file path))
+ (if (or (string/starts-with? path "-")
+ (string/blank? path))
+ (throw
+ (ex-info
+ (str "Missing watch path")
+ {:cljs.main/error :invalid-arg}))
+ (throw
+ (ex-info
+ (str "Watch path \"" path "\" does not exist")
+ {:cljs.main/error :invalid-arg}))))
+ (update-in cfg [::extra-config :watch-dirs] (fnil conj []) path))
+
+ (defn figwheel-opt [cfg bl]
+ (assoc-in cfg [::config :figwheel-core] (not= bl "false")))
+
+ (defn get-build [bn]
+ (let [fname (if (.contains bn (System/getProperty "path.separator"))
+ bn
+ (str bn ".cljs.edn"))
+ build (->> (cljs.util/split-paths bn)
+ (map #(str % ".cljs.edn"))
+ (string/join (System/getProperty "path.separator"))
+ load-edn-opts)]
+ (when build
+ (when-not (false? (:validate-config (meta build)))
+ (when (meta build)
+ (log/debug "Validating metadata in build: " fname)
+ (validate-config!
+ :figwheel.main.schema.config/edn
+ (meta build)
+ (str "Configuration error in build options meta data: " fname)))
+ (log/debug "Validating CLJS compile options for build:" fname)
+ (validate-config!
+ :figwheel.main.schema.cljs-options/cljs-options
+ build
+ (str "Configuration error in CLJS compile options: " fname))))
+ build))
+
+ (defn watch-dir-from-ns [main-ns]
+ (let [source (fw-util/ns->location main-ns)]
+ (when-let [f (:uri source)]
+ (when (= "file" (.getScheme (.toURI f)))
+ (let [res (fw-util/relativized-path-parts (.getPath f))
+ end-parts (fw-util/path-parts (:relative-path source))]
+ (when (= end-parts (take-last (count end-parts) res))
+ (str (apply io/file (drop-last (count end-parts) res)))))))))
+
+ (def default-main-repl-index-body
+ (str
+ "<p>Welcome to the Figwheel REPL page.</p>"
+ "<p>This page is served when you launch <code>figwheel.main</code> without any command line arguments.</p>"
+ "<p>This page is currently hosting your REPL and application evaluation environment. "
+ "Validate the connection by typing <code>(js/alert&nbsp;\"Hello&nbsp;Figwheel!\")</code> in the REPL.</p>"))
+
+ (defn get-build-with-error [bn]
+ (when-not (.exists (io/file (str bn ".cljs.edn")))
+ (if (or (string/starts-with? bn "-")
+ (string/blank? bn))
+ (throw
+ (ex-info
+ (str "Missing build name")
+ {:cljs.main/error :invalid-arg}))
+ (throw
+ (ex-info
+ (str "Build " (str bn ".cljs.edn") " does not exist")
+ {:cljs.main/error :invalid-arg}))))
+ (get-build bn))
+
+ (defn build-opt [cfg bn]
+ (let [bns (string/split bn #":")
+ id (string/join "" bns)
+ options (->> bns
+ (map get-build-with-error)
+ (reduce merge-meta))]
+ (-> cfg
+ (update :options merge options)
+ (assoc ::build (cond-> {:id id}
+ (meta options)
+ (assoc :config (meta options)))))))
+
+ (defn build-once-opt [cfg bn]
+ (let [cfg (build-opt cfg bn)]
+ (assoc-in cfg [::config ::build-once] true)))
+
+ (defn background-build-opt [cfg bn]
+ (let [{:keys [options ::build]} (build-opt {} bn)]
+ (update cfg ::background-builds
+ (fnil conj [])
+ (assoc build :options options))))
+
+ ;; TODO move these down to main action section
+
+ (declare default-compile)
+
+ (defn build-main-opt [repl-env-fn [_ build-name & args] cfg]
+ ;; serve if no other args
+ (let [args (if-not (#{"-s" "-r" "--repl" "--serve"} (first args))
+ (cons "-s" args)
+ args)]
+ (default-compile repl-env-fn
+ (merge (build-opt cfg build-name)
+ {:args args
+ ::build-main-opt true}))))
+
+ (defn build-once-main-opt [repl-env-fn [_ build-name & args] cfg]
+ (default-compile repl-env-fn
+ (merge (build-once-opt cfg build-name)
+ {:args args})))
+
+ (declare default-output-dir default-output-to)
+
+ (defn make-temp-dir []
+ (let [tempf (java.io.File/createTempFile "figwheel" "repl")]
+ (.delete tempf)
+ (.mkdirs tempf)
+ (.deleteOnExit (io/file tempf))
+ (fw-util/add-classpath! (.toURL tempf))
+ tempf))
+
+ (defn add-temp-dir [cfg]
+ (let [temp-dir (make-temp-dir)
+ config-with-target (assoc-in cfg [::config :target-dir] temp-dir)
+ output-dir (default-output-dir config-with-target)
+ output-to (default-output-to config-with-target)]
+ (-> cfg
+ (assoc-in [:options :output-dir] output-dir)
+ (assoc-in [:options :output-to] output-to)
+ (assoc-in [:options :asset-path]
+ (str "/cljs-out"
+ (when-let [id (-> cfg ::build :id)]
+ (str "/" id)))))))
+
+ (defn pwd-likely-project-root-dir? []
+ (or (some #(.isFile (clojure.java.io/file %))
+ ["project.clj" "deps.edn" "figwheel-main.edn"])
+ (->> (seq (.listFiles (clojure.java.io/file ".")))
+ (map #(.getName %))
+ (some #(.endsWith % ".cljs.edn")))))
+
+ (defn should-add-temp-dir? [cfg]
+ (not (pwd-likely-project-root-dir?)))
+
+ (defn helper-ring-app [handler html-body output-to & [force-index?]]
+ (figwheel.server.ring/default-index-html
+ handler
+ (figwheel.server.ring/index-html (cond-> {}
+ html-body (assoc :body html-body)
+ output-to (assoc :output-to output-to)))
+ force-index?))
+
+ (defn repl-main-opt [repl-env-fn args cfg]
+ (let [cfg (if (should-add-temp-dir? cfg)
+ (add-temp-dir cfg)
+ cfg)
+ cfg (if (get-in cfg [::build :id])
+ cfg
+ (assoc-in cfg [::build :id] "figwheel-default-repl-build"))
+ output-to (get-in cfg [:options :output-to]
+ (default-output-to cfg))]
+ (default-compile
+ repl-env-fn
+ (-> cfg
+ (assoc :args args)
+ (update :options (fn [opt] (merge {:main 'figwheel.repl.preload} opt)))
+ (assoc-in [:options :aot-cache] true)
+ (assoc-in [::config
+ :ring-stack-options
+ :figwheel.server.ring/dev
+ :figwheel.server.ring/system-app-handler]
+ #(helper/middleware
+ %
+ {:header "REPL Host page"
+ :body (slurp (io/resource "public/com/bhauman/figwheel/helper/content/repl_welcome.html"))
+ :output-to output-to}))
+ (assoc-in [::config :mode] :repl)))))
+
+ (declare serve update-config)
+
+ (defn print-conf [cfg]
+ (println "---------------------- Figwheel options ----------------------")
+ (pprint (::config cfg))
+ (println "---------------------- Compiler options ----------------------")
+ (pprint (:options cfg)))
+
+ (defn serve-main-opt [repl-env-fn args b-cfg]
+ (let [{:keys [repl-env-options repl-options options] :as cfg}
+ (-> b-cfg
+ (assoc :args args)
+ update-config)
+ repl-env-options
+ (update-in
+ repl-env-options
+ [:ring-stack-options
+ :figwheel.server.ring/dev
+ :figwheel.server.ring/system-app-handler]
+ (fn [sah]
+ (if sah
+ sah
+ #(helper/serve-only-middleware % {}))))
+ {:keys [pprint-config]} (::config cfg)
+ repl-env (apply repl-env-fn (mapcat identity repl-env-options))]
+ (log/trace "Verbose config:" (with-out-str (pprint cfg)))
+ (if pprint-config
+ (do
+ (log/info ":pprint-config true - printing config:")
+ (print-conf cfg))
+ (serve {:repl-env repl-env
+ :repl-options repl-options
+ :join? true}))))
+
+ (def figwheel-commands
+ {:init {["-w" "--watch"]
+ {:group :cljs.cli/compile :fn watch-opt
+ :arg "path"
+ :doc "Continuously build, only effective with the --compile and --build main options"}
+ ["-fwo" "--fw-opts"]
+ {:group :cljs.cli/compile :fn figwheel-opts-opt
+ :arg "edn"
+ :doc (str "Options to configure figwheel.main, can be an EDN string or "
+ "system-dependent path-separated list of EDN files / classpath resources. Options "
+ "will be merged left to right.")}
+ ["-ro" "--repl-opts"]
+ {:group ::main&compile :fn repl-env-opts-opt
+ :arg "edn"
+ :doc (str "Options to configure the repl-env, can be an EDN string or "
+ "system-dependent path-separated list of EDN files / classpath resources. Options "
+ "will be merged left to right.")}
+ ["-co" "--compile-opts"]
+ {:group :cljs.cli/main&compile :fn compile-opts-opt
+ :arg "edn"
+ :doc (str "Options to configure the build, can be an EDN string or "
+ "system-dependent path-separated list of EDN files / classpath resources. Options "
+ "will be merged left to right. Any meta data will be merged with the figwheel-options.")}
+ ;; TODO uncertain about this
+ ["-fw" "--figwheel"]
+ {:group :cljs.cli/compile :fn figwheel-opt
+ :arg "bool"
+ :doc (str "Use Figwheel to auto reload and report compile info. "
+ "Only takes effect when watching is happening and the "
+ "optimizations level is :none or nil."
+ "Defaults to true.")}
+ ["-bb" "--background-build"]
+ {:group :cljs.cli/compile :fn background-build-opt
+ :arg "str"
+ :doc "The name of a build config to watch and build in the background."}
+ ["-pc" "--print-config"]
+ {:group :cljs.cli/main&compile :fn print-config-opt
+ :doc "Instead of running the command print out the configuration built up by the command. Useful for debugging."}
+ }
+ :main {["-b" "--build"]
+ {:fn build-main-opt
+ :arg "string"
+ :doc (str "Run a compile process. The supplied build name or a list of build names "
+ "(seperated by \":\") refer to "
+ "EDN files of compile options "
+ "IE. If you use \"dev\" as a build name it will indicate "
+ "that a \"dev.cljs.edn\" will be read for "
+ "compile options. "
+ "Multiple build names will merged left to right along with their metadata. "
+ "The --build option will make an "
+ "extra attempt to "
+ "initialize a figwheel live reloading workflow. "
+ "May be followed buy either --repl or --serve. "
+ "If --repl follows, "
+ "will launch a REPL (along with a server) after the compile completes. "
+ "If --serve follows, will only start a web server according to "
+ "current configuration after the compile "
+ "completes.")}
+ ["-bo" "--build-once"]
+ {:fn build-once-main-opt
+ :arg "string"
+ :doc (str "Compile for the build name one time. "
+ "Looks for build EDN files just like the --build command. "
+ "This will not inject Figwheel or REPL functionality into your build. "
+ "It will still inject devtools if you are using :optimizations :none. "
+ "If --serve follows, will start a web server according to "
+ "current configuration after the compile "
+ "completes.")}
+ ["-r" "--repl"]
+ {:fn repl-main-opt
+ :doc "Run a REPL"}
+ ["-s" "--serve"]
+ {:fn serve-main-opt
+ :arg "host:port"
+ :doc "Run a server based on the figwheel-main configuration options."}
+ ["-h" "--help" "-?"]
+ {:fn help-opt
+ :doc "Print this help message and exit"}
+ }})
+
+ ;; ----------------------------------------------------------------------------
+ ;; Config
+ ;; ----------------------------------------------------------------------------
+
+ (defn default-output-dir* [target & [scope]]
+ (->> (cond-> [(or target default-target-dir) "public" "cljs-out"]
+ scope (conj scope))
+ (apply io/file)
+ (.getPath)))
+
+ (defmulti default-output-dir (fn [{:keys [options]}]
+ (get options :target :browser)))
+
+ (defmethod default-output-dir :default [{:keys [::config ::build]}]
+ (default-output-dir* (:target-dir config) (:id build)))
+
+ (defmethod default-output-dir :nodejs [{:keys [::config ::build]}]
+ (let [target (:target-dir config default-target-dir)
+ scope (:id build)]
+ (->> (cond-> [target "node"]
+ scope (conj scope))
+ (apply io/file)
+ (.getPath))))
+
+ (defn default-output-to* [target & [scope]]
+ (.getPath (io/file (or target default-target-dir) "public" "cljs-out"
+ (cond->> "main.js"
+ scope (str scope "-")))))
+
+ (defmulti default-output-to (fn [{:keys [options]}]
+ (get options :target :browser)))
+
+ (defmethod default-output-to :default [{:keys [::config ::build]}]
+ (default-output-to* (:target-dir config) (:id build)))
+
+ (defmethod default-output-to :nodejs [{:keys [::build] :as cfg}]
+ (let [scope (:id build)]
+ (.getPath (io/file (default-output-dir cfg)
+ (cond->> "main.js"
+ scope (str scope "-"))))))
+
+ (defn extra-config-merge [a' b']
+ (merge-with (fn [a b]
+ (cond
+ (and (map? a) (map? b)) (merge a b)
+ (and (sequential? a)
+ (sequential? b))
+ (distinct (concat a b))
+ (nil? b) a
+ :else b))
+ a' b'))
+
+ (defn resolve-fn-var [prefix handler]
+ (if (or (nil? handler) (var? handler))
+ handler
+ (let [prefix (when prefix (str prefix ": "))]
+ (when (and handler (nil? (namespace (symbol handler))))
+ (throw
+ (ex-info
+ (format "%sThe var '%s has the wrong form it must be a namespaced symbol" prefix
+ (pr-str handler))
+ {::error true :handler handler})))
+ (let [handler-res (fw-util/require-resolve-handler-or-error handler)]
+ (when (and handler (not handler-res))
+ (throw (ex-info
+ (format "%sWas able to load namespace '%s but unable to resolve the specific var: '%s"
+ prefix
+ (namespace (symbol handler))
+ (str handler))
+ {::error true
+ :handler handler})))
+ (when (map? handler-res)
+ (letfn [(error [s]
+ (throw (ex-info s {::error true :handler handler})))]
+ (condp = (:stage handler-res)
+ :bad-namespaced-symbol
+ (do (log/syntax-exception (:exception handler-res))
+ (error (format "%sThere was an error while trying to resolve '%s"
+ prefix
+ (pr-str handler))))
+ :unable-to-resolve-handler-fn
+ (error (format "%sWas able to load namespace '%s but unable to resolve the specific var: '%s"
+ prefix
+ (namespace (symbol handler))
+ (str handler)))
+ :unable-to-load-handler-namespace
+ (do
+ (log/syntax-exception (:exception handler-res))
+ (error (format "%sThere was an exception while requiring the namespace '%s while trying to load the var '%s"
+ prefix
+ (namespace (symbol handler))
+ (str handler)))))))
+ handler-res))))
+
+ (defn resolve-ring-handler [ring-handler]
+ (resolve-fn-var "ring-handler" ring-handler))
+
+ (defn process-main-config [{:keys [ring-handler] :as main-config}]
+ (let [handler (resolve-ring-handler ring-handler)]
+ (cond-> main-config
+ handler (assoc :ring-handler handler))))
+
+ (defn process-figwheel-main-edn [main-edn]
+ (when main-edn
+ (when-not (false? (:validate-config main-edn))
+ (log/info "Validating figwheel-main.edn")
+ (validate-config!
+ :figwheel.main.schema.config/edn
+ main-edn "Configuration error in figwheel-main.edn"
+ "figwheel-main.edn is valid \\(ツ)/"))
+ (process-main-config main-edn)))
+
+ ;; use tools reader read-string for better error messages
+ #_(redn/read-string)
+ (defn fetch-figwheel-main-edn [cfg]
+ (when (.isFile (io/file "figwheel-main.edn"))
+ (read-edn-file "figwheel-main.edn")))
+
+ (defn- config-figwheel-main-edn [cfg]
+ (let [config-edn (process-figwheel-main-edn
+ (or (::start-figwheel-options cfg)
+ (fetch-figwheel-main-edn cfg)))]
+ (cond-> cfg
+ config-edn (update ::config #(merge config-edn %)))))
+
+ (defn- config-merge-current-build-conf [{:keys [::extra-config ::build] :as cfg}]
+ (update cfg
+ ::config #(extra-config-merge
+ (merge-with (fn [a b] (if b b a)) %
+ (process-main-config (:config build)))
+ extra-config)))
+
+ (defn host-port-arg? [arg]
+ (and arg (re-matches #"(.*):(\d*)" arg)))
+
+ (defn update-server-host-port [config [f address-port & args]]
+ (if (and (#{"-s" "--serve"} f) address-port)
+ (let [[_ host port] (host-port-arg? address-port)]
+ (cond-> config
+ (not (string/blank? host)) (assoc-in [:ring-server-options :host] host)
+ (not (string/blank? port)) (assoc-in [:ring-server-options :port] (Integer/parseInt port))))
+ config))
+
+ ;; targets options
+ (defn- config-main-ns [{:keys [ns options] :as cfg}]
+ (let [main-ns (if (and ns (not (#{"-r" "--repl" "-s" "--serve"} ns)))
+ (symbol ns)
+ (:main options))]
+ (cond-> cfg
+ main-ns (assoc :ns main-ns) ;; TODO not needed?
+ main-ns (assoc-in [:options :main] main-ns))))
+
+ (defn warn-that-dir-not-on-classpath [typ dir]
+ (let [[n k] (condp = typ
+ :source ["Source directory" :source-paths]
+ :target ["Target directory" :resource-paths])]
+ (log/warn (ansip/format-str
+ [:yellow n " "
+ (pr-str (str dir))
+ " is not on the classpath"]))
+ (log/warn "Please fix this by adding" (pr-str (str dir))
+ "to your classpath\n"
+ "I.E.\n"
+ "For Clojure CLI Tools in your deps.edn file:\n"
+ " ensure" (pr-str (str dir))
+ "is in your :paths key\n\n"
+ (when k
+ (format
+ (str "For Leiningen in your project.clj:\n"
+ " add it to the %s key\n")
+ (pr-str k))))))
+
+ ;; takes a string or file representation of a directory
+ (defn- add-classpath! [dir]
+ (when-not (fw-util/dir-on-classpath? dir)
+ (log/warn (ansip/format-str [:yellow
+ (format "Attempting to dynamically add %s to classpath!"
+ (pr-str (str dir)))]))
+ (fw-util/add-classpath! (->> dir
+ io/file
+ .getCanonicalPath
+ io/file
+ .toURL))))
+
+ (defn- config-main-source-path-on-classpath [{:keys [options] :as cfg}]
+ (when-let [main (:ns cfg)]
+ (when-not (fw-util/safe-ns->location main)
+ (when-let [src-dir (fw-util/find-source-dir-for-cljs-ns main)]
+ (when-not (fw-util/dir-on-classpath? src-dir)
+ (if (get-in cfg [::config :helpful-classpaths] true)
+ (do
+ (add-classpath! src-dir)
+ (warn-that-dir-not-on-classpath :source src-dir))
+ (log/warn (ansip/format-str
+ [:yellow
+ "The source directory for the main ns "
+ (pr-str (str src-dir))
+ " is not the classpath!"])))))))
+ cfg)
+
+ ;; targets local config
+ (defn- config-repl-serve? [{:keys [ns args] :as cfg}]
+ (let [rfs #{"-r" "--repl"}
+ sfs #{"-s" "--serve"}]
+ (cond-> cfg
+ (boolean (or (rfs ns) (rfs (first args))))
+ (assoc-in [::config :mode] :repl)
+ (boolean (or (sfs ns) (sfs (first args))))
+ (->
+ (assoc-in [::config :mode] :serve)
+ (update ::config update-server-host-port args))
+ (rfs (first args))
+ (update :args rest)
+ (sfs (first args))
+ (update :args rest)
+ (and (sfs (first args)) (host-port-arg? (second args)))
+ (update :args rest))))
+
+ ;; targets local config
+ (defn- config-update-watch-dirs [{:keys [options ::config] :as cfg}]
+ ;; remember we have to fix this for the repl-opt fn as well
+ ;; so that it understands multiple watch directories
+ (update-in cfg [::config :watch-dirs]
+ #(not-empty
+ (distinct
+ (let [ns-watch-dir (and
(#{:repl :serve} (:mode config))
(not (::build-once config))
(not (:watch options))
(empty? %)
(:main options)
(watch-dir-from-ns (:main options)))]
- (cond-> %
- (:watch options) (conj (:watch options))
- ns-watch-dir (conj ns-watch-dir)))))))
-
-(defn- config-ensure-watch-dirs-on-classpath [{:keys [::config] :as cfg}]
- (doseq [src-dir (:watch-dirs config)]
- (when-not (fw-util/dir-on-current-classpath? src-dir)
- (log/warn (ansip/format-str
- [:yellow
- "The watch directory "
- (pr-str (str src-dir))
- " is not on the classpath! A watch directory is must "
- "on the classpath and point to the root directory of your namespace "
- "source tree. A general all encompassing watch directory will not work."]))
- (when (get config :helpful-classpaths true)
- (add-classpath! src-dir)
- (warn-that-dir-not-on-classpath :source src-dir)))) cfg)
-
-;; needs local config
-(defn figwheel-mode? [{:keys [::config options]}]
- (and (:figwheel-core config true)
- (and (#{:repl :serve} (:mode config))
- (not (::build-once config))
- (not-empty (:watch-dirs config)))
- (= :none (:optimizations options :none))))
-
-(defn repl-connection? [{:keys [::config options] :as cfg}]
- (or (and (#{:repl :serve} (:mode config))
- (not (::build-once config))
- (= :none (:optimizations options :none)))
- (figwheel-mode? cfg)))
-
-;; TODO this is a no-op right now
-(defn prep-client-config [config]
- (let [cl-config (select-keys config [])]
- cl-config))
-
-;; targets options needs local config
-(defn- config-figwheel-mode? [{:keys [::config options] :as cfg}]
- (cond-> cfg
- ;; check for a main??
- (figwheel-mode? cfg)
- (->
- (update ::initializers (fnil conj []) #(figwheel.core/start*))
- (update-in [:options :preloads]
- (fn [p]
- (vec (distinct
- (concat p '[figwheel.core figwheel.main]))))))
- (false? (:heads-up-display config))
- (update-in [:options :closure-defines] assoc 'figwheel.core/heads-up-display false)
- (true? (:load-warninged-code config))
- (update-in [:options :closure-defines] assoc 'figwheel.core/load-warninged-code true)))
-
-(defn- modules-output-to [{:keys [options ::config ::build] :as cfg}]
- (if (not-empty (:modules options))
- (update-in
- cfg [:options :modules]
- #(->> %
- (map (fn [[k v]]
- (if-not (:output-to v)
- [k (assoc v :output-to
- (string/replace
- (default-output-to cfg)
- "main.js" ;; brittle
- (str (name k) ".js")))]
- [k v])))
- (into {})))
- cfg))
-
-;; targets options
-;; TODO needs to consider case where one or the other is specified???
-(defn- config-default-dirs [{:keys [options ::config ::build] :as cfg}]
- (cond-> cfg
- (and (nil? (:output-to options)) (not (:modules options)))
- (assoc-in [:options :output-to] (default-output-to cfg))
- (:modules options)
- modules-output-to
- (nil? (:output-dir options))
- (assoc-in [:options :output-dir] (default-output-dir cfg))))
-
-(defn figure-default-asset-path [{:keys [figwheel-options options ::config ::build] :as cfg}]
- (if (= :nodejs (:target options))
- (:output-dir options)
- (let [{:keys [output-dir]} options]
- ;; TODO could discover the resource root if there is only one
- ;; or if ONLY static file serving can probably do something with that
- ;; as well
- ;; UNTIL THEN if you have configured your static resources no default asset-path
- (when-not (contains? (:ring-stack-options figwheel-options) :static)
- (let [parts (fw-util/relativized-path-parts (or output-dir
- (default-output-dir cfg)))]
- (when-let [asset-path
- (->> parts
- (split-with (complement #{"public"}))
- last
- rest
- not-empty)]
- (str "/" (string/join "/" asset-path))))))))
-
-;; targets options
-(defn- config-default-asset-path [{:keys [options] :as cfg}]
- (cond-> cfg
- (nil? (:asset-path options))
- (assoc-in [:options :asset-path] (figure-default-asset-path cfg))))
-
-;; targets options
-(defn- config-default-aot-cache-false [{:keys [options] :as cfg}]
- (cond-> cfg
- (not (contains? options :aot-cache))
- (assoc-in [:options :aot-cache] false)))
-
-(defn config-clean [cfg]
- (update cfg :options dissoc :watch))
-
-;; TODO create connection
-
-(let [localhost (promise)]
- ;; this call takes a very long time to complete so lets get in in parallel
- (doto (Thread. #(deliver localhost (try (java.net.InetAddress/getLocalHost)
- (catch Throwable e
- nil))))
- (.setDaemon true)
- (.start))
- (defn fill-connect-url-template [url host server-port]
- (cond-> url
- (.contains url "[[config-hostname]]")
- (string/replace "[[config-hostname]]" (or host "localhost"))
-
- (.contains url "[[server-hostname]]")
- (string/replace "[[server-hostname]]" (or (some-> @localhost
- .getHostName)
- "localhost"))
-
- (.contains url "[[server-ip]]")
- (string/replace "[[server-ip]]" (or (some-> @localhost
- .getHostAddress)
- "127.0.0.1"))
-
- (.contains url "[[server-port]]")
- (string/replace "[[server-port]]" (str server-port)))))
-
-(defn add-to-query [uri query-map]
- (let [[pre query] (string/split uri #"\?")]
- (str pre
- (when (or query (not-empty query-map))
- (str "?"
- (string/join "&"
- (map (fn [[k v]]
- (str (name k)
- "="
- (java.net.URLEncoder/encode (str v) "UTF-8")))
- query-map))
- (when (not (string/blank? query))
- (str "&" query)))))))
-
-#_(add-to-query "ws://localhost:9500/figwheel-connect?hey=5" {:ab 'ab})
-
-(defn config-connect-url [{:keys [::config repl-env-options] :as cfg} connect-id]
- (let [port (get-in config [:ring-server-options :port] figwheel.repl/default-port)
- host (get-in config [:ring-server-options :host] "localhost")
- connect-url
- (fill-connect-url-template
- (:connect-url config "ws://[[config-hostname]]:[[server-port]]/figwheel-connect")
- host
- port)]
- (add-to-query connect-url connect-id)))
-
-#_(config-connect-url {} {:abb 1})
-
-(defn config-repl-connect [{:keys [::config options ::build] :as cfg}]
- (let [connect-id (:connect-id config
- (cond-> {:fwprocess process-unique}
- (:id build) (assoc :fwbuild (:id build))))
- conn-url (config-connect-url cfg connect-id)
- conn? (repl-connection? cfg)]
- (cond-> cfg
- conn?
- (update-in [:options :closure-defines] assoc 'figwheel.repl/connect-url conn-url)
- conn?
- (update-in [:options :preloads]
- (fn [p]
- (vec (distinct
- (concat p '[figwheel.repl.preload])))))
- conn?
- (update-in [:options :repl-requires] into '[[cljs.repl :refer-macros [source doc find-doc apropos dir pst]]
- [cljs.pprint :refer [pprint] :refer-macros [pp]]
- [figwheel.main :refer-macros [stop-builds start-builds build-once reset clean status]]
- [figwheel.repl :refer-macros [conns focus]]])
- (and conn? (:client-print-to config))
- (update-in [:options :closure-defines] assoc
- 'figwheel.repl/print-output
- (string/join "," (distinct (map name (:client-print-to config)))))
- (and conn? (:client-log-level config))
- (update-in [:options :closure-defines] assoc
- 'figwheel.repl/client-log-level
- (name (:client-log-level config)))
- (and conn? (not-empty (:watch-dirs config)))
- (update-in [:repl-options :analyze-path] (comp vec concat) (:watch-dirs config))
- (and conn? (not-empty connect-id))
- (assoc-in [:repl-env-options :connection-filter]
- (let [kys (keys connect-id)]
- (fn [{:keys [query]}]
- (if (not (:fwprocess query))
- (= (:fwbuild query)
- (:fwbuild connect-id))
- (= (select-keys query kys)
- connect-id))))))))
-
-(defn config-cljs-devtools [{:keys [::config options] :as cfg}]
- (if (and
- (nil? (:target options))
- (= :none (:optimizations options :none))
- (:cljs-devtools config true)
- (try (bapi/ns->location 'devtools.preload) (catch Throwable t false)))
- (update-in cfg
- [:options :preloads]
- (fn [p]
- (vec (distinct
- (concat p '[devtools.preload])))))
- cfg))
-
-(defn config-open-file-command [{:keys [::config options] :as cfg}]
- (if-let [setup (and (:open-file-command config)
- (repl-connection? cfg)
- (fw-util/require-resolve-var 'figwheel.main.editor/setup))]
- (-> cfg
- (update ::initializers (fnil conj []) #(setup (:open-file-command config)))
- (update-in [:options :preloads]
- (fn [p] (vec (distinct (conj p 'figwheel.main.editor))))))
- cfg))
-
-(defn config-eval-back [{:keys [::config options] :as cfg}]
- (if-let [setup (and (repl-connection? cfg)
- (fw-util/require-resolve-var 'figwheel.main.evalback/setup))]
- (-> cfg
- (update ::initializers (fnil conj []) #(setup))
- (update-in [:options :preloads]
- (fn [p] (vec (distinct (concat p '[figwheel.main.evalback #_figwheel.main.testing]))))))
- cfg))
-
-(defn watch-css [css-dirs]
- (when-let [css-dirs (not-empty css-dirs)]
- (when-let [start-css (fw-util/require-resolve-var 'figwheel.main.css-reload/start*)]
- (start-css css-dirs))))
-
-(defn config-watch-css [{:keys [::config options] :as cfg}]
- (cond-> cfg
- (and (not-empty (:css-dirs config))
- (repl-connection? cfg))
- (->
- (update ::initializers (fnil conj []) #(watch-css (:css-dirs config)))
- (update-in [:options :preloads]
- (fn [p] (vec (distinct (conj p 'figwheel.main.css-reload))))))))
-
-(defn get-repl-options [{:keys [options args inits repl-options] :as cfg}]
- (assoc (merge (dissoc options :main)
- repl-options)
- :inits
- (into
- [{:type :init-forms
- :forms (when-not (empty? args)
- [`(set! *command-line-args* (list ~@args))])}]
- inits)))
-
-(defn get-repl-env-options [{:keys [repl-env-options ::config options] :as cfg}]
- (let [repl-options (get-repl-options cfg)]
- (merge
- (select-keys config
- [:ring-server
- :ring-server-options
- :ring-stack
- :ring-stack-options
- :ring-handler
- :cljsjs-resources
- :launch-node
- :inspect-node
- :node-command
- :broadcast
- :open-url
- :launch-js
- :repl-eval-timeout])
- repl-env-options ;; from command line
- (select-keys options [:output-to :output-dir :target]))))
-
-(defn config-finalize-repl-options [cfg]
- (let [repl-options (get-repl-options cfg)
- repl-env-options (get-repl-env-options cfg)]
- (assoc cfg
- :repl-options repl-options
- :repl-env-options repl-env-options)))
-
-(defn config-set-log-level! [{:keys [::config] :as cfg}]
- (when-let [log-level (:log-level config)]
- (log/set-level log-level))
- cfg)
-
-(defn config-ansi-color-output! [{:keys [::config] :as cfg}]
- (when (some? (:ansi-color-output config))
- (alter-var-root #'ansip/*use-color* (fn [_] (:ansi-color-output config))))
- cfg)
-
-(defn config-log-syntax-error-style! [{:keys [::config] :as cfg}]
- (when (some? (:log-syntax-error-style config))
- (alter-var-root #'log/*syntax-error-style* (fn [_] (:log-syntax-error-style config))))
- cfg)
-
-(defn- config-warn-resource-directory-not-on-classpath [{:keys [::config options] :as cfg}]
- ;; this could check for other directories than resources
- ;; but this is mainly to help newcomers
- (when (and (nil? (:target options))
- (or (and (::build-once config)
- (#{:serve} (:mode config)))
- (#{:repl :serve} (:mode config)))
- (.isFile (io/file "resources/public/index.html"))
- (not (fw-util/dir-on-classpath? "resources")))
- (log/warn (ansip/format-str
- [:yellow "A \"resources/public/index.html\" exists but the \"resources\" directory is not on the classpath\n"
- " the default server will not be able to find your index.html"])))
- cfg)
-
-(defn config-pre-post-hooks [{:keys [::config] :as cfg}]
- (cond-> cfg
- (not-empty (:pre-build-hooks config))
- (update ::pre-build-hooks concat
- (doall
- (keep (partial resolve-fn-var "pre-build-hook-fn")
- (:pre-build-hooks config))))
- (not-empty (:post-build-hooks config))
- (update ::post-build-hooks concat
- (doall
- (keep (partial resolve-fn-var "post-build-hook-fn")
- (:post-build-hooks config))))))
-
-(defn alter-output-to [nm {:keys [output-to output-dir] :as opts}]
- (cond
- output-to
- (let [f (io/file output-to)
- fname (string/replace (.getName f) #"\.js$" "")]
- (assoc opts :output-to
- (str (io/file (.getParent f) (str fname "-" nm ".js")))))
- output-dir
- (assoc opts :output-to (str (io/file output-dir (str "main-" nm ".js"))))
- :else nil))
-
-(defn merge-extra-key-with [m extra-m f k]
- (if-let [value (get extra-m k)]
- (let [act-key (keyword (string/replace (name k) #"^extra-" "")) ]
- (update m act-key f value))
- m))
-
-(defn merge-extra-cljs-options
- "Merges ClojureScript options that are collections if they are
- prepended with :extra."
- [opts extra-opts]
- (let [coll-keys [:extra-foreign-libs
- :extra-externs
- :extra-preloads
- :extra-closure-extra-annotations]
- map-keys [:extra-modules
- :extra-npm-deps
- :extra-closure-defines
- :extra-closure-warnings]]
- (as-> (merge opts extra-opts) opts
- (apply dissoc opts
- (concat coll-keys map-keys
- [:extra-warnings]))
- (merge-extra-key-with opts extra-opts (fn [x y]
- (merge (if (boolean? x) {} x) y)) :extra-warnings)
- (reduce #(merge-extra-key-with %1 extra-opts (comp vec concat) %2) opts coll-keys)
- (reduce #(merge-extra-key-with %1 extra-opts merge %2) opts map-keys))))
-
-(defn extra-main-options [nm em-options options]
- ;; need to remove modules so that we get a funcitioning independent main endpoint
- (merge-extra-cljs-options
- (dissoc (alter-output-to (name nm) options) :modules)
- em-options))
-
-(defn- compile-resource-helper [res opts]
- (let [parts (-> res
- (string/replace #"\.cljs$" ".js")
- (string/split #"/"))]
- (when-not (.exists (apply io/file (:output-dir opts) parts))
- (cljs.closure/-compile (io/resource res)
- (assoc opts
- :output-file
- (str (apply io/file parts)))))))
-
-(defn extra-main-fn [nm em-options options]
- ;; TODO modules??
- (let [opts (extra-main-options nm em-options options)]
- (fn [_]
- (log/info (format "Outputting main file: %s" (:output-to opts "main.js")))
- (let [switch-to-node? (and (= :nodejs (:target em-options)) (not= :nodejs (:target options)))
- ;; fix asset-path for nodejs to output-dir if the original options are not nodejs
- ;; and if the asset path isn't set in the extra-main options
- opts (if (and switch-to-node? (not (:asset-path em-options)))
- (assoc opts :asset-path (:output-dir opts))
- opts)]
- (cljs.closure/output-main-file
- (cljs.closure/add-implicit-options
- opts))
- (when switch-to-node?
- (compile-resource-helper "cljs/nodejs.cljs" opts)
- (compile-resource-helper "cljs/nodejscli.cljs" opts)
- (spit (io/file (:output-dir opts) "cljs_deps.js")
- (str "goog.addDependency(\"../cljs/nodejs.js\", ['cljs.nodejs'], []);\n"
- "goog.addDependency(\"../cljs/nodejscli.js\", ['cljs.nodejscli'], ['goog.object', 'cljs.nodejs']);\n")
- :append true)
- (cljs.closure/output-bootstrap opts))))))
-
-(defn config-extra-mains [{:keys [::config options] :as cfg}]
- (let [{:keys [extra-main-files]} config]
- (if (and (not-empty extra-main-files)
- (= :none (:optimizations options :none)))
- (update cfg ::post-build-hooks
- concat (map (fn [[k v]]
- (extra-main-fn k v options))
- extra-main-files))
- cfg)))
-
-(defn expand-build-inputs [{:keys [watch-dirs build-inputs] :as config} {:keys [main] :as options}]
- (doall
- (distinct
- (mapcat
- (fn [x]
+ (cond-> %
+ (:watch options) (conj (:watch options))
+ ns-watch-dir (conj ns-watch-dir)))))))
+
+ (defn- config-ensure-watch-dirs-on-classpath [{:keys [::config] :as cfg}]
+ (doseq [src-dir (:watch-dirs config)]
+ (when-not (fw-util/dir-on-current-classpath? src-dir)
+ (log/warn (ansip/format-str
+ [:yellow
+ "The watch directory "
+ (pr-str (str src-dir))
+ " is not on the classpath! A watch directory is must "
+ "on the classpath and point to the root directory of your namespace "
+ "source tree. A general all encompassing watch directory will not work."]))
+ (when (get config :helpful-classpaths true)
+ (add-classpath! src-dir)
+ (warn-that-dir-not-on-classpath :source src-dir)))) cfg)
+
+ ;; needs local config
+ (defn figwheel-mode? [{:keys [::config options]}]
+ (and (:figwheel-core config true)
+ (and (#{:repl :serve} (:mode config))
+ (not (::build-once config))
+ (not-empty (:watch-dirs config)))
+ (= :none (:optimizations options :none))))
+
+ (defn repl-connection? [{:keys [::config options] :as cfg}]
+ (or (and (#{:repl :serve} (:mode config))
+ (not (::build-once config))
+ (= :none (:optimizations options :none)))
+ (figwheel-mode? cfg)))
+
+ ;; TODO this is a no-op right now
+ (defn prep-client-config [config]
+ (let [cl-config (select-keys config [])]
+ cl-config))
+
+ ;; targets options needs local config
+ (defn- config-figwheel-mode? [{:keys [::config options] :as cfg}]
+ (cond-> cfg
+ ;; check for a main??
+ (figwheel-mode? cfg)
+ (->
+ (update ::initializers (fnil conj []) #(figwheel.core/start*))
+ (update-in [:options :preloads]
+ (fn [p]
+ (vec (distinct
+ (concat p '[figwheel.core figwheel.main]))))))
+ (false? (:heads-up-display config))
+ (update-in [:options :closure-defines] assoc 'figwheel.core/heads-up-display false)
+ (true? (:load-warninged-code config))
+ (update-in [:options :closure-defines] assoc 'figwheel.core/load-warninged-code true)))
+
+ (defn- modules-output-to [{:keys [options ::config ::build] :as cfg}]
+ (if (not-empty (:modules options))
+ (update-in
+ cfg [:options :modules]
+ #(->> %
+ (map (fn [[k v]]
+ (if-not (:output-to v)
+ [k (assoc v :output-to
+ (string/replace
+ (default-output-to cfg)
+ "main.js" ;; brittle
+ (str (name k) ".js")))]
+ [k v])))
+ (into {})))
+ cfg))
+
+ ;; targets options
+ ;; TODO needs to consider case where one or the other is specified???
+ (defn- config-default-dirs [{:keys [options ::config ::build] :as cfg}]
+ (cond-> cfg
+ (and (nil? (:output-to options)) (not (:modules options)))
+ (assoc-in [:options :output-to] (default-output-to cfg))
+ (:modules options)
+ modules-output-to
+ (nil? (:output-dir options))
+ (assoc-in [:options :output-dir] (default-output-dir cfg))))
+
+ (defn figure-default-asset-path [{:keys [figwheel-options options ::config ::build] :as cfg}]
+ (if (= :nodejs (:target options))
+ (:output-dir options)
+ (let [{:keys [output-dir]} options]
+ ;; TODO could discover the resource root if there is only one
+ ;; or if ONLY static file serving can probably do something with that
+ ;; as well
+ ;; UNTIL THEN if you have configured your static resources no default asset-path
+ (when-not (contains? (:ring-stack-options figwheel-options) :static)
+ (let [parts (fw-util/relativized-path-parts (or output-dir
+ (default-output-dir cfg)))]
+ (when-let [asset-path
+ (->> parts
+ (split-with (complement #{"public"}))
+ last
+ rest
+ not-empty)]
+ (str "/" (string/join "/" asset-path))))))))
+
+ ;; targets options
+ (defn- config-default-asset-path [{:keys [options] :as cfg}]
+ (cond-> cfg
+ (nil? (:asset-path options))
+ (assoc-in [:options :asset-path] (figure-default-asset-path cfg))))
+
+ ;; targets options
+ (defn- config-default-aot-cache-false [{:keys [options] :as cfg}]
+ (cond-> cfg
+ (not (contains? options :aot-cache))
+ (assoc-in [:options :aot-cache] false)))
+
+ (defn config-clean [cfg]
+ (update cfg :options dissoc :watch))
+
+ ;; TODO create connection
+
+ (let [localhost (promise)]
+ ;; this call takes a very long time to complete so lets get in in parallel
+ (doto (Thread. #(deliver localhost (try (java.net.InetAddress/getLocalHost)
+ (catch Throwable e
+ nil))))
+ (.setDaemon true)
+ (.start))
+ (defn fill-connect-url-template [url host server-port]
+ (cond-> url
+ (.contains url "[[config-hostname]]")
+ (string/replace "[[config-hostname]]" (or host "localhost"))
+
+ (.contains url "[[server-hostname]]")
+ (string/replace "[[server-hostname]]" (or (some-> @localhost
+ .getHostName)
+ "localhost"))
+
+ (.contains url "[[server-ip]]")
+ (string/replace "[[server-ip]]" (or (some-> @localhost
+ .getHostAddress)
+ "127.0.0.1"))
+
+ (.contains url "[[server-port]]")
+ (string/replace "[[server-port]]" (str server-port)))))
+
+ (defn add-to-query [uri query-map]
+ (let [[pre query] (string/split uri #"\?")]
+ (str pre
+ (when (or query (not-empty query-map))
+ (str "?"
+ (string/join "&"
+ (map (fn [[k v]]
+ (str (name k)
+ "="
+ (java.net.URLEncoder/encode (str v) "UTF-8")))
+ query-map))
+ (when (not (string/blank? query))
+ (str "&" query)))))))
+
+ #_(add-to-query "ws://localhost:9500/figwheel-connect?hey=5" {:ab 'ab})
+
+ (defn config-connect-url [{:keys [::config repl-env-options] :as cfg} connect-id]
+ (let [port (get-in config [:ring-server-options :port] figwheel.repl/default-port)
+ host (get-in config [:ring-server-options :host] "localhost")
+ connect-url
+ (fill-connect-url-template
+ (:connect-url config "ws://[[config-hostname]]:[[server-port]]/figwheel-connect")
+ host
+ port)]
+ (add-to-query connect-url connect-id)))
+
+ #_(config-connect-url {} {:abb 1})
+
+ (defn config-repl-connect [{:keys [::config options ::build] :as cfg}]
+ (let [connect-id (:connect-id config
+ (cond-> {:fwprocess process-unique}
+ (:id build) (assoc :fwbuild (:id build))))
+ conn-url (config-connect-url cfg connect-id)
+ conn? (repl-connection? cfg)]
+ (cond-> cfg
+ conn?
+ (update-in [:options :closure-defines] assoc 'figwheel.repl/connect-url conn-url)
+ conn?
+ (update-in [:options :preloads]
+ (fn [p]
+ (vec (distinct
+ (concat p '[figwheel.repl.preload])))))
+ conn?
+ (update-in [:options :repl-requires] into '[[cljs.repl :refer-macros [source doc find-doc apropos dir pst]]
+ [cljs.pprint :refer [pprint] :refer-macros [pp]]
+ [figwheel.main :refer-macros [stop-builds start-builds build-once reset clean status]]
+ [figwheel.repl :refer-macros [conns focus]]])
+ (and conn? (:client-print-to config))
+ (update-in [:options :closure-defines] assoc
+ 'figwheel.repl/print-output
+ (string/join "," (distinct (map name (:client-print-to config)))))
+ (and conn? (:client-log-level config))
+ (update-in [:options :closure-defines] assoc
+ 'figwheel.repl/client-log-level
+ (name (:client-log-level config)))
+ (and conn? (not-empty (:watch-dirs config)))
+ (update-in [:repl-options :analyze-path] (comp vec concat) (:watch-dirs config))
+ (and conn? (not-empty connect-id))
+ (assoc-in [:repl-env-options :connection-filter]
+ (let [kys (keys connect-id)]
+ (fn [{:keys [query]}]
+ (if (not (:fwprocess query))
+ (= (:fwbuild query)
+ (:fwbuild connect-id))
+ (= (select-keys query kys)
+ connect-id))))))))
+
+ (defn config-cljs-devtools [{:keys [::config options] :as cfg}]
+ (if (and
+ (nil? (:target options))
+ (= :none (:optimizations options :none))
+ (:cljs-devtools config true)
+ (try (bapi/ns->location 'devtools.preload) (catch Throwable t false)))
+ (update-in cfg
+ [:options :preloads]
+ (fn [p]
+ (vec (distinct
+ (concat p '[devtools.preload])))))
+ cfg))
+
+ (defn config-open-file-command [{:keys [::config options] :as cfg}]
+ (if-let [setup (and (:open-file-command config)
+ (repl-connection? cfg)
+ (fw-util/require-resolve-var 'figwheel.main.editor/setup))]
+ (-> cfg
+ (update ::initializers (fnil conj []) #(setup (:open-file-command config)))
+ (update-in [:options :preloads]
+ (fn [p] (vec (distinct (conj p 'figwheel.main.editor))))))
+ cfg))
+
+ (defn config-eval-back [{:keys [::config options] :as cfg}]
+ (if-let [setup (and (repl-connection? cfg)
+ (fw-util/require-resolve-var 'figwheel.main.evalback/setup))]
+ (-> cfg
+ (update ::initializers (fnil conj []) #(setup))
+ (update-in [:options :preloads]
+ (fn [p] (vec (distinct (concat p '[figwheel.main.evalback #_figwheel.main.testing]))))))
+ cfg))
+
+ (defn watch-css [css-dirs]
+ (when-let [css-dirs (not-empty css-dirs)]
+ (when-let [start-css (fw-util/require-resolve-var 'figwheel.main.css-reload/start*)]
+ (start-css css-dirs))))
+
+ (defn config-watch-css [{:keys [::config options] :as cfg}]
+ (cond-> cfg
+ (and (not-empty (:css-dirs config))
+ (repl-connection? cfg))
+ (->
+ (update ::initializers (fnil conj []) #(watch-css (:css-dirs config)))
+ (update-in [:options :preloads]
+ (fn [p] (vec (distinct (conj p 'figwheel.main.css-reload))))))))
+
+ (defn get-repl-options [{:keys [options args inits repl-options] :as cfg}]
+ (assoc (merge (dissoc options :main)
+ repl-options)
+ :inits
+ (into
+ [{:type :init-forms
+ :forms (when-not (empty? args)
+ [`(set! *command-line-args* (list ~@args))])}]
+ inits)))
+
+ (defn get-repl-env-options [{:keys [repl-env-options ::config options] :as cfg}]
+ (let [repl-options (get-repl-options cfg)]
+ (merge
+ (select-keys config
+ [:ring-server
+ :ring-server-options
+ :ring-stack
+ :ring-stack-options
+ :ring-handler
+ :cljsjs-resources
+ :launch-node
+ :inspect-node
+ :node-command
+ :broadcast
+ :open-url
+ :launch-js
+ :repl-eval-timeout])
+ repl-env-options ;; from command line
+ (select-keys options [:output-to :output-dir :target]))))
+
+ (defn config-finalize-repl-options [cfg]
+ (let [repl-options (get-repl-options cfg)
+ repl-env-options (get-repl-env-options cfg)]
+ (assoc cfg
+ :repl-options repl-options
+ :repl-env-options repl-env-options)))
+
+ (defn config-set-log-level! [{:keys [::config] :as cfg}]
+ (when-let [log-level (:log-level config)]
+ (log/set-level log-level))
+ cfg)
+
+ (defn config-ansi-color-output! [{:keys [::config] :as cfg}]
+ (when (some? (:ansi-color-output config))
+ (alter-var-root #'ansip/*use-color* (fn [_] (:ansi-color-output config))))
+ cfg)
+
+ (defn config-log-syntax-error-style! [{:keys [::config] :as cfg}]
+ (when (some? (:log-syntax-error-style config))
+ (alter-var-root #'log/*syntax-error-style* (fn [_] (:log-syntax-error-style config))))
+ cfg)
+
+ (defn- config-warn-resource-directory-not-on-classpath [{:keys [::config options] :as cfg}]
+ ;; this could check for other directories than resources
+ ;; but this is mainly to help newcomers
+ (when (and (nil? (:target options))
+ (or (and (::build-once config)
+ (#{:serve} (:mode config)))
+ (#{:repl :serve} (:mode config)))
+ (.isFile (io/file "resources/public/index.html"))
+ (not (fw-util/dir-on-classpath? "resources")))
+ (log/warn (ansip/format-str
+ [:yellow "A \"resources/public/index.html\" exists but the \"resources\" directory is not on the classpath\n"
+ " the default server will not be able to find your index.html"])))
+ cfg)
+
+ (defn config-pre-post-hooks [{:keys [::config] :as cfg}]
+ (cond-> cfg
+ (not-empty (:pre-build-hooks config))
+ (update ::pre-build-hooks concat
+ (doall
+ (keep (partial resolve-fn-var "pre-build-hook-fn")
+ (:pre-build-hooks config))))
+ (not-empty (:post-build-hooks config))
+ (update ::post-build-hooks concat
+ (doall
+ (keep (partial resolve-fn-var "post-build-hook-fn")
+ (:post-build-hooks config))))))
+
+ (defn alter-output-to [nm {:keys [output-to output-dir] :as opts}]
(cond
- (= x :main)
- (when-let [{:keys [uri]} (and main (fw-util/ns->location (symbol main)))]
- [uri])
- (symbol? x)
- (when-let [{:keys [uri]} (fw-util/ns->location x)]
- [uri])
- (= x :watch-dirs)
- watch-dirs
- :else [x]))
- build-inputs))))
-
-(defn config->inputs [{:keys [watch-dirs mode build-inputs ::build-once] :as config} options]
- (if (not-empty build-inputs)
- (expand-build-inputs config options)
- (if-let [inputs (and (not build-once)
- (= :none (:optimizations options :none))
- (not-empty watch-dirs))]
- inputs
- (let [source (when (:main options)
- (:uri (fw-util/ns->location (symbol (:main options)))))]
- (cond
- source [source]
- (not-empty watch-dirs) watch-dirs)))))
-
-(defn config-build-inputs [{:keys [options ::config] :as cfg}]
- (if-let [inputs (not-empty (config->inputs config options))]
- (update-in cfg [::config ::build-inputs] (comp vec distinct concat) inputs)
- cfg))
-
-(defn config-compile-is-build-once [{:keys [args] :as cfg}]
- (cond-> cfg
- (and (= (set (keys cfg)) #{:args :ns})
- (not (#{"-r" "--repl" "-s" "--serve"} (first args))))
- (assoc-in [::config ::build-once] true)))
-
-(defn update-config [cfg]
- (->> cfg
- config-compile-is-build-once
- config-figwheel-main-edn
- config-merge-current-build-conf
- config-ansi-color-output!
- config-set-log-level!
- config-log-syntax-error-style!
- config-repl-serve?
- config-main-ns
- config-main-source-path-on-classpath
-
- config-update-watch-dirs
- config-ensure-watch-dirs-on-classpath
- config-figwheel-mode?
- config-default-dirs
- config-default-asset-path
- config-default-aot-cache-false
- npm/config
- testing/plugin
- config-pre-post-hooks
- config-repl-connect
- config-cljs-devtools
- config-open-file-command
- #_config-eval-back
- config-watch-css
- config-finalize-repl-options
- config-extra-mains
- config-build-inputs
- config-clean
- config-warn-resource-directory-not-on-classpath))
-
-;; ----------------------------------------------------------------------------
-;; Main action
-;; ----------------------------------------------------------------------------
-
-(defn build [{:keys [watch-dirs mode ::build-once ::build-inputs] :as config}
- options cenv]
- (let [id (:id (::build *config*) "unknown")]
- (assert (not-empty build-inputs) "Should have at least one build input!")
- (build-cljs id (apply bapi/inputs build-inputs) options cenv)
- ;; are we watching?
- (when-let [paths (and (not build-once)
- (= :none (:optimizations options :none))
- (not-empty watch-dirs))]
- (watch-build id paths build-inputs options cenv (config->reload-config config)))))
-
-(defn log-server-start [repl-env]
- (let [host (get-in repl-env [:ring-server-options :host] "localhost")
- port (get-in repl-env [:ring-server-options :port] figwheel.repl/default-port)
- scheme (if (get-in repl-env [:ring-server-options :ssl?])
- "https" "http")]
- (log/info (str "Starting Server at " scheme "://" host ":" port ))))
-
-(defn start-file-logger []
- (when-let [log-fname (and (bound? #'*config*) (get-in *config* [::config :log-file]))]
- (log/info "Redirecting log ouput to file:" log-fname)
- (io/make-parents log-fname)
- (log/switch-to-file-handler! log-fname)))
-
-;; ------------------------------
-;; REPL
-;; ------------------------------
-
-(defn repl-api-docs []
- (let [dvars (filter (comp :cljs-repl-api meta) (vals (ns-publics 'figwheel.main)))]
- (string/join
- "\n"
- (map (fn [{:keys [ns name arglists doc]}]
- (str "--------------------------------------------------------------------------------\n"
- "(" ns "/" name
- (when-let [args (not-empty (first arglists))]
- (str " " (pr-str args)))
- ")\n " doc))
- (map meta dvars)))))
-
-#_(println (repl-api-docs))
-
-(defn bound-var? [sym]
- (when-let [v (resolve sym)]
- (thread-bound? v)))
-
-(defn in-nrepl? []
- (or
- (bound-var? 'nrepl.middleware.interruptible-eval/*msg*)
- (bound-var? 'clojure.tools.nrepl.middleware.interruptible-eval/*msg*)))
-
-(defn nrepl-repl [repl-env repl-options]
- (if-let [piggie-repl (or (and (bound-var? 'cider.piggieback/*cljs-repl-env*)
- (resolve 'cider.piggieback/cljs-repl))
- (and (bound-var? 'cemerick.piggieback/*cljs-repl-env*)
- (resolve 'cemerick.piggieback/cljs-repl)))]
- (apply piggie-repl repl-env (mapcat identity repl-options))
- (throw (ex-info "Failed to launch Figwheel CLJS REPL: nREPL connection found but unable to load piggieback.
+ output-to
+ (let [f (io/file output-to)
+ fname (string/replace (.getName f) #"\.js$" "")]
+ (assoc opts :output-to
+ (str (io/file (.getParent f) (str fname "-" nm ".js")))))
+ output-dir
+ (assoc opts :output-to (str (io/file output-dir (str "main-" nm ".js"))))
+ :else nil))
+
+ (defn merge-extra-key-with [m extra-m f k]
+ (if-let [value (get extra-m k)]
+ (let [act-key (keyword (string/replace (name k) #"^extra-" "")) ]
+ (update m act-key f value))
+ m))
+
+ (defn merge-extra-cljs-options
+ "Merges ClojureScript options that are collections if they are
+ prepended with :extra."
+ [opts extra-opts]
+ (let [coll-keys [:extra-foreign-libs
+ :extra-externs
+ :extra-preloads
+ :extra-closure-extra-annotations]
+ map-keys [:extra-modules
+ :extra-npm-deps
+ :extra-closure-defines
+ :extra-closure-warnings]]
+ (as-> (merge opts extra-opts) opts
+ (apply dissoc opts
+ (concat coll-keys map-keys
+ [:extra-warnings]))
+ (merge-extra-key-with opts extra-opts (fn [x y]
+ (merge (if (boolean? x) {} x) y)) :extra-warnings)
+ (reduce #(merge-extra-key-with %1 extra-opts (comp vec concat) %2) opts coll-keys)
+ (reduce #(merge-extra-key-with %1 extra-opts merge %2) opts map-keys))))
+
+ (defn extra-main-options [nm em-options options]
+ ;; need to remove modules so that we get a funcitioning independent main endpoint
+ (merge-extra-cljs-options
+ (dissoc (alter-output-to (name nm) options) :modules)
+ em-options))
+
+ (defn- compile-resource-helper [res opts]
+ (let [parts (-> res
+ (string/replace #"\.cljs$" ".js")
+ (string/split #"/"))]
+ (when-not (.exists (apply io/file (:output-dir opts) parts))
+ (cljs.closure/-compile (io/resource res)
+ (assoc opts
+ :output-file
+ (str (apply io/file parts)))))))
+
+ (defn extra-main-fn [nm em-options options]
+ ;; TODO modules??
+ (let [opts (extra-main-options nm em-options options)]
+ (fn [_]
+ (log/info (format "Outputting main file: %s" (:output-to opts "main.js")))
+ (let [switch-to-node? (and (= :nodejs (:target em-options)) (not= :nodejs (:target options)))
+ ;; fix asset-path for nodejs to output-dir if the original options are not nodejs
+ ;; and if the asset path isn't set in the extra-main options
+ opts (if (and switch-to-node? (not (:asset-path em-options)))