thheller / shadow-cljs

ClojureScript compilation made easy
https://github.com/thheller/shadow-cljs
Eclipse Public License 1.0
2.27k stars 180 forks source link

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

Closed piotr-yuxuan closed 5 years ago

piotr-yuxuan commented 5 years ago

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.

thheller commented 5 years ago

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.

piotr-yuxuan commented 5 years ago

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.

piotr-yuxuan commented 5 years ago

Here is the build report as you suggested.

thheller commented 5 years ago
(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.

piotr-yuxuan commented 5 years ago

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.

piotr-yuxuan commented 5 years ago

See the translation table here: https://shadow-cljs.github.io/docs/UsersGuide.html#_using_npm_packages

ericzwong commented 5 years ago

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?

thheller commented 5 years ago

@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.

ericzwong commented 5 years ago

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
thheller commented 5 years ago

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.