Плагины (plugins)

Плагины в Webpack предназначены для расширения возможностей сборщика. Если loaders работают на уровне преобразования отдельных файлов, то plugins вмешиваются в процесс сборки целиком: изменяют выходные файлы, управляют оптимизацией, внедряют переменные окружения, очищают директории, генерируют HTML, анализируют бандлы и выполняют десятки других задач.

Плагин представляет собой JavaScript-класс или объект, подключаемый через секцию plugins в конфигурации Webpack.

Базовая структура:

module.exports = {
    plugins: [
        // экземпляры плагинов
    ]
};

Большинство плагинов подключаются через оператор new.

Пример:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin()
    ]
};

Отличие plugins от loaders

Loaders

Loaders обрабатывают конкретные файлы.

Примеры:

  • babel-loader
  • css-loader
  • sass-loader

Они работают через секцию module.rules.

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: 'babel-loader'
            }
        ]
    }
};

Plugins

Plugins влияют на весь процесс сборки.

Примеры задач:

  • генерация HTML;
  • минификация;
  • очистка папки dist;
  • извлечение CSS;
  • анализ размера бандла;
  • копирование файлов;
  • внедрение переменных окружения.

Секция plugins

Все плагины подключаются внутри массива plugins.

module.exports = {
    plugins: []
};

Пример подключения нескольких плагинов:

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

module.exports = {
    plugins: [
        new HtmlWebpackPlugin(),
        new MiniCssExtractPlugin()
    ]
};

Порядок подключения иногда имеет значение, особенно если плагины работают с одними и теми же ресурсами.


Установка плагинов

Большинство плагинов устанавливаются через npm.

Пример:

npm install html-webpack-plugin --save-dev

После установки плагин импортируется в конфигурацию.

const HtmlWebpackPlugin = require('html-webpack-plugin');

HtmlWebpackPlugin

Один из самых популярных плагинов Webpack.

Назначение:

  • автоматическое создание HTML;
  • автоматическое подключение JS-бандлов;
  • подключение CSS-файлов;
  • работа с шаблонами.

Установка

npm install html-webpack-plugin --save-dev

Базовое подключение

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin()
    ]
};

Webpack создаст HTML-файл автоматически.


Генерация HTML на основе шаблона

Чаще всего используется собственный шаблон.

Структура проекта:

src/
    index.js
    index.html

Конфигурация:

const HtmlWebpackPlugin = require('html-webpack-plugin');

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

Настройка имени выходного HTML

new HtmlWebpackPlugin({
    filename: 'main.html'
})

Результат:

dist/
    main.html

Передача параметров в шаблон

new HtmlWebpackPlugin({
    template: './src/index.html',
    title: 'Webpack Application'
})

В шаблоне:

<title><%= htmlWebpackPlugin.options.title %></title>

CleanWebpackPlugin

Используется для очистки папки сборки перед созданием новых файлов.

Без очистки старые файлы могут накапливаться в dist.

Установка

npm install clean-webpack-plugin --save-dev

Подключение

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    plugins: [
        new CleanWebpackPlugin()
    ]
};

MiniCssExtractPlugin

Извлекает CSS в отдельные файлы.

Без него стили часто встраиваются в JavaScript.

Установка

npm install mini-css-extract-plugin --save-dev

Подключение

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

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },

    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/style.css'
        })
    ]
};

DefinePlugin

Плагин для создания глобальных констант.

Чаще всего используется для переменных окружения.

Подключение

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.DefinePlugin({
            API_URL: JSON.stringify('https://api.site.com')
        })
    ]
};

Использование:

console.log(API_URL);

Работа с NODE_ENV

new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify('production')
})

CopyWebpackPlugin

Копирует файлы без обработки.

Подходит для:

  • изображений;
  • шрифтов;
  • статических файлов;
  • favicon;
  • robots.txt.

Установка

npm install copy-webpack-plugin --save-dev

Подключение

const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: 'public',
                    to: ''
                }
            ]
        })
    ]
};

CompressionWebpackPlugin

Создаёт gzip-версии файлов.

Установка

npm install compression-webpack-plugin --save-dev

Подключение

const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
    plugins: [
        new CompressionPlugin()
    ]
};

BundleAnalyzerPlugin

Используется для анализа размера бандла.

Позволяет определить:

  • слишком большие зависимости;
  • дублирование модулей;
  • неэффективные библиотеки.

Установка

npm install webpack-bundle-analyzer --save-dev

Подключение

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
    plugins: [
        new BundleAnalyzerPlugin()
    ]
};

После сборки открывается интерактивная диаграмма.


HotModuleReplacementPlugin

Обеспечивает горячую замену модулей без полной перезагрузки страницы.

Подключение

const webpack = require('webpack');

module.exports = {
    devServer: {
        hot: true
    },

    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

ProvidePlugin

Автоматически импортирует модули.

Пример с jQuery:

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        })
    ]
};

Теперь можно использовать:

$('.menu').hide();

Без явного импорта:

import $ from 'jquery';

IgnorePlugin

Позволяет исключать ненужные зависимости.

Пример:

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.IgnorePlugin({
            resourceRegExp: /^\.\/locale$/,
            contextRegExp: /moment$/
        })
    ]
};

Этот код исключает локализации библиотеки moment.


ProgressPlugin

Отображает прогресс сборки.

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.ProgressPlugin()
    ]
};

EnvironmentPlugin

Упрощённая версия DefinePlugin.

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.EnvironmentPlugin([
            'NODE_ENV'
        ])
    ]
};

TerserWebpackPlugin

Используется для минификации JavaScript.

Webpack 5 уже включает его автоматически в production-режиме, однако возможна ручная настройка.

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin()
        ]
    }
};

CssMinimizerWebpackPlugin

Минифицирует CSS.

Установка

npm install css-minimizer-webpack-plugin --save-dev

Подключение

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
    optimization: {
        minimizer: [
            '...',
            new CssMinimizerPlugin()
        ]
    }
};

'...' сохраняет стандартные minimizer-плагины Webpack.


FaviconsWebpackPlugin

Автоматически генерирует favicon разных размеров.

Установка

npm install favicons-webpack-plugin --save-dev

Подключение

const FaviconsWebpackPlugin = require('favicons-webpack-plugin');

module.exports = {
    plugins: [
        new FaviconsWebpackPlugin('./src/logo.png')
    ]
};

DotenvWebpack

Подгружает переменные из .env.

Установка

npm install dotenv-webpack --save-dev

Подключение

const Dotenv = require('dotenv-webpack');

module.exports = {
    plugins: [
        new Dotenv()
    ]
};

Комбинирование плагинов

В реальных проектах используется множество плагинов одновременно.

Пример production-конфигурации:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    mode: 'production',

    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },

    plugins: [
        new CleanWebpackPlugin(),

        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),

        new MiniCssExtractPlugin({
            filename: 'css/style.[contenthash].css'
        })
    ],

    optimization: {
        minimizer: [
            '...',
            new CssMinimizerPlugin()
        ]
    }
};

Плагины и режимы сборки

Часто плагины подключаются только в определённом режиме.

Пример:

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
    plugins: [
        ...(isProd ? [
            new MiniCssExtractPlugin()
        ] : [])
    ]
};

Условное подключение плагинов

const plugins = [];

if (process.env.NODE_ENV === 'production') {
    plugins.push(new MiniCssExtractPlugin());
}

module.exports = {
    plugins
};

Создание собственного плагина

Плагин представляет собой класс с методом apply.

Простейший пример:

class MyPlugin {

    apply(compiler) {
        console.log('Webpack plugin initialized');
    }

}

module.exports = MyPlugin;

Подключение:

const MyPlugin = require('./MyPlugin');

module.exports = {
    plugins: [
        new MyPlugin()
    ]
};

Объект compiler

compiler предоставляет доступ к внутренним механизмам Webpack.

Через него можно:

  • подписываться на события;
  • изменять процесс сборки;
  • анализировать модули;
  • создавать дополнительные файлы.

Хуки (hooks)

Webpack использует систему hooks.

Пример:

class MyPlugin {

    apply(compiler) {

        compiler.hooks.done.tap(
            'MyPlugin',
            () => {
                console.log('Build finished');
            }
        );

    }

}

Популярные хуки

beforeRun

Вызывается перед запуском сборки.

compiler.hooks.beforeRun.tap('Plugin', () => {});

run

Запуск compiler.

compiler.hooks.run.tap('Plugin', () => {});

emit

Перед записью файлов.

compiler.hooks.emit.tap('Plugin', compilation => {});

done

После завершения сборки.

compiler.hooks.done.tap('Plugin', stats => {});

Синхронные и асинхронные хуки

tap

Синхронный хук.

compiler.hooks.done.tap('Plugin', () => {});

tapAsync

Асинхронный вариант.

compiler.hooks.emit.tapAsync(
    'Plugin',
    (compilation, callback) => {
        callback();
    }
);

tapPromise

Работа через Promise.

compiler.hooks.emit.tapPromise(
    'Plugin',
    async compilation => {

    }
);

Генерация файлов внутри плагина

class BuildInfoPlugin {

    apply(compiler) {

        compiler.hooks.emit.tap(
            'BuildInfoPlugin',
            compilation => {

                compilation.assets['build.txt'] = {
                    source: () => 'Build completed',
                    size: () => 15
                };

            }
        );

    }

}

Ошибки при работе с плагинами

Забытый оператор new

Неправильно:

plugins: [
    HtmlWebpackPlugin()
]

Правильно:

plugins: [
    new HtmlWebpackPlugin()
]

Плагин установлен, но не импортирован

const HtmlWebpackPlugin = require('html-webpack-plugin');

Неверная последовательность плагинов

Некоторые плагины зависят друг от друга.


Конфликт loader и plugin

Например:

  • CSS loader работает неправильно без extraction plugin;
  • source map может ломаться из-за minimizer plugin.

Встроенные плагины Webpack

Webpack содержит собственные плагины:

  • DefinePlugin
  • ProvidePlugin
  • EnvironmentPlugin
  • HotModuleReplacementPlugin
  • IgnorePlugin
  • ProgressPlugin

Подключаются через объект webpack.

const webpack = require('webpack');

Практическая production-конфигурация

const path = require('path');

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

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {

    mode: 'production',

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

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/main.[contenthash].js'
    },

    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },

    plugins: [

        new CleanWebpackPlugin(),

        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),

        new MiniCssExtractPlugin({
            filename: 'css/style.[contenthash].css'
        })

    ],

    optimization: {
        minimizer: [
            '...',
            new CssMinimizerPlugin()
        ]
    }

};