Что такое сборщик (bundler) и чем Webpack отличается от Vite?

MiddleFrontend инструменты #tooling #bundlers #webpack #vite

Вопрос

Что делает сборщик (bundler) и в чём принципиальная разница между Webpack и Vite?

Короткий ответ

Сборщик анализирует граф зависимостей проекта (import/require), применяет к модулям трансформации (Babel/TS/CSS-загрузчики) и собирает их в один или несколько оптимизированных бандлов. Webpack всегда собирает бандл перед запуском dev-сервера, а Vite в разработке отдаёт исходные ES-модули браузеру напрямую, поэтому стартует почти мгновенно.

Подробный ответ

Задача любого бандлера сводится к четырём вещам:

  • Резолвинг модулей — построение графа зависимостей, начиная с entry-файлов.
  • Трансформации — прогон файлов через loaders/plugins: TS → JS, SCSS → CSS, JSX → JS и т.д.
  • Code-splitting — разбиение графа на чанки (по роутам, по import()), чтобы не грузить всё приложение сразу.
  • Оптимизация вывода — tree shaking, минификация, хэширование имён файлов для кэширования.

Webpack появился раньше остальных и решает всё это через единый конвейер loaders/plugins. Даже в dev-режиме он должен построить полный (или почти полный) бандл перед тем, как отдать что-то браузеру — на больших проектах это секунды и десятки секунд на старте и при hot reload. Зато у него самая зрелая экосистема плагинов и максимальная гибкость конфигурации.

Vite меняет подход именно для разработки: он не бандлит приложение целиком. Браузеры давно умеют нативно грузить ES-модули (<script type="module">), поэтому Vite отдаёт файлы по запросу — модуль запросили, Vite на лету его трансформировал (через esbuild, написанный на Go, что в 10–100 раз быстрее JS-трансформеров) и отдал. Тяжёлые зависимости из node_modules (react, lodash и т.д.) Vite заранее один раз "предбандлит" тем же esbuild в CommonJS→ESM, чтобы не делать сотни отдельных HTTP-запросов на внутренние файлы пакета. Отсюда почти мгновенный старт dev-сервера независимо от размера приложения.

Для продакшен-сборки Vite не использует нативные ESM-импорты браузера (слишком много запросов в проде), а под капотом собирает бандл через Rollup — то есть в проде Vite тоже делает полноценный бандлинг, просто с другим инструментом, заточенным на маленький и чистый output.

Отдельно стоят esbuild и Turbopack (от Vercel, на Rust) — сборщики нового поколения, написанные на компилируемых языках вместо JS, за счёт чего трансформации и парсинг на порядок быстрее. Их часто используют не как замену Webpack/Vite целиком, а как ускоряющий слой внутри других инструментов.

Пример

// vite.config.js — минимальная конфигурация
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    // прод-сборка идёт через Rollup под капотом
    rollupOptions: {
      output: {
        // ручной code-splitting для тяжёлых вендорных библиотек
        manualChunks: {
          vendor: ['vue', 'vue-router'],
        },
      },
    },
  },
})
// webpack.config.js — для сравнения тот же смысл в Webpack
module.exports = {
  entry: './src/main.js',
  optimization: {
    splitChunks: { chunks: 'all' }, // аналог manualChunks
  },
  module: {
    rules: [{ test: /\.vue$/, loader: 'vue-loader' }],
  },
}

Дополнительные вопросы

  • Почему Vite использует esbuild в dev и Rollup в проде, а не esbuild везде?
  • Что такое tree shaking и при каких условиях он не срабатывает (например, из-за side-эффектов в модуле)?
  • Как работает HMR (Hot Module Replacement) и чем он отличается от полной перезагрузки страницы?