Webpack — классический бандлер

Webpack собирает «пакет» (bundle): берёт точку входа, обходит все import-ы и собирает итоговый JS/CSS для браузера. Это стандарт индустрии, который живёт в тысячах проектов — его знание обязательно для работы с легаси-кодовой базой.

Уровень: средний 17 разделов ⏱ 3 часа

1. Зачем нужен бандлер

Браузер давно умеет в ES-модули, но реальной команде нужны:

Vite (курс Vite) — современный преемник Webpack. Понимание Webpack поможет и там: многие концепции из Vite пришли именно из Webpack.

2. Установка и базовый конфиг

npm init -y
npm install --save-dev webpack webpack-cli
// webpack.config.js
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    clean: true
  },
  mode: "development"
};
// package.json
"scripts": {
  "build": "webpack",
  "watch": "webpack --watch"
}

Точка входа — файл, с которого Webpack начинает обход графа зависимостей. Всё, что импортируется из неё (прямо или косвенно), попадёт в бандл.

3. Режимы: development и production

modeНазначениеОптимизации
developmentскорость сборки, читаемый кодsource maps, комменты
productionминимальный размерминификация, tree-shaking, хэши
npm run build -- --mode production

Минификация в Webpack 5 по умолчанию — TerserPlugin, CSS-минификация — CssMinimizerPlugin.

4. Loaders

Loaders преобразуют файлы по расширению и «присоединяют» их к графу модулей:

module: {
  rules: [
    {
      test: /\.css$/,
      use: ["style-loader", "css-loader"]
    },
    {
      test: /\.(png|jpe?g|svg)$/i,
      type: "asset/resource"
    }
  ]
}
// use выполняется справа налево:
// css-loader парсит CSS в JS-модуль, style-loader добавляет его в DOM
import "./styles.css";
Порядок loaders критичен: для CSS это style-loader → css-loader (справа налево в массиве use). Переставь — и стили не применятся.

5. Plugins

Plugins делают то, что loaders не умеют: генерируют HTML, очищают папки, инжектят переменные окружения:

npm install --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({ template: "./src/index.html" })
  ]
};

Плагин автоматически вставит <script src="bundle.js"> в HTML. Популярные плагины: MiniCssExtractPlugin (отдельный CSS-файл), DefinePlugin (переменные), CleanWebpackPlugin.

6. Sass, CSS Modules и картинки

module: {
  rules: [
    {
      test: /\.s[ac]ss$/i,
      use: ["style-loader", {
        loader: "css-loader",
        options: { modules: true }   // CSS Modules
      }, "sass-loader"]
    }
  ]
}
import styles from "./Card.module.scss";

<div className={styles.card}>Карточка</div>
import logo from "./assets/logo.png";
<img src={logo} alt="лого" />
CSS Modules изолируют классы: хэш в имени («card_1a2b»), который не сталкивается с чужими стилями. Подробнее про сам Sass — в курсе Sass.

7. DevServer

npm install --save-dev webpack-dev-server
devServer: {
  static: "./dist",
  port: 3000,
  hot: true,               // HMR
  historyApiFallback: true,// SPA-роутинг
  proxy: { "/api": "http://localhost:8080" }
}

Запусти webpack serve — код обновится без перезагрузки страницы.

8. Entry точки и code splitting

Несколько точек входа — например, страницы или отдельные модули:

entry: {
  main: "./src/index.js",
  admin: "./src/admin.js"
},
output: {
  filename: "[name].[contenthash].js"
}

Общий код выносится автоматически в отдельный чанк:

optimization: {
  splitChunks: {
    chunks: "all",
    cacheGroups: {
      vendor: { test: /node_modules/, name: "vendor" }
    }
  }
}
Хэш в имени файла ([contenthash]) заставляет браузер перекачать бандл только когда он реально изменился — это основа кэширования.

9. TypeScript и Babel

Для TypeScript используем ts-loader:

npm install --save-dev typescript ts-loader
{
  test: /\.tsx?$/,
  use: "ts-loader",
  exclude: /node_modules/
},
// resolve
resolve: { extensions: [".tsx", ".ts", ".js"] }

Для React+JSX через Babel: babel-loader с пресетом @babel/preset-react. Подробности по TS — в курсе TypeScript.

10. Оптимизация продакшена

mode: "production",
optimization: {
  minimize: true,
  usedExports: true,     // tree-shaking
  splitChunks: { chunks: "all" }
},
performance: {
  hints: "warning"
}
Tree-shaking работает только с ES-модулями (import/export). Помечай библиотеки как sideEffects: false, иначе бандл может включить лишний код.

11. Переменные окружения

Режим передаётся через --mode, а значения — через DefinePlugin или плагин dotenv-webpack:

const Dotenv = require("dotenv-webpack");
module.exports = {
  plugins: [ new Dotenv() ]
};
// .env
API_URL=https://api.example.com

В коде значение подставляется статически:

const api = process.env.API_URL;   // заменится на строку при сборке
Всё, что инлайнится через DefinePlugin, попадает в итоговый бандл. Секреты сервера храни в .env.local и не коммить, а прави не зашивай в build.

12. Анализ размера бандла

После сборки измеряй результат:

npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
module.exports = {
  plugins: [ new BundleAnalyzerPlugin() ]
};

Откроется интерактивная карта модулей — видно, что съедает килобайты. Дальше оптимизируй:

Команда webpack --profile --json > stats.json даёт статистику без плагина; сравнивай размеры до и после каждой правки.

13. Module Federation

Module Federation (Webpack 5) позволяет приложениям обмениваться кодом в рантайме: одна команда выставляет «микро-приложение», другая подключает его без пересборки:

