Перейти к содержимому

5. Code Splitting

Code Splitting — это разделение бандла на части, которые загружаются по необходимости. Вместо одного огромного JS файла — много маленьких.

❌ Без code splitting:
Загружаем 2MB JS → пользователь ждёт → видит страницу
✅ С code splitting:
Загружаем 200KB core → пользователь видит страницу
Загружаем остальное → когда нужно
// ❌ Статический импорт — всё в одном бандле
import { Chart } from './chart'; // тяжёлая библиотека
// ✅ Динамический импорт — загружается при необходимости
const loadChart = async () => {
const { Chart } = await import('./chart');
return new Chart(/* ... */);
};
// Загружаем только при клике
button.addEventListener('click', async () => {
const { openModal } = await import('./modal');
openModal();
});
// С обработкой ошибок
async function loadFeature() {
try {
const { Feature } = await import('./feature');
Feature.init();
} catch (error) {
console.error('Failed to load feature:', error);
}
}
import { lazy, Suspense } from 'react';
// ✅ Ленивая загрузка компонента
const Dashboard = lazy(() => import('./Dashboard'));
const UserProfile = lazy(() => import('./UserProfile'));
const HeavyChart = lazy(() => import('./HeavyChart'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<Dashboard />
</Suspense>
);
}
// ✅ С роутером
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Blog = lazy(() => import('./pages/Blog'));
function AppRoutes() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
</Routes>
</Suspense>
);
}
webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Выносим vendor в отдельный чанк
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
// Выносим shared components
common: {
name: 'common',
minChunks: 2,
chunks: 'async',
priority: 10,
},
},
},
// Runtime в отдельный файл
runtimeChunk: 'single',
},
};
vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
// Выносим тяжёлые библиотеки
'vendor-react': ['react', 'react-dom'],
'vendor-router': ['react-router-dom'],
'vendor-charts': ['recharts'],
'vendor-form': ['react-hook-form', 'zod'],
},
},
},
},
};
// Автоматически по директориям
export default {
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
if (id.includes('src/pages')) {
return 'pages';
}
},
},
},
},
};

Next.js автоматически делает code splitting по страницам:

// app/page.tsx — отдельный чанк
// app/about/page.tsx — отдельный чанк
// app/blog/[slug]/page.tsx — отдельный чанк
// ✅ Явная динамическая загрузка
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Загрузка...</p>,
ssr: false, // отключить SSR для этого компонента
});
// ✅ Загрузка при viewport
const LazySection = dynamic(() => import('./LazySection'), {
loading: () => <div style={{ height: 400 }}>Загрузка...</div>,
});
Окно терминала
# Webpack Bundle Analyzer
npm install --save-dev webpack-bundle-analyzer
# В webpack.config.js:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [new BundleAnalyzerPlugin()]
# Vite
npm install --save-dev rollup-plugin-visualizer
# vite.config.js:
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [visualizer({ open: true })]
# Next.js
npm install @next/bundle-analyzer
# next.config.js:
const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: true });
module.exports = withBundleAnalyzer({});
// ✅ Prefetch при наведении (вероятный следующий переход)
const links = document.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('mouseenter', () => {
const href = link.getAttribute('href');
if (href) {
import(href); // начинаем загрузку
}
});
});
// ✅ React Router prefetch
import { Link } from 'react-router-dom';
<Link
to="/dashboard"
onMouseEnter={() => import('./Dashboard')} // prefetch on hover
>
Dashboard
</Link>