Що таке Webpack?
Webpack - це статичний бандлер модулів для JavaScript-застосунків, який будує граф залежностей з твоїх вихідних файлів і генерує один або кілька оптимізованих бандлів для браузера.
Теорія
TL;DR
- Webpack схожий на конвеєр: сирі файли (JS, CSS, зображення) потрапляють через точки входу, проходять через лоадери (спеціалізовані обробники) і виходять як готові до браузера бандли
- Головна відмінність від простого склеювання файлів: Webpack розуміє
import/requireі будує повний граф залежностей, а не сліпо об'єднує файли - Чотири концепції закривають 90% питань на співбесіді: entry (де починати), output (куди писати), loaders (трансформують не-JS файли), plugins (все інше)
- Використовуй, якщо є більше 5 JS файлів, потрібна обробка CSS або зображень, або підтримка старих браузерів
- Пропусти для маленьких статичних сайтів або коли достатньо CDN посилань
Швидкий приклад
// webpack.config.js - мінімальна робоча конфігурація
const path = require('path');
module.exports = {
entry: './src/index.js', // Починаємо звідси
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production' // Вмикає мінімізацію + tree shaking
};
// Запуск: npx webpack
// Результат: dist/bundle.js - один оптимізований файл з усіх модулівКілька вихідних файлів на вході, один оптимізований бандл на виході. Ось і вся ідея.
Як працює граф залежностей
Webpack стартує з точки входу і рекурсивно проходить кожен import та require. Кожен знайдений файл стає вузлом у графі. Якщо index.js імпортує utils.js, який імпортує helpers.js, всі три потрапляють до бандлу.
Це принципово інший підхід ніж простий конкатенатор файлів. Конкатенатор склеює файли у заданому порядку. Webpack знає, який код реально виконується, тому може вирізати те, що ніколи не використовується.
Entry, output, loaders, plugins
Ці чотири концепції питають на майже кожній співбесіді про Webpack.
Entry - вихідний файл. Для багатосторінкових застосунків можна вказати кілька точок входу.
Output визначає, куди Webpack записує бандл і як його назвати. path.resolve(__dirname, 'dist') - стандартний варіант.
Loaders трансформують файли, які Webpack не вміє читати з коробки. За замовчуванням він розуміє тільки JS і JSON. Додай babel-loader для транспіляції ES6+, css-loader для парсингу CSS-імпортів, style-loader для вставки стилів у DOM. Лоадери виконуються справа наліво в масиві use.
Plugins беруть на себе задачі, які лоадерам недоступні: генерація HTML файлів, витягування CSS в окремі файли, мінімізація. HtmlWebpackPlugin - найчастіший гість у конфігах.
Режим розробки і продакшену
// Development: читабельний код, source maps, швидкі перебудови
module.exports = { mode: 'development', entry: './src/index.js' };
// Production: мінімізація Terser, tree shaking, без source maps
module.exports = { mode: 'production', entry: './src/index.js' };Забути про mode: 'production' - найпоширеніша помилка. Dev-бандл буває у 10 разів більший за продакшн. Бандл 200KB у продакшні часто виходить 2MB у dev-режимі. Без перебільшень.
Коли використовувати Webpack
- React або Vue застосунок з багатьма модулями і ресурсами: Webpack підходить, особливо коли потрібен точний контроль над code splitting
- Потрібна підтримка старих браузерів: поєднай Webpack з Babel для транспіляції ES6+ до ES5
- Складний білд-пайплайн з кастомними лоадерами, кількома точками входу або module federation: Webpack тут доречний
- Одна HTML-сторінка з двома
<script>тегами: Webpack зайвий, CDN посилання швидше налаштувати - Новий проект зі швидкою ітерацією: Vite краще підійде завдяки миттєвому HMR
Порівняння бандлерів
| Характеристика | Webpack | Parcel | Vite | Rollup |
|---|---|---|---|---|
| Конфігурація | Потрібна (JS/JSON) | Не потрібна | Мінімальна | Потрібна |
| Tree shaking | Так | Так | Так | Відмінний |
| Швидкість HMR | Добра | Швидка | Найшвидша | Потребує налаштування |
| Найкраще для | Складних застосунків (React/Next.js) | Швидких прототипів | Сучасних SPA | Бібліотек |
| Поріг входу | Вищий | Низький | Низький | Середній |
Як Webpack обробляє файли всередині
Компілятор Webpack (побудований на системі плагінів Tapable) стартує з точки входу, використовує Acorn для парсингу AST (абстрактного синтаксичного дерева) кожного файлу, знаходить виклики import/require, розрізняє шляхи, застосовує лоадери і формує чанки. У продакшені Terser виконує мінімізацію і видалення мертвого коду. У dev-режимі webpack-dev-server додає Express-сервер з HMR через WebSocket, щоб браузер замінював модулі без перезавантаження сторінки.
Типові помилки
1. Немає mode: 'production'
// Неправильно: без mode - режим 'none', жодної оптимізації
module.exports = { entry: './src/index.js' };
// Результат: ~2MB немінімізованого бандлу
// Правильно:
module.exports = { mode: 'production', entry: './src/index.js' };
// Результат: ~200KB2. Неправильний порядок лоадерів для CSS
// Неправильно: лоадери йдуть справа наліво, цей порядок ламає вставку стилів
{ test: /\.css$/, use: ['css-loader', 'style-loader'] }
// Правильно: style-loader першим у масиві (виконується останнім)
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }Ця помилка дає порожню сторінку без жодного повідомлення про помилку. Дуже поширена ситуація.
3. Ігнорування publicPath при деплої на підшлях
// Неправильно: чанки вантажаться з кореня сайту, 404 на шляху /app/
output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }
// Правильно:
output: { publicPath: '/app/', filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }4. Бандлинг Node-only модулів для браузера
// Неправильно: Webpack намагається поліфілити fs, path тощо для браузера
import fs from 'fs';
// Правильно: вкажи target і externals
module.exports = {
target: 'node',
externals: { fs: 'commonjs fs' }
};Де зустрічається у реальних проектах
- React (Create React App, Next.js): збирає JSX, CSS і зображення у vendor і app чанки
- Vue CLI: обробляє однофайлові компоненти (.vue) через vue-loader
- Angular CLI: компілює TypeScript і шаблони в AOT-оптимізовані бандли
- Electron: збирає Node і браузерний код для десктопних застосунків
- Storybook: dev-сервер з HMR для розробки компонентів
Я бачив конфіги Webpack на 200+ рядків у великих монорепо. У таких проектах міграція на Vite часто окупається швидше, ніж здається. Але для проектів на основі Create React App або старих налаштувань Webpack залишається стандартом.
Питання на співбесіді
Q: Яка різниця між loaders і plugins?
A: Loaders трансформують окремі типи файлів до того, як вони потрапляють у бандл (Babel транспілює JS, css-loader парсить CSS). Plugins працюють на рівні всього процесу компіляції і роблять те, чого лоадери не можуть: генерують HTML, витягують CSS в окремі файли, визначають глобальні константи.
Q: Як працює tree shaking у Webpack?
A: Базується на статичному аналізі синтаксису ES-модулів (import/export). Webpack позначає невикористані експорти як мертвий код, потім Terser видаляє їх під час мінімізації. Для роботи потрібен "sideEffects": false у package.json пакету і використання ES-модулів замість CommonJS.
Q: Яка різниця між code splitting і dynamic imports?
A: Code splitting через optimization.splitChunks - це рішення на рівні збірки: Webpack автоматично розділяє бібліотеки від коду застосунку. Dynamic imports (import('./module.js')) керуються в рантаймі: чанк завантажується за вимогою, коли браузер доходить до цього рядка. Обидва зменшують початковий розмір бандлу, але dynamic imports дають більше явного контролю.
Q: HMR проти live reload - яка реальна різниця?
A: Live reload перезавантажує всю сторінку при зміні файлу. HMR (Hot Module Replacement) замінює тільки змінений модуль у запущеному застосунку, зберігаючи стан компонентів. Для React-форми з 10 заповненими полями live reload стирає все; HMR зберігає дані і оновлює тільки логіку зміненого компонента.
Q: Що змінилось між Webpack 4 і Webpack 5?
A: Три речі для співбесіди: (1) Webpack 5 прибрав вбудовані Node-поліфіли (Buffer, process), тому браузерні збірки що на них покладались потребують явного налаштування. (2) ID чанків тепер детерміновані за замовчуванням, що покращує довготривале кешування. (3) API Asset Modules замінив file-loader, url-loader і raw-loader чотирма вбудованими типами ресурсів.
Приклади
Базовий: об'єднання двох файлів
// src/message.js
export default 'Hello Webpack';
// src/index.js
import message from './message.js';
console.log(message); // 'Hello Webpack'
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production'
};
// Запуск: npx webpack
// Результат: dist/bundle.js (~1KB, обидва файли злиті і мінімізовані)Два вихідних файли, один результат. Webpack вирішив import і об'єднав їх автоматично. Це граф залежностей у найпростішому вигляді.
Середній: React застосунок з CSS і генерацією HTML
// webpack.config.js для реального React-проекту
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/ // Залежності не транспілюємо
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // Справа наліво: парсинг, потім вставка
}
]
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' })
],
output: {
filename: 'main.[contenthash].js', // Хеш для cache busting
path: path.resolve(__dirname, 'dist')
},
mode: 'production'
};babel-loader обробляє JSX і ES6+. css-loader парсить CSS-імпорти. style-loader вставляє стилі у <head> під час виконання. HtmlWebpackPlugin генерує dist/index.html і автоматично додає <script> тег з посиланням на хешований бандл.
Просунутий: динамічні імпорти і code splitting
// src/index.js
const button = document.getElementById('load-btn');
button.addEventListener('click', () => {
// Цей import() створює ОКРЕМИЙ чанк-файл
// Він завантажується лише при кліку на кнопку
import('./heavyComponent.js')
.then(module => module.default());
});
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js', // Шаблон назви для async чанків
path: path.resolve(__dirname, 'dist')
},
mode: 'production'
};
// Результат:
// dist/main.bundle.js (~1KB, завантажується одразу)
// dist/1.chunk.js (~50KB, завантажується тільки при кліку)Початкове завантаження сторінки залишається мінімальним. heavyComponent.js завантажується тільки тоді, коли юзер справді натискає кнопку. Саме так великі SPA тримають швидкість початкового завантаження. У Webpack 5 невикористані чанки в продакшн-режимі видаляються автоматично.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.