Webpack. Начало работы, часть 1 из 2

06.07.2022

Теги: FrontendJavaScriptNode.jsWeb-разработкаКонфигурацияНастройкаУстановкаШаблонСайта

По большей части, сайты больше не пишутся на чистом HTML с небольшим количеством JavaScript — часто они создаются только с помощью JavaScript. Поэтому возникает необходимость в сборке, минификации и транспиляции кода. Вот здесь webpack и приходит на помощь разработчику.

Вебпак — это сборщик модулей. Он служит для упаковки кода для дальнейшего использования браузером. Позволяет использовать последние возможности JavaScript с помощью Babel или использовать TypeScript и преобразовывать его в кроссбраузерный минифицированный код. Он также позволяет импортировать статические ресурсы в JavaScript.

Для разработчиков webpack также предоставляет сервер для разработки, который умеет обновлять модули и стили на лету при сохранении файла. Инструменты командной строки, такие как «vue create» и «create-react-app» используют вебпак за сценой, но можно создать собственную настройку webpack для указанных фреймворков.

Создание проекта

Создаем директорию проекта и инициализируем его:

$ mkdir webpack-config-example
$ cd webpack-config-example
$ npm init -y

Немного подредактируем файл package.json:

{
    "name": "webpack-config-example",
    "version": "1.0.0",
    "description": "Webpack config file example",
    "private": true,
    "scripts": {
        "build": "webpack"
    },
    "keywords": ["webpack", "config", "javascript", "frontend"],
    "author": "Evgeniy Tokmakov",
    "license": "ISC"
}

Устанавливаем два пакета как зависимости для разработки:

$ npm install webpack webpack-cli --save-dev

Создаем директорию src, а внутри нее — директории js, css, img, font, data. Внутри src/js создаем файл index.js.

console.log('Webpack config file example')

Создаем файл webpack.config.js в корневой директории проекта. Файл экспортирует объект, который содержит все настройки сборщика.

module.exports = {}

Точка входа

Сколько бы модулей не содержало приложение, всегда имеется единственная точка входа.

const path = require('path')

module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src/js/index.js'),
    },
}
const path = require('path')

module.exports = {
    // для одной точки входа код можно сократить
    entry: path.resolve(__dirname, 'src/js/index.js'),
}

Точка выхода

Точка выхода — это директория, в которую помещаются сформированные вебпаком файлы.

const path = require('path')

module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src/js/index.js'),
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
}
Значение по умолчанию точки входа — ./src/index.js, значение по умолчанию точки выхода — ./dist/main.js.

Когда есть несколько точек входа, то файл dist/bundle.js будет перезаписан, потому что имя одинаковое. В этом случае можно использовать шаблон, чтобы использовать ключ объекта entry в имени выходного файла.

module.exports = {
    entry: {
        app: '.....',
        admin: '.....',
    },
    output: {
        filename: '[name].bundle.js',
        path: __dirname + '/dist',
    },
}
// выходные файлы ./dist/app.js и ./dist/admin.js

В принципе, минимальная конфигурация готова, так что можно запустить сборку:

$ npm run build

Мы получим предупреждение, что не установлен режим — development или production.

module.exports = {
    mode: 'development',
    /* .......... */
}

Наблюдение за файлами

При использовании опции watch вебпак будет отслеживать изменения файлов и повторно запускать сборку.

module.exports = {
    mode: 'development',
    watch: true,
    watchOptions: {
        ignored: /node_modules/, // не отслеживать node_modules
        poll: 1000, // проверять изменения каждую секунду
    },
    /* .......... */
}
Чаще всего нет необходимости в использовании опции watchOptions.poll для наблюдения за файлами. Эта опция помогает решить проблемы с наблюдением при спользовании NFS, VirtualBox и Docker.

Плагины

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

1. Плагин html-webpack-plugin

У нас есть готовая сборка, но она бесполезна без html-разметки, которая загрузит сборку в качестве скрипта. Поскольку нам нужно, чтобы такой html-файл генерировался автоматически, используем плагин html-webpack-plugin.

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

Создаем файл фаблона index.html в директории src. В шаблон добавляем две переменные, которые определим в настройках плагина.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <h1><%= htmlWebpackPlugin.options.title %></h1>
    <pre><%= htmlWebpackPlugin.options.content %></pre>
</body>
</html>
const path = require('path')
const fs = require('fs')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: 'development',
    entry: {
        main: path.resolve(__dirname, 'src/js/index.js'),
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js',
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Webpack config example',
            content: fs.readFileSync('webpack.config.js', 'utf8'),
            template: path.resolve(__dirname, 'src/index.html'), // файл шаблона
            filename: 'index.html', // выходной файл
        }),
    ],
}

