emezeske / lein-cljsbuild

Leiningen plugin to make ClojureScript development easy.
Other
1.1k stars 151 forks source link

Set default compiler options for all builds via profiles #444

Open devth opened 8 years ago

devth commented 8 years ago

In other words, set this from a profile.

Compilation Profiles describes setting compiler options with profiles, but if your :cljsbuild contains more than one build, these are not correctly merged in. Instead, it tricks cljsbuild into thinking config is in the wrong format, and outputs a bogus "fixed" format:

WARNING: your :cljsbuild configuration is in a deprecated format.  It has been
automatically converted it to the new format, which will be printed below.
It is recommended that you update your :cljsbuild configuration ASAP.
--------------------------------------------------------------------------------
:cljsbuild
{:builds
 [{:builds
  …

Using compilation profiles is only possible if you fully specify every build in every profile, like this project.clj which is super verbose, and unDRY.

Instead, I want to specify settings like:

mneise commented 8 years ago

Would something like this work in your case?

(defproject my-project "0.1.0-SNAPSHOT"
  :description "FIXME: write this!"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.8.51"]]
  :source-paths ["src"]
  :clean-targets ["out"]
  :target-path "target"
  :plugins [[lein-cljsbuild "1.1.3"]]
  :cljsbuild {:builds {:my-project {:source-paths ["src"]
                                    :compiler {:my-project my-project.core
                                               :output-to "out/my_project.js"
                                               :output-dir "out"
                                               :source-map "out/map.js.map"
                                               :pretty-print true}}}}
  :profiles {:dev {:cljsbuild {:builds {:my-project {:compiler {:verbose true}}}}}
             :release {:cljsbuild {:builds {:my-project {:compiler {:optimizations :advanced}}}}}})

If not, do you have a certain config structure in mind that would be helpful in your case? It would be great if you could give a minimal example of a current config of yours and how you would like it to be instead.

devth commented 8 years ago

Your example is essentially what I'm doing now, but results in a fair amount of duplication when dealing with multiple builds. For example, I have several foreign libs that need to be duplicated across every build.

e.g. with two separate builds:

  :cljsbuild {:builds {:build1 {:source-paths ["src/build1"]
                                :compiler {:my-project build1.core
                                           :output-to "build1/out.js"
                                           :output-dir "build1/out"
                                           :source-map "build1/out/map.js.map"
                                           :pretty-print true}}
                       :build2 {:source-paths ["src/build2"]
                                :compiler {:my-project build2.core
                                           :output-to "build2/out.js"
                                           :output-dir "build2/out"
                                           :source-map "build2/out/map.js.map"
                                           :pretty-print true}}}}

Now if I want to add the same set of foreign libs to all my builds I need to set them individually, either inline or using a profile – in either case there will still be a separate :foreign-libs for each build.

What I'd like to do is set cljsbuild configuration globally for all builds using a profile, then mix-in that profile for dev and release (in this example), e.g.:

  :profiles {:cljs-shared
             ;; common configuration shared across all builds
             {:cljsbuild {:compiler {:foreign-libs [{:file "file1.js" :provides ["Lib1"] :language-in :ecmascript5}
                                                    {:file "file2.js" :provides ["Lib2"] :language-in :ecmascript5}
                                                    {:file "file3.js" :provides ["Lib3"] :language-in :ecmascript5}]}}}
             ;; dev only configuration for all builds
             :dev [:cljs-shared
                   {:cljsbuild {:compiler {:verbose true}}}]
             ;; release only config for all builds
             :release [:cljs-shared
                       {:cljsbuild {:compiler {:optimizations :advanced}}}]})

So I think what would need to happen is: if a :cljsbuild map doesn't specify {:build {:somebuild {...}}, then it should apply the configuration across all build configurations.

Other ideas for removing duplication of common configuration across build1 and build2?

mneise commented 8 years ago

Your example should work if you specify the same build id across the different profiles, e.g.:

:profiles {:cljs-shared
           ;; common configuration shared across all builds
           {:cljsbuild {:builds {:app {:compiler {:foreign-libs [{:file "file1.js" :provides ["Lib1"] :language-in :ecmascript5}
                                                                 {:file "file2.js" :provides ["Lib2"] :language-in :ecmascript5}
                                                                 {:file "file3.js" :provides ["Lib3"] :language-in :ecmascript5}]}}}}}
           ;; dev only configuration for all builds
           :dev [:cljs-shared
                 {:cljsbuild {:builds {:app {:compiler {:verbose true}}}}}]
           ;; release only config for all builds
           :release [:cljs-shared
                     {:cljsbuild {:builds {:app {:compiler {:optimizations :advanced}}}}}]}

When using profiles the merging of the different configurations is done by leiningen as described here. You can take a look at the final project configuration that is handed to cljsbuild by using the pprint plugin, e.g. lein with-profile dev pprint.

devth commented 8 years ago

Yep, that's essentially where I am now. The problem is the amount of duplication required across build config. So my idea / feature request was for cljsbuild to apply config across all configs if the build id was unspecified. There are other ways to achieve this too, e.g. if the build-id key is a vector, apply it to multiple configs, which has the benefit of more precision than my previous suggestion:

  :profiles {:cljs-shared
             ;; common configuration shared across all builds
             {:cljsbuild {:builds {[:build1 :build2]
                                   {:compiler {:foreign-libs [{:file "file1.js" :provides ["Lib1"] :language-in :ecmascript5}
                                                              {:file "file2.js" :provides ["Lib2"] :language-in :ecmascript5}
                                                              {:file "file3.js" :provides ["Lib3"] :language-in :ecmascript5}]}}}}}
             ;; dev only configuration for all builds
             :dev [:cljs-shared
                   {:cljsbuild {:builds {[:build1 :build2] {:compiler {:verbose true}}}}}]
             ;; release only config for all builds
             :release [:cljs-shared
                       {:cljsbuild {:builds {[:build1 :build2]{:compiler {:optimizations :advanced}}}}}]}

  :cljsbuild {:builds {:build1 {:source-paths ["src/build1"]
                                :compiler {:my-project build1.core
                                           :output-to "build1/out.js"
                                           :output-dir "build1/out"
                                           :source-map "build1/out/map.js.map"
                                           :pretty-print true}}
                       :build2 {:source-paths ["src/build2"]
                                :compiler {:my-project build2.core
                                           :output-to "build2/out.js"
                                           :output-dir "build2/out"
                                           :source-map "build2/out/map.js.map"
                                           :pretty-print true}}}}

(Thanks for the pprint plugin tip!)

mneise commented 8 years ago

I would need to think a bit about this, but using an approach that is similar to composing leiningen profiles might be more intuitive, e.g. using a vector instead of a map as the value for a specific build. For example:

:cljsbuild {:builds {:app {:source-paths ["src"]
                           :compiler {:main app.core
                                      :output-to "out/app.js"
                                      :output-dir "out"
                                      :source-map "out/map.js.map"
                                      :pretty-print true}}
                     :dev [:app {:compiler {:verbose true}}]
                     :release [:app {:compiler {:optimization :advanced}}]}}

So for this example, if you would run lein cljsbuild once dev cljsbuild could merge the config for the :app build and {:compiler {:verbose true}} into one build config.

devth commented 8 years ago

Sounds useful!

I want to make sure it still works with lein profiles, as that's the standard for specifying differences between envs:

  1. cljsbuild merging would be useful for sharing common configuration across different src builds (i.e. build1 and build2 in my example)
  2. lein profile merging, in combination with cljsbuild support, should be used for specifying differences in environments

In other words, to build a release it should look more like:

lein with-profile release compile

And for dev:

lein compile

Not sure the cljsbuild merging would support multiple builds, e.g.:

:profiles {:cljs-common
           {:cljsbuild {:builds {:shared-cljs
                                 {:compiler
                                  {:foreign-libs [{:file "file1.js" :provides ["Lib1"] :language-in :ecmascript5}
                                                  {:file "file2.js" :provides ["Lib2"] :language-in :ecmascript5}
                                                  {:file "file3.js" :provides ["Lib3"] :language-in :ecmascript5}]}}

                                 :build1 [:shared-cljs {:source-paths ["src/build1"]}]

                                 :build2 [:shared-cljs {:source-paths ["src/build2"]}]}}}

           :dev
           [:cljs-common
                                              ;; not sure this makes sense,
                                              ;; merging multiple builds into
                                              ;; one?
            {:cljsbuild {:builds {:dev-build [:build1 :build2 {:compiler {:verbose true}}]}}}]

           :release
           [:cljs-common
                                                  ;; same here
            {:cljsbuild {:builds {:release-build [:build1 :build2 {:compiler {:optimizations :advanced}}]}}}]}
ip1981 commented 6 years ago

I guess we can use ... Clojure :)

(def optimizing-compiler
  {:compiler {:optimizations :advanced
              :elide-asserts true
              :pretty-print false}})

(defproject com.capital-match.lending/ui "0"
  :description "Capital Match Web application"
  :url "https://www.capital-match.com"
  :min-lein-version "2.8.1"

  :dependencies [
                 [bidi "2.1.3"]
                 [cljsjs/create-react-class "15.6.3-0"]
                 [cljsjs/csv "1.1.1-0"]
                 [cljsjs/react "16.0.0-0"]
                 [cljsjs/react-dom "16.0.0-0"]
                 [com.andrewmcveigh/cljs-time "0.5.2"]
                 [org.clojure/clojure "1.9.0"]
                 [org.clojure/clojurescript "1.10.339"]
                 [org.clojure/core.async "0.4.474"]
                 [org.omcljs/om "1.0.0-beta4"]
                 [prismatic/dommy "1.1.0"]
                 ]
  :plugins [
            [lein-aggravate "0.1.2-SNAPSHOT"]
            [lein-cljsbuild "1.1.7"]
            ]

  :profiles {:release
             {:cljsbuild
              {:builds
               {:user ~optimizing-compiler
                :admin ~optimizing-compiler
               }}}}
;;............