Search code examples
clojurescriptgoogle-closure-compilerdead-codeshadow-cljs

Clojurescript dead-code elimination apparently not working


I have a ClojureScript project with the following barebone frontend app (main being the entry point):

(ns shadowman.app
  (:require
   ;; [cljs-http.client :as http]
   ;; [reagent.core :as r]
   ))

(defn main 
  ""
  []

  (js/console.log "hi from browser"))

This compiles to an expected 95,2 kB. Once I uncomment [cljs-http.client :as http] the total jumps to 299,7 kB; including reagent further raises it to 457,4 kB. Since I'm not calling any functions from these namespaces, shouldn't the Google Closure compiler eliminate them as dead-code?

The relevant parts of my shadow-cljs.edn are as follows: (omitting only three other builds, which, I might mention, do use the libs that are commented out):

 {:source-paths [;; "env/prod"
                 "src/server" "test" "src/browser" "src/common" "src/plibs" "target/classes"] 
 :dependencies [[reagent "0.9.1"]
                [bidi "2.1.6"]
                [com.taoensso/timbre "4.10.0"]
                ;; [org.clojure/clojurescript "1.10.520"]
                [macchiato/hiccups "0.4.1"]
                [macchiato/core "0.2.16"]
                [macchiato/env "0.0.6"]
                [mount "0.1.16"]

                ;; [cljs-ajax "0.8.0"]
                [cljs-http "0.1.46"]
                [hickory "0.7.1"]
                ;; [markdown-to-hiccup "0.6.2"]
                ]


 ;; :dev-http {3001 "public"}
 :builds {
          :spa-prod
          {
           :target :browser
           :output-dir "public/js/compiled"
           :asset-path "/js/compiled"
           :modules {:app-comp {:init-fn shadowman.app/main}}
           :compiler-options
           {:optimizations :advanced
            }}
          }}

I get the numbers above by running shadow-cljs release spa-prod. Unless I'm mistaken about what to expect from dead-code elimination something is wrong with this picture. If so I'll be grateful for any ideas on how to investigate it.


Solution

  • You can generate a build report to find out what your final build includes.

    shadow-cljs and the Closure Compiler do not perform DCE for npm dependencies that were required by your build. Once they are included only basic DCE is done (ie. :simple) which is not capable of fully eliminating code. This is done because :advanced breaks too many npm dependencies. So in the case of reagent it'll end up including react and react-dom by default which won't be eliminated even if reagent is.

    This is not limited to npm dependencies however. Not all CLJS code or even Closure Library code can be fully eliminated. Some code patterns just prevent DCE from ever kicking in. One example would be any defmulti/defmethod which can't be removed.