Excluding libraries from your Vite bundle isn’t just about saving a few kilobytes; it’s a strategic move that can dramatically improve build times and runtime performance by leveraging pre-existing, globally available JavaScript environments.

Imagine you’re building a web application that relies on a heavy-duty library like React or a charting library. You want to make sure your users don’t have to download these massive dependencies every single time they visit your site, especially if they’re already available in the browser’s environment (e.g., via a CDN) or if you plan to load them separately. This is where Vite’s build.rollupOptions.external comes into play.

Let’s see this in action. Suppose you have a simple Vite project and you want to exclude react and react-dom from your bundle. Your vite.config.js would look something like this:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        // This is crucial for UMD/IIFE builds where you might want
        // to manually manage globals.
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
});

When you run vite build, Vite (under the hood, using Rollup) will process this configuration. Instead of bundling react and react-dom into your dist folder, it will simply generate import statements that expect these libraries to be available in the global scope.

The core problem externals solves is dependency bloat. Without it, every library you import gets bundled into your application’s JavaScript files. This leads to:

  • Larger File Sizes: Users download more code than necessary.
  • Slower Initial Load Times: More JavaScript to parse and execute.
  • Wasted Bandwidth: Repeated downloads of the same libraries across different sites.

By marking a module as external, you’re telling Vite: "Don’t worry about bundling this. Assume it will be provided by the environment where this code runs." This is particularly useful for:

  • Libraries already available in the browser: Like popular frameworks (React, Vue, Angular) or utility libraries (Lodash) that might be loaded via a <script> tag from a CDN.
  • Server-side rendering (SSR): When building for SSR, you often don’t want to bundle code that will be available on the server.
  • Browser extensions or specific environments: Where certain APIs or libraries are already globally injected.

Let’s break down the configuration:

  • build.rollupOptions: This is where you can pass specific configurations directly to Rollup, Vite’s build tool.
  • external: This is an array of strings. Each string is a module ID that should be treated as an external dependency. These are typically the names you use in your import statements (e.g., 'react', 'lodash', '@mui/material').
  • output.globals: This is an object that maps external module IDs to their corresponding global variable names. This is essential when you’re building for environments that use global variables, like UMD or IIFE formats. If you’re building a library meant to be consumed via <script> tags, the browser needs to know that when it sees import React from 'react', it should look for a global variable named React.

Consider a scenario where you’re building a small widget that needs React, but you expect the host page to provide React. Your widget’s index.js might have:

// src/widget.js
import React from 'react';
import ReactDOM from 'react-dom';

function MyWidget() {
  return React.createElement('div', null, 'Hello from my widget!');
}

const container = document.getElementById('widget-container');
ReactDOM.render(React.createElement(MyWidget), container);

If you build this with the vite.config.js shown earlier, the output bundle will contain your widget’s code but will have statements like var React = require('react'); (depending on the output format) or import React from 'react'; which, when configured with globals, will resolve to var React = window.React;.

The external option is evaluated at build time. Vite (Rollup) looks at your import statements. If a module ID matches an entry in the external array, it skips the process of finding, parsing, and bundling that module. Instead, it generates code that assumes that module will be available at runtime. This is why specifying globals is so important for browser-consumable formats; it tells the bundler how to reference these externally provided modules in the generated code.

The most common pitfall is forgetting to configure output.globals when you intend for your externalized library to be available globally. If you externalize react but don’t provide a globals: { react: 'React' } mapping, your UMD or IIFE build might fail or produce code that looks for a non-existent module loader resolution, rather than a simple window.React. Another subtle point is that the strings in external must exactly match the module specifiers used in your import statements. For instance, if you import import { Button } from '@mui/material/Button', you might need to externalize '@mui/material/Button' or, more commonly, '@mui/material' if you’re using direct imports from the package root.

Once you’ve successfully externalized a library, your next step might be to explore dynamic imports (import()) for code-splitting, allowing you to load parts of your application on demand.

Want structured learning?

Take the full Vite course →