const { ModuleFederationPlugin } = require("webpack");
new ModuleFederationPlugin({
  name: "host",
  remotes: {
    "orderApp": "orderApp@https://order.example.com/orderApp.js"
  },
  shared: { react: { singleton: true }, "react-dom": { singleton: true } }
})
const OrderBlock = React.lazy(() => import("orderApp/OrderBlock"));
Federation снимает ограничение одной сборки, но требует аккуратной версии shared-библиотек: разъехавшиеся мажоры приведут к двум копиям Vue/React и странным багам.

14. resolve: алиасы и порядок поиска модулей

Короткие алиасы избавляют от лесенок ../../ и ускоряют разрешение импортов:

resolve: {
  alias: { "@": path.resolve(__dirname, "src") },
  extensions: [".tsx", ".ts", ".js", ".vue"],
  modules: [path.resolve(__dirname, "src"), "node_modules"]
}
// в коде
import { Button } from "@/ui/Button";
Настрой alias в начале проекта: переименование вложенных каталогов потом обходится дороже, чем минута настройки сейчас.

15. Source maps: отладка «собранного» кода

Source map связывает минифицированный бандл с исходниками, чтобы точки останова в DevTools ставились в вашем коде, а не в однострочном минифите:

devtool: "source-map"        // для продакшена
devtool: "eval-source-map"   // для быстрой разработки
devtool: "hidden-source-map" // карта без ссылки: для Sentry
РежимСкоростьДля чего
evalочень быстраяdev без полной карты
eval-source-mapбыстраяdev с точными исходниками
source-mapмедленнееproduction + отладка
nosources-source-mapмедленнаяproduction без отдачи исходного кода
Не публикуй sourcemap в проде без контроля: в них лежит почти исходный код проекта. Либо прячь (hidden / nosources), либо отдавай только во внутренний сервис ошибок.

16. Скорость сборки: persistent cache и watch

Медленная пересборка съедает часы команды. Быстрые оптимизации config:

cache: { type: "filesystem" },   // кэш между запусками
watchOptions: { ignored: ["**/node_modules"] },
output: { pathinfo: false },
optimization: { usedExports: "global" }
Измеряй до и после: time webpack --mode development. Если сборка выросла после добавления пакета — смотри bundle-analyzer (раздел 12), возможно, он тянет лишнее.

17. Безопасность: уязвимости и целостность бандла

Webpack не знает, какие пакеты вы собираете, — риск приходит из node_modules. Основы гигиены:

npm audit --omit=dev        // только прод-зависимости
npm audit fix               // безопасные обновления
Автоматическое обновление пакета ломает совместимость чаще, чем кажется: после npm audit fix прогони тесты и посмотри diff в lock-файле, прежде чем мёржить.

18. Качество кода внутри сборки

Линтер и проверка типов не должны жить отдельным процессом: их подключают прямо к бандлеру, чтобы сборка падала на ошибках, а не пропускала их до продакшена:

npm install -D eslint-webpack-plugin fork-ts-checker-webpack-plugin
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

module.exports = {
  plugins: [
    new ESLintWebpackPlugin({ extensions: ["js", "ts", "vue"] }),
    new ForkTsCheckerWebpackPlugin()
  ],
  module: {
    rules: [{ test: /\.ts$/, use: [{ loader: "ts-loader", options: { transpileOnly: true } }] }]
  }
};
Подробнее о правилах — в курсах ESLint и TypeScript. Здесь важно: за компиляцию отвечают loaders, за качество — plugins.

19. Кроссбраузерность: browserslist и полифиллы

Webpack сам не знает, какие браузеры нужны вашему проекту. Список поддерживаемых версий передаётся через browserslist, а его читают Babel и Autoprefixer:

// package.json
"browserslist": ["defaults", "not IE 11", "last 2 Chrome versions"]
{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: "babel-loader",
    options: { presets: [["@babel/preset-env", { useBuiltIns: "usage", corejs: 3 }]] }
  }
}
npm i -D core-js autoprefixer postcss-loader
Явно проверяйте полифиллы для fetch, Promise и Object.entries для старых браузеров: Babel транспилирует синтаксис, но не все рантайм-API.

20. Кэш клиента: contenthash и runtimeChunk

Самая бесплатная оптимизация — правильные имена файлов. Когда бандл выходит с новым хэшем, браузер качает только изменившееся:

output: {
  filename: "[name].[contenthash:8].js",
  chunkFilename: "[name].[contenthash:8].chunk.js"
},
optimization: {
  moduleIds: "deterministic",
  chunkIds: "deterministic",
  runtimeChunk: "single"
}
// подключение длинноживущих чанков в index.html


Проверьте эффект: соберите дважды без изменений — хэши должны остаться одинаковыми. Если прыгают, значит в сборке есть недетерминизм.

21. Воспроизводимые сборки и чек-лист продакшена

«Локально собралось, а в CI упало» — следствие плавающего окружения. Три правила, которые делают сборку воспроизводимой:

# в CI вместо npm install
npm ci
# переиспользование кэша между джобами
cache: webpack filesystem cache
# артефакт для выкладки
upload-artifact: dist
ПроверкаЧем убедиться
Хэши стабильныдве сборки без изменений идентичны
Нет sourcemap в продеотключён devtool, кроме внутренних нужд
Нет ключей в бандлеgrep по env-плейсхолдерам не находит значений
Перед публичным релизом прогоняй npm audit и сверяй итоговые размеры с предыдущей версией — безопасность и перформанс проверяются здесь, а не на бою.

Что дальше

В новых проектах чаще используют Vite — курс Vite. Но Webpack-конфиг встретится в легаси, поэтому потренируйся: собери проект на React или Vue 3 с TypeScript, подключи Sass и ESLint, а результат задеплой на GitHub.

← Вернуться к курсам