On Measuring the Bundle Size of JavaScript Packages

Glitch
4 min readApr 25, 2024

--

When I began creating JavaScript libraries, some concerns started to arise. Usually we write libraries that offer a solution to a specific problem, a problem that we need to face multiple times in multiple projects, therefore instead of solving the same problem again and again, we abstract the solution inside an NPM package. Sometimes there are more than one way to solve a problem, and here we start to ask ourselves, what’s the best way to solve it? What’s the most optimal and efficient solution?

Library maintainers usually care a lot about optimization and sometimes we need external tools that can help us with this. One of the key points on this topic is the bundle size of a package. A smaller bundle size can improve the speed of our code and our websites (particularly in web development).

In this article, I will share my journey of discovering different ways to measure a package’s bundle size, how accurate existing tools are, and why I eventually decided to build my own tool, vite-size.

Definitions & Technics

Before we begin, I’d like to explain some of the techniques we can use to optimize our package’s bundle size and performance.

  • Dependencies & Peer Dependencies: Library dependencies (also called sub-dependencies) are always installed under the hood by npm when installing the library.
    Peer depedencies are also automatically installed by npm and pnpm but not by yarn.
    Optional peer dependencies are not installed by any npm package manager. If you need them, you must install them manually.
  • Package Exports: The package.json exports field allows you to define the entry points of a package. It is useful for modularizing your library. If you want to use optional peer dependencies, package exports can help you avoid issues across different environments where your users (developers using your library) work. This also helps prevent unused peer dependencies from being installed and included in the production build when they are not required.
  • Dynamic Imports & Code Splitting: Dynamic imports create code splitting by importing a module dynamically. This imported JavaScript is generated in a separate JS file, and in a production environment, this code is only sent to the browser when the import function is called. This can improve page load performance. However, optional peer dependencies are not convenient here because some bundlers (e.g. webpack with Next.js) will break if these peer dependencies are missing when dynamically importing them. Vite, on the other hand, handles this appropriately.

Summary: With package exports you can use optional peer dependencies, with dynamic imports it’s recommended to use dependencies (sub-deps). Therefore, package exports can help reduce the production bundle size of your code, while dynamic imports are not useful for this, but are helpful for improving page load performance.

Tools for bundle size measurement:

When you’re building a library, one of the main questions you’ll ask yourself is, ‘What is the bundle size of my library?’ There are various existing tools to measure this, but they all work differently and can be inaccurate depending on the use case.

  • Bundlephobia: This tool considers only the size of your main entry point. If you’re dynamically importing a module, it will ignore it completely, as well as any sub-dependencies. In reality, your library’s dependencies, peer dependencies, and split code are going to be bundled in the production build, which is why Bundlephobia can be quite misleading.
  • Bundle.js & size-limit: These are more accurate as they will take into account your library dependencies, but they will ignore dynamic imports. They also use esbuild which fails to tree shake correctly.

Vite-Size:

None of the tools I found offered me a clear picture of my package’s bundle size in the way I wanted, or gave me an understanding of how it was bundled for production. So I decided to create my own tool to meet my specific requirements. I wanted to be able to calculate the code that was split and have the flexibility to choose whether to include sub-dependencies and peer dependencies in the calculation.

I chose Vite’s JavaScript API for this task, it tree shakes properly because it uses rollup instead of esbuild for production build, Vite is also used for production apps ensuring its output would be realistic.

Vite-size essentially runs Vite’s build script agaisnt your library, you can see the code that’s split, the size of each file and also can choose which sub-dependencies to exclude from the bundle.

Conclution

I don’t think any of the tools mentioned here are objectively bad; they just serve different use cases. It’s possible that vite-size might be inconvenient for some people, and that’s okay. This was my journey through bundle size measurement, and this is where I find myself right now. Please leave your thoughts in the comments, I’d love to hear about your experiences on this topic. Thanks for reading!

--

--