Today we will talk about the Moby Dick of ES Module bundlers. Tree shaking is a smart JavaScript optimization that prunes unused code from your final bundle. It's not just about tidiness; it's a key performance factor, shrinking file sizes and making our applications load significantly faster.
Build as a single file
While modern bundlers aim to provide tree shaking for libraries using ES Modules (identified via module
or exports
in package.json
), the reality is often more nuanced. Achieving optimal results depends heavily on specific configurations. To illustrate, consider the recommended Rollup configuration recommended by Vite devs when operating in library mode:
import {resolve} from 'path';
import {defineConfig} from 'vite';
export default defineConfig({
//... other config
build: {
sourcemap: true,
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLibraryName',
fileName: 'index'
},
rollupOptions: {
// Ensure dependencies aren't bundled into the library
external: external(),
output: {
// Define global variable names
globals: {
vue: 'Vue', // Example for Vue
react: 'React' // Example for React
}
}
}
}
});
Package file configuration.
{
"type": "module",
"files": [
"dist"
],
"main": "./dist/index.umd.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.umd.cjs"
}
}
}
This example provides us with perfectly capable ES module and UMD builds.
Our library is built in a following way, src/index.ts
works as a barrel file containing re-exports of our library modules.
export {ComponentA} from './ComponentA';
export {ComponentB} from './ComponentB';
// ...
A significant drawback of Rollup's standard ES Module bundling strategy is its negative impact on tree shaking in bundlers like Webpack and Next.js. By compiling all module code into a single ./dist/index.js
file (which is also designated in the module
property of the package.json), even a small import can result in the entire library and its dependencies being included in your application.
While this setup is compatible with Vite and Rollup, Next.js experimental optimizePackageImports
feature, which I tested unsuccessfully on version 15.1.6
, doesn't seem to resolve this issue.
Build as multiple files
Fortunately, Rollup offers a solution to this tree shaking challenge. By enabling the preserveModules
setting, we can maintain the original module structure, significantly improving tree shaking capabilities. This configuration results in the library being built with one file per module instead of a single chunk.
Since this approach is incompatible with the default UMD (Universal Module Definition) build, we've updated the formats
setting to ['es', 'cjs']
, replacing UMD build with CommonJS.
To manage output file names, we've implemented a fileName
function. This ensures the main entry point is output as dist/index.js
for ES modules and dist/index.cjs
for CommonJS, while other entries retain their original path with the appropriate extension.
import {defineConfig} from 'vite';
export default defineConfig(() => ({
// ...
build: {
sourcemap: true,
lib: {
// ...
fileName: (format, entryName) => {
if (entryName === 'src/lib/index') {
return `index.${format === 'es' ? 'js' : 'cjs'}`;
}
return `${entryName}.${format === 'es' ? 'js' : 'cjs'}`;
},
formats: ['es', 'cjs'],
},
rollupOptions: {
// ...
output: {
// ...
preserveModules: true,
},
},
},
}));
Bundle size impact
I made a comparison using two different versions of Koval UI library, the first had a single file bundle and the second was split. The total size of the library is approximately 75 kb gzipped. Two small modules were used in the Next.js project.
Here are the results of the next build
command.
Build | First load | Server code | Client code |
---|---|---|---|
Single file | 184 kb | 62.4 kb | 37 kb |
Split by module | 124 kb | 12.6 kb | 10 kb |
Use library template
You can use this set up for your projects by cloning the React library template repository. Many other useful features are included.
Top comments (0)