2

Trim the fat: tips for keeping bundle size small ๐Ÿ‹๏ธ

 1 year ago
source link: https://blog.bryce.io/post/1086137
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Trim the fat: tips for keeping bundle size small ๐Ÿ‹๏ธ

Posted on May 15, 2022

It's easy to add a bunch of npm packages to a project. It's also just as easy to add so many that it takes ages for your bundle to to build, download and execute. In the real world this translates to bad user experience or worse: losing users entirely.

I had some spare time this weekend and did some refactoring of my personal site, getting rid of the packages I didn't need and got the project's bundle from this:

public/index.853702c4.js                        282.07 KB  1.49s
โ”œโ”€โ”€ /react-dom/cjs/react-dom.production.min.js  257.67 KB   48ms
โ”œโ”€โ”€ /popmotion/dist/popmotion.es.js              62.27 KB   16ms
โ”œโ”€โ”€ /popmotion-pose/dist/popmotion-pose.es.js    33.59 KB   66ms
โ”œโ”€โ”€ /stylefire/dist/stylefire.es.js                 25 KB    7ms
โ”œโ”€โ”€ /pose-core/dist/pose-core.es.js              21.74 KB    7ms
โ”œโ”€โ”€ /react-pose/dist/react-pose.es.js            21.67 KB   85ms
โ”œโ”€โ”€ /@emotion/stylis/dist/stylis.browser.esm.js  19.88 KB    4ms
โ”œโ”€โ”€ /@popmotion/popcorn/dist/popcorn.es.js       17.37 KB    7ms
โ”œโ”€โ”€ src/js/legos.js                              16.08 KB  318ms
โ””โ”€โ”€ /react-inlinesvg/esm/index.js                14.52 KB  207ms
โ””โ”€โ”€ + 79 more assets

To this: โœจ

public/index.1d2e670f.js                         53.59 KB  348ms
โ”œโ”€โ”€ /preact/dist/preact.module.js                31.56 KB   19ms
โ”œโ”€โ”€ /@ctrl/tinycolor/dist/module/index.js        19.45 KB    5ms
โ”œโ”€โ”€ /preact/compat/dist/compat.module.js         17.13 KB   18ms
โ”œโ”€โ”€ /react-meta-tags/lib/meta_tags.js             9.39 KB   64ms
โ”œโ”€โ”€ /@ctrl/tinycolor/dist/module/format-input.js  7.68 KB    8ms
โ”œโ”€โ”€ src/js/app.js                                 7.52 KB  139ms
โ”œโ”€โ”€ /preact/hooks/dist/hooks.module.js            7.25 KB   21ms
โ”œโ”€โ”€ /@ctrl/tinycolor/dist/module/conversion.js    6.44 KB   76ms
โ”œโ”€โ”€ /react-meta-tags/lib/utils.js                 5.88 KB    4ms
โ””โ”€โ”€ /react-meta-tags/lib/meta_tags_context.js     5.07 KB    3ms
โ””โ”€โ”€ + 25 more assets

1. Use smaller libraries โœ‚๏ธ

This one only applies to React-based projects, but the simplest way to cut out a sizeable chunk from your bundle is to swap React for Preact. There are guides for doing this process in a few steps, and with the preact-compat compatibility layer chances are you won't notice a difference (except for the significantly smaller bundle size!)

Beyond this, take a hard look at your dependencies and decide if you really need all the features they provide. Even small packages can stack up over time. Tools like bundlephobia are helpful for finding smaller alternatives to a library with a similar API.

But even then, you may still be left with a bunch of packages that you don't necessarily need.

2. Rewrite library-heavy code ๐Ÿ—‘

Bye emotion ๐Ÿ‘ฉโ€๐ŸŽค

After using bundlephobia to replace some libraries and make small changes so things still work I realized there wasn't a good reason why I needed some of them at all. Obviously this is only relevant on a case-by-case basis, but the smallest library to affect your bundle size is no library at all!

For example: I was using emotion to style components, but this was overkill for such a small project. There was no good reason why I needed to keep it, so I just scrapped it for old-fashioned CSS and let the bundler take care of it.

Some logic that relied on props to define a styled component's coloring needed to be rewritten but that was easy with CSS variables. This:

const Brick = styled.div`
  .child-class {
    background: ${props => darken(0.08, props.color)};
  }
`;

<Brick color="#fff">
  {children}
</Brick>

Which used both @emotion/styled and polished, was rewritten to use a much smaller color utility library:

const color = new TinyColor(props.color).darken(80).toString();

const cssVars = {
  '--color-1': color
};

<div style={cssVars} className="brick">
  {children}
</div>

Combined with some CSS:

.brick .child-class {
  background: var(--color-1);
}

And the resulting behavior is identical! Removing emotion shrank the bundle significantly. The next biggest one would be getting rid of the library that was added to handle animations.

Animation library go poof ๐Ÿ’จ

Framer Motion (previously react-pose) is a powerful animation library. But in my case, too powerful. I added it to play around with moving elements around but it was blowing up the project's bundle for just some simple entry animations.

I ended up replacing the motion component with a class to apply a CSS transform then a useEffect to remove the class after a delay. The new behavior very closely resembles what was before, and it's definitely close enough to rationalize removing such a massive dependency (almost 100kb alone!).

3. Always tree-shake ๐ŸŒณ

Tree shaking is not a new concept and all modern bundlers support it. The simplest example is instead of importing an entire massive library like lodash:

import lodash from 'lodash';

const number = lodash.random(0, 10); 

Use a tree-shakeable library that lets you only import what you want:

import random from 'lodash-es/random';

const number = random(0, 10); 

That way your bundler can ignore the unused portions of a library and only include what's needed. Not every library supports this however; it's wise to seek out the ones that do.

Analyze bundles frequently ๐Ÿ”

It's always good to keep track of these things over time so performance doesn't slide. Parcel, which I used for this project, has a helpful bundle analyzer (similar to the one for Webpack) that gives a nice visual overview of a project's bundle. This is especially helpful for identifying bundled dead code coming from packages that could be avoided with tree-shaking. There are also plenty of tools you can integrate with CI to enforce bundle size.

End result โšก๏ธ

This project now takes less than a second to build and the gzipped bundle size is down from ~150kb to only 18kb! The page loads significantly faster and the dev experience is much smoother too.

Hopefully these basic concepts are helpful, please share any tips I didn't cover!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK