Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке Webpack?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Webpack** - це статичний бандлер модулів, який будує граф залежностей з JS, CSS і зображень та генерує оптимізовані бандли для браузера. Entry визначає вхідний файл, output - куди записувати, loaders трансформують не-JS ресурси, plugins виконують задачі рівня збірки. ```javascript module.exports = { entry: './src/index.js', output: { filename: 'bundle.js' }, mode: 'production' }; ``` **Головне:** без `mode: 'production'` мінімізація і tree shaking вимкнені.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Webpack** - це статичний бандлер модулів для JavaScript-застосунків, який будує граф залежностей з твоїх вихідних файлів і генерує один або кілька оптимізованих бандлів для браузера. ## Теорія ### TL;DR - Webpack схожий на конвеєр: сирі файли (JS, CSS, зображення) потрапляють через точки входу, проходять через лоадери (спеціалізовані обробники) і виходять як готові до браузера бандли - Головна відмінність від простого склеювання файлів: Webpack розуміє `import`/`require` і будує повний граф залежностей, а не сліпо об'єднує файли - Чотири концепції закривають 90% питань на співбесіді: entry (де починати), output (куди писати), loaders (трансформують не-JS файли), plugins (все інше) - Використовуй, якщо є більше 5 JS файлів, потрібна обробка CSS або зображень, або підтримка старих браузерів - Пропусти для маленьких статичних сайтів або коли достатньо CDN посилань ### Швидкий приклад ```javascript // 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` - найчастіший гість у конфігах. ### Режим розробки і продакшену ```javascript // 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'`** ```javascript // Неправильно: без mode - режим 'none', жодної оптимізації module.exports = { entry: './src/index.js' }; // Результат: ~2MB немінімізованого бандлу // Правильно: module.exports = { mode: 'production', entry: './src/index.js' }; // Результат: ~200KB ``` **2. Неправильний порядок лоадерів для CSS** ```javascript // Неправильно: лоадери йдуть справа наліво, цей порядок ламає вставку стилів { test: /\.css$/, use: ['css-loader', 'style-loader'] } // Правильно: style-loader першим у масиві (виконується останнім) { test: /\.css$/, use: ['style-loader', 'css-loader'] } ``` Ця помилка дає порожню сторінку без жодного повідомлення про помилку. Дуже поширена ситуація. **3. Ігнорування `publicPath` при деплої на підшлях** ```javascript // Неправильно: чанки вантажаться з кореня сайту, 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 модулів для браузера** ```javascript // Неправильно: 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` чотирма вбудованими типами ресурсів. ## Приклади ### Базовий: об'єднання двох файлів ```javascript // 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 ```javascript // 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 ```javascript // 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 невикористані чанки в продакшн-режимі видаляються автоматично.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.