There is a need more complex build scripts for ClojureScript. It would be nice to have a minmal interface and method for composing these scripts. Somehting like:
(-> (watch-cljs ["src"])
(watch-sass [""])
(compile-sass)
(watch-css [""])
(mark-macro-affected-source build-options)
(compile-cljs build-options)
(cljs-browser-repl)
#_(fighweel-browser-connection)
#_(figwheel-browser-repl)
(ambly-repl))It is not lost on me that this is similar to what boot currently does. I have nothing against Boot, it would just be nice to have something that was neither tied to lein or boot that allows us to create and share building tools.
This system would not require buy-in just adhearance to a simple interface.
What should this interface look like?
Right now I'm leaning towards a ring-like functional interace where individual components can use core.async or other methods to handle the nastiness of this type of async build work.
Things to consider.
-
It would be nice to be able to create these composable flows and perhaps explicitely start and stop them.
-
I am also leaning toward a message based flow. Where the current component either recieves and operates on the message eventually forwarding it or passes it on untouched.
Tiny example:
(defn compile-cljs [source-paths build-options]
(fn [f]
(fn [signal]
(f (if (= [:cljs-changed] signal)
(do (cljs.build.api/build source-paths build-options)
[:cljs.build/cljs-compiled {:data ...}])
signal)))))I'm just putting this out there. This idea has been in my head for a year and I'd like to break figwheel down into parts that can be more easily reused.
@bhauman the problem I've had with this approach is that upstream tasks must enumerate the signals they generate a-priori. Then, downstream tasks must be familiar with the names that indicate certain things must happen. This is as opposed to a FileSet, a context, the interpretation of which is open.
For example, the FileSet-ized
cljstask could choose to do things when the FileSet it received contains.cljsfiles. In this way, upstream tasks don't need to know or care ifcljsis downstream - their responsibility is only to (conditionally) pass the FileSet to the next handler.The difference between an event indicating that certain work needs to be done, and a FileSet, a query over which might indicate the same, is subtle. However, the FileSet is better in my mind because it represents both an intent and all possible context required to act on that intent - without stating any particular intent. This fact is what saves users from the difficulties and inefficiencies inherent in managing both the order of events and the states they individually referred to at the time they were initiated.
Does that make sense? Datomic might be a good thing to compare to, with its idea of "the database as a value". With the FileSet, we get "the filesystem as a value" and all the immutable goodness along with it.
@danielsz I'm not sure all roads lead to the Ring/Boot/Bruce middleware style of composition. There are a lot of ways to arrange functions (monads, reducers, arrows, lenses, birds), most of which remain unexplored in this domain. What they'll all definitely need is a workable, immutable value to accept and return. If we can orient our tools around something as low level as an immutable associative type, all of these things, current and future, have the potential to compose functionally. Which would be unprecedented and awesome!