Запускаем сборку. Директория dist теперь содержит файл index.html с подключенным в нем скриптом main.bundle.js и подставленными значениями переменных.

2. Плагин clean-webpack-plugin

Установим clean-webpack-plugin, очищающий директорию dist при каждой сборке проекта. Это позволяет автоматически удалять старые файлы, ставшие ненужными.

$ npm install clean-webpack-plugin --save-dev
/* .......... */
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    /* .......... */
    plugins: [
        /* .......... */
        new CleanWebpackPlugin(),
    ],
}

Но этот плагин уже устарел, потому что вебпак сам умеет очищать директорию dist с помощью опции output.clean:

module.exports = {
    /* .......... */
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js',
        clean: true,
    },
    /* .......... */
}

3. Плагин copy-webpack-plugin

Копирует отдельные файлы и целые директории, которые уже существуют, в выходную директорию проекта. Давайте разместим в директории src иконку favicon.png, добавим в html-шаблон ссылку на эту иконку, и скопируем файл при сборке в директорию dist.

$ npm install copy-webpack-plugin --save-dev
/* .......... */
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
    /* .......... */
    plugins: [
        /* .......... */
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, 'src/favicon.png'),
                    to: path.resolve(__dirname, 'dist')
                },
            ],
        }),
    ],
}
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <link rel="icon" href="favicon.png" type="image/png">
</head>
<body>
    <h1><%= htmlWebpackPlugin.options.title %></h1>
    <pre><%= htmlWebpackPlugin.options.content %></pre>
</body>
</html>

Модули и загрузчики

По умолчанию, при создании графа зависимостей на основе import или require, вебпак способен обрабатывать только javascript и json файлы. Для работы с css-стилями, изображениями, шрифтами — предназначены модули и загрузчики. Загрузчики описывают, как обрабатывать те файлы, с которыми webpack не умеет работать — и включать их в конечную сборку.

1. Загрузчик raw-loader

Позволяет импортировать файл как строку. Давайте создадим файл src/data/webpack.txt и запишем в него строку «Это пример файла конфигурации Webpack». Установим загрузчик raw-loader, пропишем его в файле конфигурации, потом в файле src/js/index.js импортируем файл как строку и покажем на странице.

$ npm install raw-loader --save-dev
module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                use: 'raw-loader',
                type: 'javascript/auto'
            },
        ],
    }
}
import data from '../data/webpack.txt'

document.body.insertAdjacentHTML('beforeend', `<p>${data}</p>`)

Обратите внимание на type в значении javascript/auto — в пятой версии Webpack нет необходимости использовать внешние загрузчики raw-loader, file-loader, url-loader. При использовании этих устаревших загрузчиков нужно установить type в значение javascript/auto, чтобы избежать повторной обработки встроенными загрузчиками вебпака.

И давайте посмотрим, как импортировать строку с использованием встроенного загрузчика, без установки raw-loader:

module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
        ],
    }
}

2. Загрузчик file-loader

Позволяет импортировать файл (возвращая URL этого файла) и отправляет этот файл в выходной каталог. Давайте поместим файл логотипа logo.png в директории src/img, установим загрузчик file-loader, пропишем его в файле конфигурации, а потом покажем изображение на странице.

$ npm install file-loader --save-dev
module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
            {
                test: /\.(png|jpe?g|gif|webp)$/i, // изображение
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            outputPath: 'img' // все изображения в dist/img
                        },
                    },
                ],
                type: 'javascript/auto'
            },
        ],
    }
}
import data from '../data/webpack.txt'
import logo from '../img/logo.png'

document.body.insertAdjacentHTML('beforeend', `<p>${data}</p>`)
document.body.insertAdjacentHTML('afterbegin', `<img src="${logo}" alt="">`)

И давайте посмотрим, как импортировать строку с использованием встроенного загрузчика, без установки file-loader:

module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
            {
                test: /\.(png|jpe?g|gif|webp)$/i, // изображение
                type: 'asset/resource',
                generator: {
                    filename: 'img/[hash][ext][query]'
                }
            },
        ],
    }
}

3. Загрузчик url-loader

Загрузчик url-loader работает как file-loader, но может возвращать DataURL вместо URL, если размер файла меньше заданного ограничения в байтах. Давайте поместим маленькое изображение icon.png в директорию src/img, установим загрузчик url-loader, пропишем его в файле конфигурации, а потом покажем это изображение на странице.

module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
            {
                test: /\.(png|jpe?g|gif|webp)$/i, // изображение
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            outputPath: 'img', // все изображения в dist/img
                        },
                    },
                ],
                type: 'javascript/auto'
            },
        ],
    }
}
import data from '../data/webpack.txt'
import logo from '../img/logo.png'
import icon from '../img/icon.png'

