Acronis UIKit
Guides

Tree-Shaking & Performance

How tree-shaking works in @acronis-platform/ui-react and best practices for optimal bundle size.

Tree-Shaking & Performance

Overview

@acronis-platform/ui-react is built to be tree-shakeable, so your production bundle includes only the components you actually import.

Tree-Shaking Enabled

The library is built with preserveModules: true in its Vite library config (packages/ui-react/vite.lib.config.ts), which preserves the original module structure. This lets modern bundlers (Vite, Webpack 5+, Rollup) drop the components you don't use.

What This Means

// You import only what you need
import { Button } from '@acronis-platform/ui-react';

// Your production bundle includes:
// the Button component + its dependencies (cn utility, cva, the Base UI
// primitives it wraps)
// NOT included: Switch, InputText, Tag, or any other unused component

Build Output Structure

The library is built with preserved module structure — each component compiles to its own file under dist/:

dist/
├── index.js                          (re-exports all components)
├── react.js                          (the ./react subpath entry)
├── components/
│   └── ui/
│       ├── button/button.js          (individual component)
│       ├── switch/switch.js          (individual component)
│       ├── input-text/input-text.js
│       ├── tag/tag.js
│       └── ...                        (24 component modules, each separate)
├── lib/
│   └── utils.js                      (cn class-merge helper)
└── ui-react.css                      (compiled styles, imported separately)

How Tree-Shaking Works

1. ES Module Format

The library is built as ES modules (formats: ['es']), which is required for tree-shaking:

// Tree-shakeable (ES modules)
export { Button } from './components/ui/button';
export { Switch } from './components/ui/switch';

// Not tree-shakeable (CommonJS)
module.exports = { Button, Switch };

ui-react ships ESM only — there is no CommonJS entry.

2. Preserved Module Structure

Each component is a separate file, so bundlers can analyze dependencies precisely:

// When you import Button
import { Button } from '@acronis-platform/ui-react';

// Bundler traces dependencies:
// index.js -> components/ui/button/button.js -> lib/utils.js
// Only these files are included in your bundle

3. Peer Dependencies Stay External

react, react-dom, @base-ui/react, and @acronis-platform/icons-react are marked external in the build, so they are not inlined into the bundle. Consumers install them once, and bundlers de-duplicate them across your app and the library.

Bundler Compatibility

Vite has excellent tree-shaking by default:

// vite.config.ts
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Optional: separate vendor chunk
          'ui-components': ['@acronis-platform/ui-react'],
        },
      },
    },
  },
};

Webpack 5+

Webpack 5 supports tree-shaking with production mode:

// webpack.config.js
module.exports = {
  mode: 'production', // Enables tree-shaking
  optimization: {
    usedExports: true,
    sideEffects: true,
  },
};

Next.js

Next.js automatically tree-shakes in production:

// No configuration needed
import { Button } from '@acronis-platform/ui-react';

Rollup

Rollup has built-in tree-shaking:

// rollup.config.js
export default {
  output: {
    format: 'es',
  },
};

Optimization Tips

1. Import from the Main Entry

Always import from the package entry:

// Recommended — tree-shakeable
import { Button, Switch } from '@acronis-platform/ui-react';

// Avoid — reaches into build internals, which may change
import { Button } from '@acronis-platform/ui-react/dist/components/ui/button/button';

2. Use Named Imports

Use named imports, not namespace imports:

// Good — tree-shakeable
import { Button, Switch } from '@acronis-platform/ui-react';

// Avoid — may pull in the entire module
import * as UI from '@acronis-platform/ui-react';

3. Import Styles Once

CSS is not tree-shakeable. ui-react ships a single compiled stylesheet; import it once at your app entry:

import '@acronis-platform/ui-react/styles';

4. Lazy Load Heavy Composites

For larger composite components you don't need on first paint (e.g. the sidebars or Resizable), consider lazy loading:

import { lazy } from 'react';

const SidebarSecondary = lazy(() =>
  import('@acronis-platform/ui-react').then((mod) => ({
    default: mod.SidebarSecondary,
  }))
);

Bundle Analysis

Analyze Your Bundle

Use bundle analyzers to verify tree-shaking:

Vite:

npm install -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default {
  plugins: [visualizer()],
};

Webpack:

npm install -D webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [new BundleAnalyzerPlugin()],
};

Expected Results

After analysis, you should see:

  • Only the components you import in your bundle
  • Peer dependencies (React, @base-ui/react) shared, not duplicated
  • Unused components excluded

Performance Best Practices

1. Code Splitting by Route

Split components by route for optimal loading:

// Route-based splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

2. Dynamic Imports

Load components on demand:

// Load a component when needed
const loadResizable = async () => {
  const { Resizable } = await import('@acronis-platform/ui-react');
  return Resizable;
};

3. Preload Critical Components

Import the components needed for first render up front:

// Statically imported, available immediately
import { Button, InputText, Switch } from '@acronis-platform/ui-react';

Common Issues

Issue: Bundle includes all components

Cause: Using import * or importing from the wrong path.

Solution:

// Wrong
import * as UI from '@acronis-platform/ui-react';

// Correct
import { Button, Switch } from '@acronis-platform/ui-react';

Issue: Tree-shaking not working

Cause: Development mode or a misconfigured bundler.

Solution:

  • Ensure production mode: NODE_ENV=production
  • Check your bundler configuration
  • Confirm you import from the package entry, not dist/ internals

Issue: Large bundle size

Cause: Importing heavy dependencies.

Solution:

  • Use a bundle analyzer to identify large modules
  • Lazy load heavy composites
  • Consider code splitting

Summary

@acronis-platform/ui-react is built for production:

  • Tree-shakeable — preserved module structure lets bundlers drop unused components
  • ESM-only — modern, statically analyzable output
  • External peers — React and @base-ui/react are not bundled in
  • Modern bundlers — works with Vite, Webpack 5+, Next.js, and Rollup

For most apps, importing a handful of components results in a small footprint relative to the full library. Verify the exact savings for your app with a bundle analyzer.

Edit on GitHub

On this page