Библиотечный режим: build.lib

Режим build.lib предназначен для сборки библиотек, SDK, UI-компонентов, утилит и других переиспользуемых пакетов. В отличие от стандартной сборки приложения, библиотечный режим ориентирован не на генерацию HTML-страниц, а на создание универсального JavaScript-пакета, который может использоваться в других проектах.

При использовании build.lib Vite переключается на специализированный режим Rollup-сборки:

  • не создаётся index.html;
  • изменяется стратегия генерации чанков;
  • используется другой формат вывода;
  • отключаются некоторые оптимизации, характерные для SPA;
  • появляется поддержка форматов es, cjs, umd, iife.

Базовая настройка build.lib

Минимальная конфигурация выглядит следующим образом:

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

export default defineConfig({
    build: {
        lib: {
            entry: path.resolve(__dirname, 'src/index.js'),
            name: 'MyLibrary',
            fileName: 'my-library'
        }
    }
})

Основные параметры build.lib

entry

Точка входа библиотеки.

lib: {
    entry: 'src/index.js'
}

Именно этот файл становится основным экспортом всей библиотеки.

Пример:

// src/index.js
export { Button } from './components/Button'
export { Modal } from './components/Modal'
export { formatDate } from './utils/date'

name

Имя глобальной переменной для форматов umd и iife.

lib: {
    name: 'MyLibrary'
}

После подключения UMD-сборки библиотека станет доступна:

<script src="my-library.umd.js"></script>

<script>
    MyLibrary.Button
</script>

Для формата es параметр не используется.


fileName

Имя выходных файлов.

lib: {
    fileName: 'my-library'
}

Результат:

dist/
├── my-library.js
├── my-library.umd.cjs
└── style.css

Можно использовать функцию:

fileName: (format) => `my-library.${format}.js`

Результат:

my-library.es.js
my-library.cjs.js

Форматы сборки

Формат es

Современный стандарт ES Modules.

build: {
    lib: {
        formats: ['es']
    }
}

Результат:

import { Button } from 'my-library'

Особенности:

  • поддерживает tree-shaking;
  • оптимален для современных bundler;
  • используется по умолчанию;
  • рекомендуется для npm-библиотек.

Формат cjs

CommonJS-модули для Node.js.

formats: ['cjs']

Использование:

const lib = require('my-library')

Подходит для:

  • старых Node.js-проектов;
  • совместимости с CommonJS-экосистемой;
  • некоторых backend-инструментов.

Формат umd

Универсальный формат.

formats: ['umd']

Поддерживает:

  • браузеры через <script>;
  • AMD;
  • CommonJS.

Пример подключения:

<script src="my-library.umd.js"></script>

Формат iife

Самовызывающаяся функция.

formats: ['iife']

Используется для:

  • CDN-скриптов;
  • standalone-библиотек;
  • подключения без module system.

Сборка нескольких форматов

Наиболее распространённый вариант:

build: {
    lib: {
        entry: 'src/index.js',
        name: 'MyLibrary',
        formats: ['es', 'cjs', 'umd']
    }
}

Результат:

dist/
├── my-library.js
├── my-library.cjs
└── my-library.umd.cjs

Внешние зависимости: external

Критически важная часть библиотечной сборки.

Если библиотека использует Vue, React или другие крупные зависимости, их обычно исключают из бандла.

Пример

build: {
    lib: {
        entry: 'src/index.js',
        name: 'MyLibrary'
    },
    rollupOptions: {
        external: ['vue']
    }
}

Теперь vue не попадёт в итоговый bundle.


Глобальные переменные для UMD

При использовании external для umd требуется указать глобальные переменные.

rollupOptions: {
    external: ['vue'],
    output: {
        globals: {
            vue: 'Vue'
        }
    }
}

Без этого UMD-сборка не сможет получить зависимость в браузере.


Полная конфигурация библиотеки

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

export default defineConfig({
    build: {
        lib: {
            entry: path.resolve(__dirname, 'src/index.js'),
            name: 'MyLibrary',
            fileName: (format) => `my-library.${format}.js`,
            formats: ['es', 'umd']
        },
        rollupOptions: {
            external: ['vue'],
            output: {
                globals: {
                    vue: 'Vue'
                }
            }
        }
    }
})

Структура библиотеки

Типичная структура проекта:

project/
├── src/
│   ├── components/
│   ├── composables/
│   ├── utils/
│   └── index.js
├── dist/
├── package.json
└── vite.config.js

Экспорты библиотеки

Именованные экспорты

export { Button }
export { Modal }

Использование:

import { Button } from 'my-library'

Экспорт по умолчанию

export default MyLibrary

Использование:

import MyLibrary from 'my-library'

CSS в библиотечном режиме

Vite автоматически извлекает CSS.

Пример:

import './style.css'

Результат:

dist/
├── my-library.js
└── style.css

Отключение CSS code splitting

Иногда требуется единый CSS-файл.

build: {
    cssCodeSplit: false
}

Генерация TypeScript declaration files

Vite сам не генерирует .d.ts.

Обычно используется vite-plugin-dts.

Установка

npm install vite-plugin-dts -D

Настройка

import dts from 'vite-plugin-dts'

export default defineConfig({
    plugins: [
        dts()
    ]
})

Результат:

dist/
├── index.d.ts
├── my-library.js
└── style.css

Использование preserveModules

Позволяет сохранять структуру модулей.

