I don’t know if there are enough articles on how to optimize the bundle’s size, but in almost all of them I’ve seen I find something else to add to the pipeline and that is why I want to start with the conclusion I reached.
A bundle is a set of assets grouped together because of their interdependence deduced from the source code (imports), solving the problem of integrating the dependencies of a project. Having a range of assets different in type and functionality, some included explicitly in the code and others implicitly by tools and libraries that help the development process (webpack, react, dev mode, internal imports) it is evident that the complexity will be proportional to the size of the bundle.
At the end, the final product is not only the content, it also includes the interaction experience and this begins when the user decides to click the link and awaits for the response. Although the bundle simplified the complexity of the dependencies, it is necessary to acknowledge some assumptions that simplified the development process and change them to improve the experience, such as all the code will be present at the same time (splitting), or must be loaded sequentially (parallelism) and finally the source code must be pretty and understandable (minify).
In summary, i found 4 groups of actions needed to reduce a bundle’s size
- delete: remove redundant or sub utilized code
- extract: unbundle assets favoring paralelization
- divide: group only the code needed
- optimize: review each asset, according to its type
Problem
I made a simple SPA to show some projects and then hosted it on github, I needed to add some dynamic components to the page so I used ReactJS to implement it with portals. My point is why something so simple was so big
app.js 586.6KB 2.js 377.3KB 3.js 45.7KB -------------- 1,009.6KB polyfill.js 93.1KB
These are the page’s screenshots and it requires almost 1MB of code without counting the html, css and images. My hypothesis was “the bundle is packing code that does not use” so I started to investigate a little and reduce each part involved (although in this post I’m going to focus on the javascript code)
The final result after reviewing the bundle was
app.js 481.9KB
--------------
481.9KB
polyfill.js 92.9KB
which represents a 48% of the original size and although the hypothesis was correct in some way it just describes only a part of the problem, it was more profound than a tree shaking
Analysis and Tools
To start I needed to see the bundle’s composition
webpack
It generates a file with the dependency graph and the assets list, quite big and in json format, has a lot of information but it’s not so manageable
webpack --profile --json > stats.json
webpack-bundle-analyzer
it analyzes the dependency graph generated by webpack, good for visualizing the composition but not detailed enough
# package
npm install webpack-bundle-analyzer -g
# analyze, generate report
webpack-bundle-analyzer stats.json
source-map-explorer
very similar to the previous one, not so colorful, but with a better level of detail
source-map-explorer script.js
bundle-stats
provides the most complete list of assets included in the bundle, it is more like a visualization tool for stats.json
Solution
now, with the bundle’s composition information
1. react-dom.development.js
changed the mode to production in webpack.config.js
mode: 'production'
all - 2.82MB app - 2.58MB polyfill - 248.1KB
2. moment.js -> date-fns
The library moment.js, despite of being quite complete is quite big. I replaced it with date-fns.
all - 2.32MB app - 2.08MB polyfill - 248.1KB
3. clean unused code
after a quick review of dead code in some components i had to remove some unused imports left
all - 2.27MB app - 2.02MB polyfill - 248.1KB
4. helmet -> document.title
i used helmet just for the page title so i changed it for document.title = “title”
all - 2.22MB app - 1.98MB polyfill - 248.1KB
5. axios -> fetch
I used axios just for server requests and that could be easily replaced by fetch
all - 2.03MB app - 1.79MB polyfill - 248.1KB
6. lint fixes
all - 2.03MB app - 1.79MB polyfill - 248.1KB
7. removing javascript-time-ago
I am working on a framework that I have been building over time and at some point in time I used this functionality which can be replaced by date-fns too
all - 1.62MB app - 1.38MB polyfill - 248.1KB
8. material-ui
it would be a highly cost refactor, I just upgrade the package to the latest version hoping that the devs behind the library were also doing their thing in this matter
9. react -> preact
changing react for preact? it sounds good although the migration process was a little buggy
all - 1.51MB app - 1.27MB polyfill - 248.1KB
10. remove hot loader and development dependencies
11. extracting assets: css, fonts, images
webpack - mini-css-extract-plugin
all - 1.43MB app - 1.19MB polyfill - 248.1KB
12. dynamic loading
const { FixedSizeList } from 'react-window'; const { FixedSizeList } = Loadable({ loader: () => import('react-window'), loading: Loading, }); const FixedSizeList = Loadable({ loader: () => import('react-window/FixedSizeList'), loading: Loading, });
13. targeting
devtool: false, target: "web", externals: { React: 'react' }
14. minimize
Terser
Summarizing, I categorized the previous 14 points as follows
delete
development support
- react-dom.development.js
- removing hot loader
refactoring
- moment.js
- helmet
- axios
- javascript-time-ago
- material-ui
- react
code review
- unused code
- linting
extract
css, images, fonts
divide
Dynamic loading
- react-window
- optimizations chunks
optimize
targeting and minimizing
This far the list for today, I am aware that it can be more extensive. I would like to know which other points you recommend taking into account.
References
- https://lemoncode.net/lemoncode-blog/2018/5/7/webpack-poniendo-a-dieta-tus-bundles-ii
- https://dev.to/sheddy_nathan/possible-ways-to-reduce-your-webpack-bundle-size-js-secrets-550
- https://medium.com/@poshakajay/heres-how-i-reduced-my-bundle-size-by-90-2e14c8a11c11
- https://material-ui.com/guides/minimizing-bundle-size/
- https://goenning.net/2018/11/26/how-we-reduced-initial-jscss-size/
- https://www.intercom.com/blog/reducing-intercom-messenger-bundle-size/