Skip to content
+

Minimizing bundle size

Learn more about the tools you can leverage to reduce the bundle size.

Bundle size matters

Material UI's maintainers take bundle size very seriously. Size snapshots are taken on every commit for every package and critical parts of those packages. Combined with dangerJS we can inspect detailed bundle size changes on every Pull Request.

When and how to use tree-shaking?

Tree-shaking Material UI works out of the box in modern frameworks. Material UI exposes its full API on the top-level @mui imports. If you're using ES6 modules and a bundler that supports tree-shaking (webpack >= 2.x, parcel with a flag) you can safely use named imports and still get an optimized bundle size automatically:

import { Button, TextField } from '@mui/material';

Development environment

Development bundles can contain the full library which can lead to slower startup times. This is especially noticeable if you use named imports from @mui/icons-material, which can be up to six times slower than the default import. For example, between the following two imports, the first (named) can be significantly slower than the second (default):

// 🐌 Named
import { Delete } from '@mui/icons-material';
// 🚀 Default
import Delete from '@mui/icons-material/Delete';

If this is an issue for you, you have two options:

Option one: use path imports

You can use path imports to avoid pulling in unused modules. For instance, use:

// 🚀 Fast
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';

instead of top-level imports (without a Babel plugin):

import { Button, TextField } from '@mui/material';

This is the option we document in all the demos since it requires no configuration. It is encouraged for library authors that are extending the components. Head to Option 2 for the approach that yields the best DX and UX.

While importing directly in this manner doesn't use the exports in the main file of @mui/material, this file can serve as a handy reference as to which modules are public.

Be aware that we only support first and second-level imports. Anything deeper is considered private and can cause issues, such as module duplication in your bundle.

// ✅ OK
import { Add as AddIcon } from '@mui/icons-material';
import { Tabs } from '@mui/material';
//                         ^^^^^^^^ 1st or top-level

// ✅ OK
import AddIcon from '@mui/icons-material/Add';
import Tabs from '@mui/material/Tabs';
//                              ^^^^ 2nd level

// ❌ NOT OK
import TabIndicator from '@mui/material/Tabs/TabIndicator';
//                                           ^^^^^^^^^^^^ 3rd level

If you're using ESLint you can catch problematic imports with the no-restricted-imports rule. The following .eslintrc configuration will highlight problematic imports from @mui packages:

{
  "rules": {
    "no-restricted-imports": [
      "error",
      {
        "patterns": ["@mui/*/*/*"]
      }
    ]
  }
}

Option two: use a Babel plugin

This option provides the best user experience and developer experience, except if you're using Next.js 13.5 or greater, where this optimization is automatically applied via the optimizePackageImports option in Next.js. In that case, using a Babel plugin is unnecessary.

  • UX: The Babel plugin enables top-level tree-shaking even if your bundler doesn't support it.
  • DX: The Babel plugin makes startup time in dev mode as fast as Option 1.
  • DX: This syntax reduces the duplication of code, requiring only a single import for multiple modules. Overall, the code is easier to read, and you are less likely to make a mistake when importing a new module.
import { Button, TextField } from '@mui/material';

However, you need to apply the following steps correctly.

1. Configure Babel

Use the babel-plugin-import with the following configuration:

yarn add -D babel-plugin-import

Create a .babelrc.js file in the root directory of your project:

const plugins = [
  [
    'babel-plugin-import',
    {
      libraryName: '@mui/material',
      libraryDirectory: '',
      camel2DashComponentName: false,
    },
    'core',
  ],
  [
    'babel-plugin-import',
    {
      libraryName: '@mui/icons-material',
      libraryDirectory: '',
      camel2DashComponentName: false,
    },
    'icons',
  ],
];

module.exports = { plugins };

Note that Material UI supports tree shaking out of the box when importing from specific paths (e.g. @mui/material/Button), so this configuration is optional and primarily useful if you want to enforce modular imports.

Vite doesn’t require extra Babel configuration by default because it uses esbuild for fast bundling and minification. However, if you need to customize Babel (for example, to use babel-plugin-import), here’s a way to do it:

Install Dependencies

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-plugin-transform-imports

Create a .babelrc file in the root directory:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": [
    [
      "babel-plugin-transform-imports",
      {
        "@mui/material": {
          "transform": "@mui/material/${member}",
          "preventFullImport": true
        },
        "@mui/icons-material": {
          "transform": "@mui/icons-material/${member}",
          "preventFullImport": true
        }
      }
    ]
  ]
}

Update your vite.config.js to use the Babel plugin:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import babel from 'vite-plugin-babel';

export default defineConfig({
  plugins: [
    react(),
    babel()
  ]
});

Enjoy significantly faster start times.

2. Convert all your imports

Finally, you can convert your existing codebase to this option with this top-level-imports codemod. It will perform the following diffs:

-import Button from '@mui/material/Button';
-import TextField from '@mui/material/TextField';
+import { Button, TextField } from '@mui/material';

Available bundles

Default bundle

The packages published on npm are transpiled with Babel, optimized for performance with the supported platforms.

A modern bundle is also available.

How to use custom bundles?

A great way to use these bundles is to configure bundler export conditions, for example with webpack's resolve.conditionNames or vite's resolve.conditions:

// webpack.config.js
{
  resolve: {
    conditionNames: ['mui-modern', '...'],
  }
}

// vite.config.js
{
  resolve: {
    conditions: ['mui-modern', 'module', 'browser', 'development|production']
  }
}