rollupOptions: {
    output: {
        preserveModules: true
    }
}

Результат:

dist/
├── components/
├── utils/
└── index.js

Tree-shaking

Одно из главных преимуществ ES Modules.

Плохой вариант

export default {
    Button,
    Modal,
    Table
}

Bundler сложнее удалять неиспользуемый код.


Хороший вариант

export { Button }
export { Modal }
export { Table }

Такой подход улучшает tree-shaking.


Несколько точек входа

Vite поддерживает multi-entry libraries.

Пример

lib: {
    entry: {
        index: 'src/index.js',
        utils: 'src/utils/index.js',
        components: 'src/components/index.js'
    }
}

Результат:

dist/
├── index.js
├── utils.js
└── components.js

Использование alias

resolve: {
    alias: {
        '@': path.resolve(__dirname, 'src')
    }
}

Пример:

import { Button } from '@/components/Button'

Минификация библиотеки

По умолчанию используется esbuild.

build: {
    minify: true
}

Можно использовать terser.

npm install terser -D
build: {
    minify: 'terser'
}

Sourcemap для библиотек

Полезно при отладке.

build: {
    sourcemap: true
}

Результат:

my-library.js.map

Генерация деклараций package exports

Современные npm-пакеты используют exports.

Пример package.json

{
    "name": "my-library",
    "type": "module",
    "main": "./dist/my-library.cjs.js",
    "module": "./dist/my-library.es.js",
    "exports": {
        ".": {
            "import": "./dist/my-library.es.js",
            "require": "./dist/my-library.cjs.js"
        }
    }
}

Side effects

Для корректного tree-shaking:

{
    "sideEffects": false
}

Если имеются CSS-импорты:

{
    "sideEffects": [
        "*.css"
    ]
}

Сборка Vue-библиотеки

import vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [vue()],
    build: {
        lib: {
            entry: 'src/index.js',
            name: 'MyUILibrary'
        },
        rollupOptions: {
            external: ['vue'],
            output: {
                globals: {
                    vue: 'Vue'
                }
            }
        }
    }
})

Сборка React-библиотеки

import react from '@vitejs/plugin-react'

export default defineConfig({
    plugins: [react()],
    build: {
        lib: {
            entry: 'src/index.jsx',
            name: 'MyReactLibrary'
        },
        rollupOptions: {
            external: ['react', 'react-dom'],
            output: {
                globals: {
                    react: 'React',
                    'react-dom': 'ReactDOM'
                }
            }
        }
    }
})

Common pitfalls

Включение зависимостей в bundle

Ошибка:

external: []

Результат:

  • огромный размер библиотеки;
  • дублирование React/Vue;
  • конфликты версий.

Использование browser-only API

Ошибка:

window.addEventListener(...)

Проблема:

  • библиотека ломается в SSR;
  • Node.js выдаёт ошибку.

Безопасный вариант:

if (typeof window !== 'undefined') {
    window.addEventListener(...)
}

Неправильный main в package.json

Ошибка:

{
    "main": "index.js"
}

Файл отсутствует после сборки.

Правильно:

{
    "main": "./dist/my-library.cjs.js"
}

Проверка итоговой библиотеки

Обычно библиотеку тестируют через:

npm pack

или:

npm link

Также часто создают отдельный playground-проект:

playground/

где проверяется:

  • импорт модулей;
  • tree-shaking;
  • CSS;
  • SSR;
  • TypeScript;
  • UMD-сборка.

Интеграция с npm

Подготовка

{
    "files": [
        "dist"
    ]
}

Скрипты

{
    "scripts": {
        "build": "vite build",
        "dev": "vite",
        "preview": "vite preview"
    }
}

Использование emptyOutDir

build: {
    emptyOutDir: true
}

Перед каждой сборкой каталог dist очищается.


Генерация manifest

Для библиотек обычно не требуется:

build: {
    manifest: false
}

Контроль имён assets

rollupOptions: {
    output: {
        assetFileNames: 'assets/[name].[ext]'
    }
}

Контроль имён чанков

output: {
    chunkFileNames: 'chunks/[name].js'
}

Встраивание ассетов

Лимит inline-файлов:

build: {
    assetsInlineLimit: 4096
}

Поддержка SSR

Для универсальных библиотек важно избегать:

  • прямого доступа к DOM;
  • использования window;
  • использования document;
  • browser-only API.

SSR-совместимость особенно важна для:

  • Nuxt;
  • Next.js;
  • Astro;
  • SvelteKit.

Оптимизация размера библиотеки

Основные методы:

  • использование external;
  • tree-shaking;
  • ES Modules;
  • отказ от лишних polyfills;
  • минимизация runtime-зависимостей;
  • раздельные entry points;
  • lazy imports.

Пример production-ready конфигурации

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
import path from 'path'

export default defineConfig({
    plugins: [
        vue(),
        dts()
    ],

    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src')
        }
    },

    build: {
        sourcemap: true,

        cssCodeSplit: false,

        lib: {
            entry: path.resolve(__dirname, 'src/index.js'),
            name: 'MyUILibrary',
            formats: ['es', 'umd'],
            fileName: (format) => `my-ui-library.${format}.js`
        },

        rollupOptions: {
            external: ['vue'],
            output: {
                globals: {
                    vue: 'Vue'
                },

                assetFileNames: 'assets/[name].[ext]',
                chunkFileNames: 'chunks/[name].js'
            }
        }
    }
})