Bundling / Code-splitting / Tree-shaking
Современные бандлеры (Webpack, Rollup, Vite, esbuild, Turbopack) решают задачи объединения модулей, удаления мёртвого кода и разбиения на чанки; TechLead должен объяснить, почему tree-shaking не работает с CommonJS и как правильно настроить code-splitting для снижения TTI.
// Tree-shaking: работает ТОЛЬКО с ES Modules (статический анализ импортов)// ✅ tree-shakeableexport function add(a, b) { return a + b; }export function subtract(a, b) { return a - b; }import { add } from './math.js'; // subtract не попадёт в bundle
// ❌ НЕ tree-shakeable (CommonJS — динамический require)// module.exports = { add, subtract };// const { add } = require('./math'); // бандлер не знает что нужно
// package.json: sideEffects для агрессивного tree-shaking// {// "sideEffects": false, // все модули — pure, можно удалять неиспользуемое// "sideEffects": ["*.css", "*.svg"] // только эти файлы имеют side effects// }
// Анализ бандла — обязательный инструмент TechLead// npx webpack-bundle-analyzer webpack-stats.json// npx vite-bundle-visualizer// npx source-map-explorer dist/app.js// → находим дубли, неожиданно большие зависимости, неправильный tree-shaking// Code Splitting: динамический import() → отдельный чанк, загружается по требованию// Route-based splitting (React)const Dashboard = React.lazy(() => import('./pages/Dashboard'));const Settings = React.lazy(() => import('./pages/Settings'));
function App() { return ( <Suspense fallback={<PageSpinner />}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> );}
// Prefetch: загрузить чанк заранее (до перехода на маршрут)// Webpack: import(/* webpackPrefetch: true, webpackChunkName: "dashboard" */ './pages/Dashboard')// Vite: автоматически добавляет <link rel="modulepreload"> в HTML
// Component-level splitting: тяжёлые зависимости (редакторы, чарты, PDF-viewer)const PdfViewer = React.lazy(() => import('./PdfViewer'));// PdfViewer + его зависимости → отдельный чанк, загружается только при открытии PDF// Vite / Rollup: стратегия чанков для оптимального кэшированияexport default defineConfig({ build: { rollupOptions: { output: { manualChunks: { // vendor: редко меняется → долгий Cache-Control: immutable + content hash vendor: ['react', 'react-dom'], ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'], charts: ['recharts', 'd3-scale'], // app-чанки: часто меняются → короткий кэш или без кэша }, }, }, chunkSizeWarningLimit: 500, // KB — порог предупреждения },});
// Цели для First Load JS (gzip):// Critical path (main + vendor chunks): < 200 КБ// Lazy-loaded chunks: < 50 КБ каждый// Метрика: @next/bundle-analyzer (Next.js) или vite-plugin-inspect
// HTTP/2 + многочисленные мелкие чанки vs HTTP/1.1 + monolith:// HTTP/2 мультиплексирует → больше мелких чанков — ОК// HTTP/1.1 (6 соединений) → оптимальнее несколько крупных чанковИтог: Tree-shaking требует ES Modules и корректного sideEffects; code-splitting делит bundle на чанки для быстрого TTI; vendor-чанки обеспечивают долгое кэширование.