Webpack

Зачем нужно

Webpack — модульный сборщик (bundler) для JavaScript-приложений. Он берёт все файлы проекта (JS, CSS, изображения, шрифты) и превращает их в оптимизированные бандлы для браузера. Webpack решает проблему модульности: браузер не понимает import/export, сотни файлов, npm-пакеты — Webpack собирает всё в один или несколько файлов.

Где используется

  • Сборка SPA (React, Vue, Angular проекты)
  • Код с ES-модулями, TypeScript, JSX, SCSS
  • Оптимизация для production (минификация, tree shaking)
  • Разработка с hot reload (dev server)

Основные концепции

Entry → Loaders → Plugins → Output

src/index.js ──→ babel-loader ──→ HtmlWebpackPlugin ──→ dist/bundle.js
src/style.css ─→ css-loader ───→ MiniCssExtract ─────→ dist/style.css
src/logo.png ──→ file-loader ──→ CopyPlugin ─────────→ dist/logo.png

Entry (точка входа)

// webpack.config.js
module.exports = {
  // Откуда начинать сборку
  entry: './src/index.js',

  // Несколько точек входа
  // entry: {
  //   app: './src/app.js',
  //   admin: './src/admin.js',
  // },
};

Output (выход)

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    // Куда складывать результат
    path: path.resolve(__dirname, 'dist'),
    // Имя файла ([name] = имя entry, [contenthash] = хеш содержимого)
    filename: '[name].[contenthash].js',
    // Очищать dist перед сборкой
    clean: true,
  },
};

Mode (режим)

module.exports = {
  // 'development' — быстрая сборка, source maps, без минификации
  // 'production' — минификация, tree shaking, оптимизации
  mode: 'production',
};

Loaders (загрузчики)

Webpack из коробки понимает только JS и JSON. Loaders учат его обрабатывать другие типы файлов:

module.exports = {
  module: {
    rules: [
      // JavaScript/JSX через Babel
      {
        test: /\.jsx?$/,          // Regex — какие файлы
        exclude: /node_modules/,  // Исключения
        use: 'babel-loader',      // Какой loader
      },

      // CSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
        // Порядок СПРАВА → НАЛЕВО:
        // 1. css-loader — разбирает CSS, обрабатывает import
        // 2. style-loader — вставляет CSS в <style> тег
      },

      // Изображения (встроенная поддержка Webpack 5)
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource',
      },

      // Шрифты
      {
        test: /\.(woff|woff2|eot|ttf)$/,
        type: 'asset/resource',
      },
    ],
  },
};

Plugins (плагины)

Plugins расширяют возможности Webpack на этапе сборки:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    // Генерирует HTML с подключёнными бандлами
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),

    // Извлекает CSS в отдельный файл
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
};

Полный конфиг

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, argv) => {
  const isDev = argv.mode === 'development';

  return {
    entry: './src/index.js',

    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isDev ? '[name].js' : '[name].[contenthash].js',
      clean: true,
    },

    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: 'babel-loader',
        },
        {
          test: /\.css$/,
          use: [
            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
            'css-loader',
          ],
        },
        {
          test: /\.(png|jpg|gif|svg)$/,
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024, // <8kb → inline base64
            },
          },
        },
      ],
    },

    plugins: [
      new HtmlWebpackPlugin({ template: './src/index.html' }),
      !isDev && new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
      }),
    ].filter(Boolean),

    devServer: {
      port: 3000,
      hot: true,                 // Hot Module Replacement
      historyApiFallback: true,  // SPA routing
      open: true,                // Открыть браузер
    },

    devtool: isDev ? 'eval-source-map' : 'source-map',

    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
      alias: {
        '@': path.resolve(__dirname, 'src'),
      },
    },
  };
};

Dev Server

# Установка
npm install --save-dev webpack-dev-server

# Запуск
npx webpack serve --mode development
// package.json
{
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production",
    "build:analyze": "webpack --mode production --env analyze"
  }
}

Code Splitting

Разделение бандла на части для ленивой загрузки:

// === Динамический import ===
// Webpack автоматически создаёт отдельный chunk

button.addEventListener('click', async () => {
  // chart.js загрузится только по клику
  const { Chart } = await import('./chart.js');
  new Chart(data);
});

// === Именование chunks ===
const module = await import(
  /* webpackChunkName: "chart" */
  './chart.js'
);

// === SplitChunks — общий код в отдельный файл ===
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // Выделить общие модули
      cacheGroups: {
        vendor: {
          test: /node_modules/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
};

Tree Shaking

Удаление неиспользуемого кода:

// utils.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { return a / b; }

// index.js — используем только add
import { add } from './utils.js';
console.log(add(1, 2));

// В production бандле multiply и divide будут УДАЛЕНЫ
// Условия: ES-модули (import/export), mode: 'production'
// package.json — подсказка Webpack что модуль "чистый"
{
  "sideEffects": false
  // или указать файлы с побочными эффектами:
  // "sideEffects": ["*.css", "./src/polyfills.js"]
}

Частые ошибки

  1. Не ставят mode — по умолчанию production, но без явного указания Webpack предупреждает
  2. Порядок loadersuse: ['style-loader', 'css-loader'] — выполняется СПРАВА→НАЛЕВО
  3. Нет contenthash в production — браузер кэширует старые файлы, пользователь видит устаревший код
  4. Не исключают node_modules — babel-loader обрабатывает зависимости (медленно и не нужно)
  5. Всё в одном бандле — нет code splitting, пользователь загружает весь код сразу
  6. CommonJS вместо ESM — tree shaking не работает с require/module.exports

Практика

  1. Настроить Webpack с нуля: entry, output, babel-loader, css-loader
  2. Добавить HtmlWebpackPlugin и dev server
  3. Реализовать code splitting с динамическим import
  4. Настроить splitChunks для выделения vendor-бандла
  5. Проанализировать бандл с webpack-bundle-analyzer

Связанные темы

Ресурсы