Shadow-cljs: [resolved] Bundle size reduction with shadow-cljs (tree-shaking, duplicate removal)

Created on 27 Nov 2018  ·  10Comments  ·  Source: thheller/shadow-cljs

Hi,

I've tried shadow-cljs and reagent interop with JS libraries (here: Material-UI) and it works like a charm. It's my first shot with shadow-cljs and I very thrilled. Thank you ❤️

However with production settings and minified builds, the resulting JS bundle isn't properly tree-shook. Vast packages are entirely included even if only a tiny part of them is required. As you can see in this minimal example repo, bundle size varies greatly from 792 KB to 5.74 MB after I import and use one single Material-UI icon.

It is a known issue and common solutions exist in JS-land, which deal with the library import in ES6. These solutions appear not to be (easily) applied to cljs code. Any general idea about how to tackle this issue in cljs-land?

This isn't only related to shadow-cljs project but I haven't figured out what's the best place to post it. If this isn't a proper place, would you be nice enough to hint me where to post? For the sake of completion I've created a thread in Reddit and I've posted about it in Slack.

Cheers.

Most helpful comment

(ns wireframe.app
  (:require [reagent.core :as reagent]
            ["@material-ui/core/styles/MuiThemeProvider" :default mui-ThemeProvider]
            ["@material-ui/core/styles" :refer [createMuiTheme]]
            ["@material-ui/core/colors" :as mui-colors]
            ["@material-ui/core/CssBaseline" :default mui-CssBaseline]
            ["@material-ui/core/Avatar" :default mui-avatar]
            ["@material-ui/icons/Android" :default AndroidIcon]))

(defn custom-theme
  []
  (createMuiTheme (clj->js #js {:palette #js {:type "light"
                                              :primary (.-blue mui-colors)
                                              :secondary (.-orange mui-colors)}
                                :typography #js {:useNextVariants true}})))

(defn init []
  (println "Hello World from shadow-cljs")
  (reagent/render
    [:> mui-ThemeProvider
     {:theme (custom-theme)}
     [:<>
      [:> mui-CssBaseline]
      [:h1 "This is my first, simple heading"]
      [:> mui-avatar
       [:> AndroidIcon {:color :secondary}]]]]
    (.getElementById js/document "app")))

Seems to be reasonable small. Could probably be reduced further but the entire @material-ui package is one gigantic mess and actually finding stuff is pretty hit or miss.

All 10 comments

First you should generate a Build Report. This will tell you exactly where each byte is coming from.

By requiring "@material-ui/icons" and "@material-ui/core" you will get every single thing in the entire library. The solutions for webpack and the other JS tools do not yet work for shadow-cljs so you need to do this manually.

Instead of ["@material-ui/core" :as mui] + mui/Avatar do ["@material-ui/core/Avatar" :as mui-Avatar] to only import the components you actually need. Same for the icons.

As far as I know this is already what I tried in https://github.com/piotr-yuxuan/shadow-cljs-wireframe/commit/b1e573838af0c2347d617da5dc57af39266ddaa5, to no avail. The require syntax is based on the Material UI doc.

Here is the build report as you suggested.

(ns wireframe.app
  (:require [reagent.core :as reagent]
            ["@material-ui/core/styles/MuiThemeProvider" :default mui-ThemeProvider]
            ["@material-ui/core/styles" :refer [createMuiTheme]]
            ["@material-ui/core/colors" :as mui-colors]
            ["@material-ui/core/CssBaseline" :default mui-CssBaseline]
            ["@material-ui/core/Avatar" :default mui-avatar]
            ["@material-ui/icons/Android" :default AndroidIcon]))

(defn custom-theme
  []
  (createMuiTheme (clj->js #js {:palette #js {:type "light"
                                              :primary (.-blue mui-colors)
                                              :secondary (.-orange mui-colors)}
                                :typography #js {:useNextVariants true}})))

(defn init []
  (println "Hello World from shadow-cljs")
  (reagent/render
    [:> mui-ThemeProvider
     {:theme (custom-theme)}
     [:<>
      [:> mui-CssBaseline]
      [:h1 "This is my first, simple heading"]
      [:> mui-avatar
       [:> AndroidIcon {:color :secondary}]]]]
    (.getElementById js/document "app")))

Seems to be reasonable small. Could probably be reduced further but the entire @material-ui package is one gigantic mess and actually finding stuff is pretty hit or miss.

Congratulations you nailed it! I wasn't aware of :default, I will have a look at it.

I'm looking forward to trying that! For the sake of completion I'll post the updated build report for those who might be interested.

I encounter the same problem, react-icon, I observed its dist/ directory, only a very large index.js, is there no way to optimize it?

@wangzhe1995 react-icon does not contain any large index.js so I assume you mean a different package. In general if the package only contains a single large index.js then there is no way to optimize it yes. Sometimes packages contain extra separate files in a lib folder or sometimes an es folder. Those files can usually the accessed directly but not all packages provide them.

React-icon https://github.com/react-icons/react-icons#readme

React-icon has a lot of icon font solutions, I am using fontawesome, I try to the "ls command" with node_modules directory and found that their fa/ directory has only the following files:

➜ ls node_modules/react-icons/fa

index.d.ts
index.esm.js 
index.js
package.json

That is react-icons not react-icon, they are separate packages.

In this case the icons all seem to be generated via a build step, so there aren't even any separate source files you could include. There is currently no way to "optimize" this package.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cark picture cark  ·  8Comments

lilactown picture lilactown  ·  6Comments

jiyinyiyong picture jiyinyiyong  ·  4Comments

alex-dixon picture alex-dixon  ·  10Comments

alex-dixon picture alex-dixon  ·  5Comments