Code Splitting: разделение кода

Code Splitting — техника разбиения JavaScript bundle на несколько chunk-файлов, загружаемых по требованию, а не единым монолитом при первом запросе.

Зачем нужно

SPA без code splitting загружает весь JS-код при первом посещении, даже если пользователь видит только главную страницу. Код админ-панели, графиков, редактора скачивается сразу. Code splitting позволяет загружать только то, что нужно сейчас: стартовый bundle минимален, остальное — по запросу. Это напрямую влияет на Time to Interactive (TTI) и Core Web Vitals.

Где используется

  • Webpack SplitChunksPlugin — автоматическое разделение vendor/common chunks
  • Vite — автоматическое разделение по динамическим импортам
  • React.lazy + import — разделение по маршрутам и тяжёлым компонентам
  • Next.js — автоматическое разделение по страницам

Динамический импорт

// Статический импорт — попадает в основной bundle
import HeavyLibrary from 'heavy-library'; // всегда загружается

// Динамический импорт — создаёт отдельный chunk
// Загружается только когда выполнится этот код
const module = await import('./heavy-module');

// Практический пример: загрузка библиотеки по требованию
async function exportToExcel(data) {
  // xlsx не загружается пока пользователь не нажал "Экспорт"
  const { utils, writeFile } = await import('xlsx');
  const wb = utils.book_new;
  utils.book_append_sheet(wb, utils.json_to_sheet(data));
  writeFile(wb, 'export.xlsx');
}

React.lazy + Suspense (разделение по маршрутам)

import { lazy, Suspense } from 'react';

// Каждая страница — отдельный chunk
const HomePage = lazy( => import('./pages/HomePage'));
const Dashboard = lazy( => import('./pages/Dashboard'));
const AdminPanel = lazy( => import('./pages/AdminPanel'));
// AdminPanel.js загрузится только при переходе на /admin

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/admin" element={<AdminPanel />} />
      </Routes>
    </Suspense>
  );
}

Webpack: настройка разделения chunks

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // Vendor chunk — React, React DOM и т.д.
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendor',
          priority: 10,
        },
        // Common chunk — код, используемый в 2+ местах
        common: {
          minChunks: 2,
          name: 'common',
          priority: 5,
        },
      },
    },
  },
};

Анализ bundle

# Webpack Bundle Analyzer — визуализация что сколько весит
npm install --save-dev webpack-bundle-analyzer

# В webpack.config.js:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [new BundleAnalyzerPlugin],

# Vite — встроенный анализ
npx vite build --report

Результат до и после

До code splitting:
  bundle.js — 2.5 MB (всё приложение)
  Первая загрузка: 2.5 MB

После code splitting:
  main.js    — 150 KB (React + роутер + главная)
  vendor.js  — 200 KB (React, React DOM — кешируется)
  dashboard.js — 300 KB (загружается при переходе)
  admin.js   — 800 KB (загружается только для админов)
  Первая загрузка: 350 KB (7× быстрее)

Частые ошибки

  • Разделение слишком мелких модулей — HTTP overhead от запроса дороже выигрыша; разделяйте chunk не менее ~20 KB.
  • Нет Suspense fallbackReact.lazy без Suspense падает с ошибкой.
  • Нет анализа bundle — делают code splitting «вслепую»; используйте Bundle Analyzer чтобы видеть реальный эффект.
  • Разделяют vendor chunk неправильно — React и React DOM должны быть в одном chunk; их разделение создаёт проблемы.

Связанные темы

Ресурсы