Rollup конфигурация

При использовании Rollup для сборки JavaScript-библиотек, таких как Tom Select, ключевая цель заключается в создании нескольких форматов дистрибутива, корректной работе с зависимостями и минимизации побочных эффектов. Tom Sel ect как компонент селектора требует аккуратной обработки модулей, стилей и плагинов, особенно при поддержке ESM и UMD.


Базовая структура Rollup-конфигурации

Типичная конфигурация Rollup для библиотеки включает входную точку, список внешних зависимостей и набор плагинов для обработки TypeScript, Babel и стилей.

import resolve fr om '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import babel from '@rollup/plugin-babel'
import terser from '@rollup/plugin-terser'
import postcss from 'rollup-plugin-postcss'
import typescript from '@rollup/plugin-typescript'
import pkg from './package.json' assert { type: 'json' }

const external = [
  ...Object.keys(pkg.dependencies || {}),
  ...Object.keys(pkg.peerDependencies || {})
]

export default [
  {
    input: 'src/index.ts',
    output: [
      {
        file: pkg.module,
        format: 'esm',
        sourcemap: true
      },
      {
        file: pkg.main,
        format: 'cjs',
        sourcemap: true
      }
    ],
    external,
    plugins: [
      resolve(),
      commonjs(),
      typescript(),
      postcss({
        extract: true,
        minimize: true
      }),
      babel({
        babelHelpers: 'bundled',
        extensions: ['.js', '.ts']
      }),
      terser()
    ]
  }
]

Управление внешними зависимостями

Для библиотеки уровня Tom Select критично исключать зависимости из бандла. Это предотвращает дублирование кода и конфликты версий в приложениях, которые используют библиотеку.

Формирование external

const external = (id) => {
  return /^lodash/.test(id) || /^@popperjs/.test(id)
}

Такой подход позволяет гибко исключать целые группы пакетов, особенно если библиотека использует вспомогательные утилиты.


Поддержка ESM и CommonJS

Rollup позволяет собирать несколько форматов одновременно, что важно для совместимости с различными экосистемами.

  • ESM используется современными сборщиками (Vite, Snowpack)
  • CommonJS необходим для Node.js и старых инструментов
output: [
  {
    file: 'dist/tom-select.esm.js',
    format: 'esm',
    sourcemap: true
  },
  {
    file: 'dist/tom-select.cjs.js',
    format: 'cjs',
    exports: 'named'
  }
]

Обработка TypeScript в сборке

Tom Select часто расширяется типами, поэтому TypeScript является важной частью pipeline.

Конфигурация tsconfig

{
  "compilerOptions": {
    "target": "ES2019",
    "module": "ESNext",
    "declaration": true,
    "outDir": "dist/types",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}

Подключение плагина

typescript({
  tsconfig: './tsconfig.json',
  declaration: true
})

Важно разделять генерацию .d.ts и JavaScript, чтобы избежать дублирования и конфликтов типов.


Работа с CSS внутри Rollup

Tom Select включает стили, которые должны корректно попадать в сборку. Rollup не обрабатывает CSS по умолчанию, поэтому используется postcss.

Вариант с извлечением CSS

postcss({
  extract: 'tom-select.css',
  minimize: true,
  sourceMap: true
})

Такой подход позволяет:

  • подключать CSS отдельно через <link>
  • уменьшать размер JS-бандла
  • переиспользовать стили в разных окружениях

Минификация и оптимизация

Для production-сборки применяется terser, который уменьшает размер итогового пакета.

terser({
  format: {
    comments: false
  }
})

Оптимизация особенно важна для компонентов UI, где размер библиотеки напрямую влияет на время загрузки страницы.


Разделение сборок: dev и prod

В реальных проектах Rollup-конфигурация часто разделяется на режимы.

Development-сборка

const dev = {
  sourcemap: true,
  plugins: [
    resolve(),
    commonjs(),
    typescript()
  ]
}

Production-сборка

const prod = {
  sourcemap: false,
  plugins: [
    resolve(),
    commonjs(),
    typescript(),
    terser()
  ]
}

Разделение позволяет ускорить разработку и сохранять производительность в продакшене.


Поддержка плагинов Tom Select

Tom Select расширяется через плагины, которые также должны учитываться в сборке.

Динамическое подключение

export default function loadPlugin(name) {
  return import(`./plugins/${name}.ts`)
}

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

Предотвращение tree-shaking проблем

plugins: [
  {
    name: 'fix-dynamic-imports',
    resolveId(id) {
      if (id.includes('plugins/')) {
        return id
      }
    }
  }
]

Обработка CommonJS зависимостей

Некоторые зависимости Tom Select могут поставляться в формате CommonJS, что требует дополнительной трансформации.

commonjs({
  include: /node_modules/
})

Без этого шага возможны ошибки вида undefined exports.


Интеграция с Node resolution

Плагин node-resolve обеспечивает корректное разрешение модулей из node_modules.

resolve({
  browser: true,
  dedupe: ['tom-select']
})

Опция dedupe предотвращает дублирование библиотек при монорепозиториях.


Сборка UMD для браузера

Для подключения через <script> используется UMD-формат.

{
  file: 'dist/tom-select.umd.js',
  format: 'umd',
  name: 'TomSelect',
  globals: {
    lodash: '_'
  }
}

UMD позволяет использовать библиотеку без модульного загрузчика.


Sourcemap стратегия

Sourcemaps необходимы для отладки и анализа ошибок в production.

output: {
  sourcemap: true
}

В некоторых случаях применяется скрытая генерация:

sourcemap: 'hidden'

Кэширование и ускорение сборки

Rollup поддерживает incremental rebuild через watch режим.

watch: {
  include: 'src/**',
  clearScreen: false
}

При разработке Tom Select это снижает время пересборки при изменении компонентов UI и логики.


Оптимизация tree-shaking

Чтобы Tom Select корректно удалял неиспользуемый код:

  • избегается побочный импорт
  • используются чистые ES-модули
  • явно задаётся sideEffects: false
{
  "sideEffects": false
}

Это позволяет Rollup удалять неиспользуемые части селектора и плагинов.


Итоговая структура конфигурации

export default {
  input: 'src/index.ts',
  external,
  output: [
    { file: 'dist/index.esm.js', format: 'esm' },
    { file: 'dist/index.cjs.js', format: 'cjs' },
    { file: 'dist/index.umd.js', format: 'umd', name: 'TomSelect' }
  ],
  plugins: [
    resolve(),
    commonjs(),
    typescript(),
    postcss({ extract: true }),
    babel({ babelHelpers: 'bundled' }),
    terser()
  ]
}