Skip to content

Instantly share code, notes, and snippets.

@mk
Created July 5, 2018 18:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mk/45f22854c590139f42b6a12dbc3f1b54 to your computer and use it in GitHub Desktop.
Save mk/45f22854c590139f42b6a12dbc3f1b54 to your computer and use it in GitHub Desktop.
Figwheel selective build
(ns com.nextjournal.build-tools.figwheel.dependency
(:require [clojure.tools.namespace.track :as ctn.track]
[clojure.tools.namespace.dir :as ctn.dir]
[clojure.tools.namespace.file :as ctn.file]
[clojure.tools.namespace.dependency :as ctn.dep]
[clojure.tools.namespace.parse :as ctn.parse]
[clojure.tools.namespace.find :as ctn.find]
[clojure.java.io :as io]
[clojure.tools.reader :as reader])
(:import java.io.PushbackReader))
(defn macro-dependency-nss
"Given a coll of cljs paths, return all namespaces that are being required
with :require-macros. Returns seq of symbol."
[files]
(with-redefs [ctn.parse/ns-clause-heads #{:require-macros}]
(into #{}
(comp
(map io/reader)
(map #(PushbackReader. %))
(map ctn.parse/read-ns-decl)
(map ctn.parse/deps-from-ns-decl)
cat)
files)))
(defn macro-dependency-paths
"Given a coll of watch-paths (source directories) and a coll of cljs paths,
return all clj/cljc file paths that are required for macros, directly or
indirectly, and are part of watch-paths (i.e. only our \"own\" files.)"
[watch-paths cljs-paths]
(let [tracker (ctn.track/tracker)
scanned (ctn.dir/scan-dirs tracker watch-paths)
nss (macro-dependency-nss cljs-paths)
{graph ::ctn.track/deps
file->ns ::ctn.file/filemap} scanned
deps (and graph (ctn.dep/transitive-dependencies-set graph nss))
ns->file (into {} (map (comp vec reverse)) file->ns)]
(map str (keep ns->file (concat nss deps)))))
(defn dependent-paths
"Given a coll of watch-paths (clj or cljs source directories) and a main
namespace (symbol), return all paths to clj/cljs/cljc files that are
required."
[watch-paths main]
(let [tracker (ctn.track/tracker)
scanned (ctn.dir/scan-dirs tracker watch-paths {:platform ctn.find/cljs})
{graph ::ctn.track/deps
file->ns ::ctn.file/filemap} scanned
deps (ctn.dep/transitive-dependencies graph main)
ns->file (into {} (map (comp vec reverse)) file->ns)
cljs-paths (map str (keep ns->file (conj deps main)))]
(into #{} cat [(macro-dependency-paths watch-paths cljs-paths)
cljs-paths])))
(ns com.nextjournal.build-tools.figwheel
(:require [integrant.core :as integrant]
[figwheel-sidecar.system :as fw-sys]
[figwheel-sidecar.config :as fw-config]
[figwheel-sidecar.repl-api :as sidecar]
[figwheel-sidecar.components.cljs-autobuild :as fw-autobuild]
[com.nextjournal.build-tools.yarn :as yarn]
[com.nextjournal.build-tools.nodejs :as nodejs]
[com.nextjournal.build-tools.cljs-config :as config]
[com.nextjournal.build-tools.figwheel.dependency :as nj-fw-dep]
[suspendable.core :as suspendable]))
(def renderer-nodejs (atom nil))
(defn start-renderer []
(future
(Thread/sleep 1000) ;; allow files to flush to disk
(reset! renderer-nodejs (nodejs/run (get-in config/builds [:renderer :compiler :output-to])))))
(defn wrap-build-fn--start-renderer [build-fn]
(let [start-once (delay (start-renderer))]
(fn [build-state]
(build-fn build-state)
(when (= (get-in build-state [:build-config :id]) "renderer")
(deref start-once)))))
(defn build-selectively
"Only trigger a Figwheel build when the changed file is a dependency of :main."
[build-fn build-state]
(let [{:keys [build-config changed-files]} build-state]
(if (seq changed-files)
(let [watch-paths (get-in build-config [:watch-paths])
main (get-in build-config [:build-options :main])
dependents (nj-fw-dep/dependent-paths watch-paths main)]
(when (some #(contains? dependents %) changed-files)
(build-fn build-state)))
;; initial build, always run
(build-fn build-state))))
(defn wrap-build-fn--build-selectively [build-fn]
(fn [build-state]
(build-selectively build-fn build-state)))
(defmethod integrant/init-key ::figwheel [_ _]
(if-not (get-in sidecar/*repl-api-system* [:figwheel-system :system-running])
(sidecar/start-figwheel! (assoc (config/figwheel-config)
:cljs-build-fn
(-> fw-autobuild/figwheel-build
wrap-build-fn--start-renderer
wrap-build-fn--build-selectively)))))
(defmethod integrant/suspend-key! ::figwheel [k s]
#_(integrant/halt-key! k s))
(defmethod integrant/resume-key ::figwheel [k c _ _]
#_(integrant/init-key k c))
(defmethod integrant/halt-key! ::figwheel [_ _]
(sidecar/stop-figwheel!)
(if-let [stop-nodejs (some-> @renderer-nodejs :stop)]
(stop-nodejs)))
(defmethod integrant/init-key ::renderer-dependencies [_ _]
(yarn/install "journal/renderer" "journal/renderer/compiled/out/node_modules"))
(defmethod integrant/suspend-key! ::figwheel [_ _]
(alter-var-root #'sidecar/*repl-api-system* suspendable/suspend))
(defmethod integrant/resume-key ::figwheel [_ _ _ _]
(alter-var-root #'sidecar/*repl-api-system* #(suspendable/resume
(fw-sys/create-figwheel-system (config/figwheel-config))
%)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment