Vite’s library mode allows you to build your project as a reusable library, rather than a typical web application, and it does this by exposing specific entry points and bundling your code into formats suitable for consumption by other projects.

Let’s see it in action with a simple example. Imagine we want to build a small utility library for string manipulation.

First, set up a new Vite project:

npm create vite@latest my-string-utils --template vanilla
cd my-string-utils
npm install

Now, let’s configure Vite for library mode. Edit your vite.config.js (or vite.config.ts):

import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  build: {
    lib: {
      // Could also be a dictionary or array of multiple entry points
      entry: resolve(__dirname, 'src/main.js'),
      name: 'MyStringUtils',
      // the proper extensions will be added to the file ending
      fileName: 'my-string-utils',
    },
    // If you are building a library, you can disable minification and sourcemaps
    // to speed up the build process, as these are typically handled by the consumer.
    // minify: false,
    // sourcemap: false,
  },
});

In this configuration:

  • entry: Specifies the main entry file of your library. This is the file that will be bundled.
  • name: This is the global variable name that will be exposed when your library is used via a <script> tag in a browser.
  • fileName: This is the base name for the output files. Vite will automatically append the appropriate extensions (e.g., .js, .umd.js, .es.js).

Now, let’s add some library code. In src/main.js, replace the existing content with:

export function capitalize(str) {
  if (typeof str !== 'string' || str.length === 0) {
    return '';
  }
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function reverseString(str) {
  if (typeof str !== 'string') {
    return '';
  }
  return str.split('').reverse().join('');
}

To build your library, run:

npm run build

After running the build command, you’ll find a dist directory in your project root. Inside, you’ll see files like:

  • my-string-utils.es.js: An ES module version of your library.
  • my-string-utils.umd.js: A UMD (Universal Module Definition) version, compatible with both AMD and CommonJS environments, and also usable via a <script> tag.
  • my-string-utils.umd.cjs: A CommonJS version.

You can test your library locally by creating a simple HTML file that includes it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test My String Utils</title>
</head>
<body>
    <script type="module">
        import { capitalize, reverseString } from './dist/my-string-utils.es.js';

        console.log(capitalize('hello')); // Output: Hello
        console.log(reverseString('world')); // Output: dlrow
    </script>
</body>
</html>

Open this HTML file in your browser, and check the console to see the output.

If you want to publish this to NPM, you’ll need to make a few more adjustments.

  1. package.json: Ensure your package.json has relevant fields like name, version, description, author, license. Crucially, you’ll need to tell NPM where your library files are. Add these fields:

    {
      "name": "my-string-utils",
      "version": "1.0.0",
      "description": "A simple string utility library",
      "main": "./dist/my-string-utils.umd.cjs",
      "module": "./dist/my-string-utils.es.js",
      "exports": {
        ".": {
          "import": "./dist/my-string-utils.es.js",
          "require": "./dist/my-string-utils.umd.cjs",
          "browser": "./dist/my-string-utils.umd.js"
        }
      },
      "files": [
        "dist"
      ],
      "scripts": {
        "dev": "vite",
        "build": "vite build"
      },
      "keywords": [
        "string",
        "utils"
      ],
      "author": "Your Name",
      "license": "MIT",
      "devDependencies": {
        "vite": "^5.0.0"
      }
    }
    
    • main: Points to the CommonJS entry point.
    • module: Points to the ES module entry point.
    • exports: Provides more granular control over how different environments (import, require, browser) resolve your package.
    • files: Specifies which files should be included when the package is published.
  2. README.md: Write a clear README.md explaining how to install and use your library.

  3. LICENSE: Include a LICENSE file (e.g., MIT, Apache 2.0).

After these changes, you can publish your library to NPM using npm publish.

The most surprising thing about Vite’s library mode is that it defaults to generating multiple bundle formats (ESM, UMD, CJS) without explicit configuration for each, aiming for maximum compatibility out of the box for your published package. This means that a single npm run build command prepares your code for consumption in virtually any modern JavaScript environment, from vanilla <script> tags to Node.js applications and modern frontend frameworks.

When specifying lib.entry, you can also provide a dictionary to define multiple entry points, allowing you to create a library with distinct modules that can be imported separately. For example:

// vite.config.js
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  build: {
    lib: {
      entry: {
        index: resolve(__dirname, 'src/main.js'),
        utils: resolve(__dirname, 'src/utils.js'),
      },
      name: 'MyLibrary',
      fileName: (format, entryName) => {
        return `${entryName}.${format}.js`;
      },
    },
  },
});

With this configuration, running npm run build would generate index.es.js, index.umd.js, utils.es.js, and utils.umd.js in your dist folder, allowing consumers to import specific parts of your library like import { capitalize } from 'my-library/utils'; if your package.json’s exports field is set up to support subpath imports.

The fileName option can also accept a function, which gives you fine-grained control over the output filenames based on the format and entry name. This is particularly useful when you have multiple entry points or want to customize the naming convention.

Understanding how Vite handles different module formats (ESM, UMD, CJS) and the role of the exports field in package.json is key to successfully publishing a library that works everywhere.

Want structured learning?

Take the full Vite course →