document.body.insertAdjacentHTML('beforeend', `<p>${data}</p>`)
document.body.insertAdjacentHTML('afterbegin', `<img src="${logo}" alt="">`)
document.body.insertAdjacentHTML('beforeend', `<img src="${icon}" alt="">`)

Загрузчик file-loader больше не нужен, поскольку его работу теперь выполняет url-loader. В результате на странице у нас будут два изображения:

<img src="/img/56ee55657feb4c5d509da1359ac4a419.png" alt="">
..........
<img src="data:image/png;base64,iVBORw0KGgoAAAAN..." alt="">

И давайте посмотрим, как импортировать изображение с использованием встроенного загрузчика, без установки url-loader:

module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
            {
                test: /\.(png|jpe?g|gif|webp)$/i, // изображение
                type: 'asset',
                generator: {
                    filename: 'img/[hash][ext][query]'
                }
            },
        ],
    }
}
<img src="/img/56ee55657feb4c5d509d.png" alt="">
..........
<img src="data:image/png;base64,iVBORw…" alt="">

При использовании asset, если размер файла больше 8кб, то применяется asset/resource (изображение в директории dist/img), в противном случае — asset/inline (изображение в атрибуте src="…"). Ограничение на размер файла можно задать самому, если значение по умолчанию не подходит.

module.exports = {
    /* .......... */
    module: {
        rules: [
            {
                test: /\.txt$/i, // простой текст
                type: 'asset/source'
            },
            {
                test: /\.(png|jpe?g|gif|webp)$/i, // изображение
                type: 'asset',
                generator: {
                    filename: 'img/[hash][ext][query]'
                }
                parser: {
                    dataUrlCondition: {
                        maxSize: 4096 // ограничение 4kb
                    }
                }
            },
        ],
    }
}
<img src="/img/56ee55657feb4c5d509d.png" alt="">
..........
<img src="/img/cb1ea424ee012d4721cf.png" alt="">
Если использовать встроенный загрузчик asset/inline — то все изображения будут DataURL, но это не слишком удачное решение.

4. Загрузчики style-loader и css-loader

Загрузчик css-loader позволяет импортировать css-файл, а загрузчик style-loader динамически добавляет в выходной html-файл тег <style>. Давайте установим эти два загрузчика, пропишем их в файле конфигурации, создадим css-файл style.css в директории src/css и импортируем стили в файле src/js/index.js.

$ npm install css-loader --save-dev
$ npm install style-loader --save-dev
body {
    margin: 0;
    padding: 0;
    background-color: #ccc;
}
module.exports = {
    /* .......... */
    module: {
        rules: [
            /* .......... */
            {
                test: /\.css$/, // файл css-стилей
                use: ['style-loader', 'css-loader']
            }
        ],
    }
}
import data from '../data/webpack.txt'
import logo from '../img/logo.png'
import icon from '../img/icon.png'
import '../css/style.css'

document.body.insertAdjacentHTML('beforeend', `<p>${data}</p>`)
document.body.insertAdjacentHTML('afterbegin', `<img src="${logo}" alt="">`)
document.body.insertAdjacentHTML('beforeend', `<img src="${icon}" alt="">`)

5. Загрузчик postcss-loader

Загрузчик postcss-loader использует процессор PostCSS для трансформации css-стилей. Сам PostCSS никаких трансформаций не делает, только создает из css-стилей js-объекты, а потом из js-объектов — обратно css-стили. Трансформацией занимаются плагины для PostCSS — в момент, когда созданы js-объекты из входных стилей, но до момента, когда еще не созданы выходные стили. Трансформации могут быть самые разные, например — добавление префиксов к css-правилам с помощью плагина autoprefixer.

Устанавливаем postcss-loader и postcss-preset-env (полезные настройки для процессора postcss):

$ npm install postcss-loader postcss-preset-env --save-dev
module.exports = {
    /* .......... */
    module: {
        rules: [
            /* .......... */
            {
                test: /\.css$/, // файл css-стилей
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: {
                                    'postcss-preset-env': {
                                        browsers: 'last 3 versions',
                                    },
                                },
                            },
                        },
                    },
                ],
            },
        ],
    }
}
/* входные стили */
body {
    margin: 0;
    padding: 0;
    background-color: #ccc;
    box-sizing: border-box;
}
/* выходные стили */
body {
    margin: 0;
    padding: 0;
    background-color: #ccc;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

Поиск: Frontend • JavaScript • Node.js • Web-разработка • Конфигурация • Настройка • Установка • Шаблон сайта • Webpack • Сборка

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.