Веб-разработка
Webpack, Vite, Bundlers
2020 год. Evan Wallace публикует esbuild - bundler написанный на Go. Первый benchmark: esbuild собирает three.js в 0.37 секунды. Webpack 4 - 54.5 секунды. Difference: 100x. В этот момент стало ясно: JavaScript tooling написанный на JavaScript - bottleneck. Началась эра Go и Rust инструментов: esbuild, SWC, Turbopack.
- **Vercel Next.js**: Turbopack (Rust) заменяет Webpack в development - 700x быстрее для HMR в больших проектах
- **Vite** (6M weekly downloads): стандарт для новых React/Vue/Svelte проектов, используется в Nuxt 3, SvelteKit, Astro
- **Shopify Hydrogen**: Vite + React Server Components для e-commerce - code splitting по умолчанию для каждого продукта
Бандлинг: из тысячи файлов в несколько
2010 год. React приложение - 847 JS файлов. Без бандлера: 847 HTTP запросов при загрузке. При HTTP/1.1 - 6 параллельных соединений, sequential waterfall. Время загрузки: 8-12 секунд. С webpack: 1-3 файла, parallel load, 1.2 секунды. Бандлер - не опция, это обязательный инструмент.
Bundler строит dependency graph: начиная с entry point (index.ts), рекурсивно резолвит все import/require, создает граф зависимостей, объединяет в bundles. Webpack (2012) - первый массовый bundler, конфигурационный, гибкий. Vite (2020) - development сервер на native ES modules + Rollup для production build. esbuild (2020) - написан на Go, 10-100x быстрее Webpack.
esbuild - написан на Go Evan Wallace (ex-Figma). Параллельный парсинг на Go goroutines vs JavaScript single thread. Shared AST между passes vs отдельные AST. Результат: typescript compilation в 10x быстрее tsc, bundling в 100x быстрее webpack. Vite использует esbuild для pre-bundling node_modules (CommonJS -> ES modules) и для TS/JSX трансформации в dev mode.
Vite использует native ES modules в development. Почему не использовать их и в production?
Tree-Shaking: удаление мертвого кода
Lodash в CommonJS: `import _ from 'lodash'` - 70KB minified. Используется только `_.debounce`. Без tree-shaking: все 70KB в bundle. С tree-shaking через `import { debounce } from 'lodash-es'`: 2KB. Разница в 35x - прямое влияние на LCP и First Contentful Paint.
Tree-shaking работает только с ES modules. Причина: CommonJS (require) - dynamic, resolved в runtime. Нельзя статически определить что импортируется. ES modules (import/export) - static, resolved в compile time. Bundler строит граф использования: какие exports используются, какие нет. Неиспользуемые - 'трясет дерево', удаляет. Условие: pure functions без side effects (marked as `/*#__PURE__*/`).
Почему многие библиотеки публикуют как 'lodash' (CJS) так и 'lodash-es' (ESM): CommonJS для server-side Node.js совместимости, ES modules для браузерного tree-shaking. В package.json: поле `exports` с `import` (ESM) и `require` (CJS) условиями. Bundler выбирает правильный вариант автоматически. Это dual package hazard: если одна зависимость импортирует CJS, другая ESM версию - могут быть два экземпляра в bundle.
Библиотека экспортирует 100 функций. Приложение использует 3. В bundle попали все 100. Что не так?
HMR: Hot Module Replacement без перезагрузки
Разработчик меняет цвет кнопки в CSS. Без HMR: save -> full page reload -> потеря состояния (форма, прокрутка, modal открыт) -> navigate обратно. Время: 5-10 секунд. С HMR: save -> только изменённый модуль заменяется -> состояние сохранено. Время: 50-200ms. Для разработчика это разница между flow и постоянным прерыванием.
Webpack HMR: bundler перебирает граф зависимостей при изменении файла, генерирует hot update patch, клиент применяет через webpack HMR runtime. Проблема: нужно вручную добавлять hot.accept() в модулях или использовать framework-specific plugins (React Refresh, Vue HMR). Vite HMR: благодаря native ES modules изменяется только один файл без перебора всего графа - значительно быстрее.
React Fast Refresh - официальная замена react-hot-loader от Facebook (2019). Ключевое отличие: Fast Refresh сохраняет React state при изменении функциональных компонентов. Исключение: если компонент экспортирует не только React компонент (например, константы или функции), Fast Refresh делает полный remount. Поэтому рекомендация: один компонент = один файл для оптимального HMR.
HMR обновил React компонент но состояние (state) сбросилось. Почему?
Code Splitting: загружать только нужное
Главная страница e-commerce приложения: нужна главная страница, не корзина, не checkout, не admin panel. Без code splitting: весь JS приложения в одном bundle - пользователь скачивает 2MB чтобы увидеть главную страницу. С code splitting: только 300KB для главной страницы, остальное - lazy loaded по требованию.
Route-based splitting: каждый роут - отдельный chunk, загружается при навигации. Component-based splitting: тяжелые компоненты (rich text editor, PDF viewer, chart library) загружаются только когда нужны. Webpack: dynamic import() создает split point. Vite: то же API. React: React.lazy() + Suspense для code splitting на уровне компонентов.
Granular chunks vs fewer larger chunks: слишком мелкое разбиение создает много HTTP запросов (пусть и параллельных по HTTP/2). Слишком крупное - пользователь скачивает лишнее. Оптимальный chunk size: 50-250KB gzipped (Core Web Vitals рекомендации). Webpack SplitChunksPlugin автоматически объединяет часто используемые модули в shared chunk. Ключевой антипаттерн: vendor chunk размером 2MB - лучше разбить на react, router, ui-library отдельно для лучшего кэширования.
Vite всегда быстрее Webpack - нужно мигрировать все проекты на Vite
Vite значительно быстрее в development; в production Webpack может давать лучший результат для сложных конфигураций с advanced plugins
Vite production использует Rollup который уступает Webpack в некоторых advanced случаях: Module Federation, custom output formats, complex chunk strategies. Webpack 5 Module Federation позволяет разделять bundles между micro-frontends - Vite не поддерживает нативно. Выбор зависит от требований проекта
После добавления code splitting размер initial bundle вырос с 800KB до 1.2MB. В чем проблема?
Связанные темы
Bundlers работают на стыке JavaScript модулей, производительности и DevOps:
- JavaScript модули — ES modules vs CommonJS - фундаментальное различие влияющее на tree-shaking
- Web Performance — Code splitting и tree-shaking напрямую влияют на LCP, FID, CLS
- CI/CD Pipeline — Production build шаг в pipeline: bundling, minification, optimization
Ключевые идеи
- **Bundling**: dependency graph из entry point -> несколько оптимизированных файлов; Vite 100x быстрее Webpack в dev через native ES modules
- **Tree-shaking**: только ES modules; CommonJS = включить всё; sideEffects в package.json управляет что можно удалить
- **HMR**: Vite 50ms vs Webpack 2+ сек; React Fast Refresh сохраняет state; mixed exports = full remount
- **Code splitting**: route-based + component-based lazy; shared vendor chunk для react/lodash; prefetch при hover для UX
Вопросы для размышления
- Почему esbuild написан на Go а не JavaScript? Какие архитектурные решения дают ему 100x преимущество?
- Micro-frontend архитектура: несколько команд деплоят отдельные части одного SPA. Как bundlers (Module Federation) помогают избежать дублирования react между командами?
- Tree-shaking удаляет 'мертвый' код. Что происходит с кодом у которого есть side effects при импорте? Приведи реальный пример когда это важно.
Связанные уроки
- web-03 — JavaScript modules (ES modules, CommonJS) - базовая концепция которую bundlers обрабатывают
- web-15 — WebSocket клиентский код проходит через bundler перед production deploy
- se-10 — CI/CD pipeline запускает build step через bundler для каждого деплоя
- web-08 — Performance оптимизация: bundler создает основу для Core Web Vitals (LCP, FID, CLS)
- alg-11 — Dependency graph analysis в bundlers использует алгоритмы обхода графа
- alg-18-topological