Узнайте, как использовать вебпак для сборки JavaScript, изображений, шрифтов и стилей, а также как запускать сервер для разработки

Если вы раньше использовали вебпак 4, вот некоторые отличия от 5 версии:

  • команда «webpack-dev-server» теперь выглядит как «webpack-serve»
  • отдельная установка file-loader, raw-loader и url-loader больше не требуется, вы можете использовать встроенные загрузчики ресурсов (asset modules)
  • полифилы для Node.js больше не поддерживаются, поэтому если, например, вы получили ошибку для stream, необходимо добавить пакет «stream-browserify» в качестве зависимости и { stream: «stream-browserify» } в качестве алиаса в настройки вебпака

Что такое вебпак?


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

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

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

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

Установка


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

mkdir webpack-tutorial
cd !$
// или
cd webpack-tutorial
yarn init -yp  // создает стандартный приватный файл "package.json"
// или
npm init -y

Устанавливаем webpack и webpack-cli в качестве зависимостей для разработки:

yarn add -D webpack webpack-cli
// или
npm i -D webpack webpack-cli

  • webpack — сборщик модулей и ресурсов
  • webpack-cli — интерфейс командной строки для вебпака

Создаем директорию «src» для хранения файлов приложения. Я начну с создания простого файла «index.js»:

console.log("Как интересно!")

Отлично, у нас имеется Node.js-проект с установленными основными пакетами и файл «index.js». Займемся настройкой вебпака.

Базовая настройка


Приступим к настройке сборщика. Создаем файл «webpack.config.js» в корневой директории проекта.

Точка входа

Прежде всего, необходимо определить точку входа приложения, т.е. то, какие файлы вебпак будет компилировать. В приведенном примере мы определяем точку входа как «src/index.js»:

// webpack.config.js
const path = require('path')

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

Точка выхода

Точка выхода — это директория, в которую помещаются скомпилированные вебпаком файлы. Установим точку выхода как «dist». Префикс "[name]" соответствует названию файла в src:

// webpack.config.js
module.exports = {
    // ...

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

Минимальная настройка для сборки проекта готова. Добавляем скрипт «build» в файл «package.json», запускающий команду «webpack»:

// package.json
"scripts": {
    "build": "webpack"
}

Запускаем вебпак:

yarn build
// или
npm run build

asset main.bundle.js 19 bytes [emitted] [minimized] (name: main)
./src/index.js 18 bytes [built] [code generated]
webpack 5.1.0 compiled successfully in 152 mss

В директории «dist» создается файл «index.bundle.js». Файл не изменился, но мы успешно осуществили сборку проекта.

Плагины


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

Плагин создания HTML на основе шаблона

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


Устанавливаем плагин:

yarn add -D html-webpack-plugin

Создаем файл «template.html» в директории «src». Мы можем добавить в шаблон переменные и другую информацию. Добавим переменную «title», в остальном шаблон будет выглядеть как обычный HTML-файл с контейнером с идентификатором «root»:

<!-- src/template.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

  <body>
    <div id="root"></div>
  </body>
</html>

Добавляем в настройки вебпака свойство «plugins», где определяем плагин, название выходного файла (index.html) и шаблон:

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    // ...

    plugins: [
        new HtmlWebpackPlugin({
            title: 'webpack Boilerplate',
            template: path.resolve(__dirname, './src/template.html'), // шаблон
            filename: 'index.html', // название выходного файла
        }),
    ],
}

Запускаем сборку. Директория «dist» теперь содержит файл «index.html» с подключенным в нем скриптом. Круто! Если вы откроете этот файл в браузере, то увидите сообщение «Как интересно!» в консоли.

Добавим немного контента в DOM. Изменим содержимое файла «index.js» и перезапустим сборку.

// index.js
// создаем элемент заголовка
const heading = document.createElement('h1')
heading.textContent = 'Как интересно!'

// добавляем заголовок в DOM
const root = document.querySelector('#root')
root.append(heading)

Перейдите в директорию «dist» и запустите сервер (для этого необходимо глобально установить http-server: yarn global add http-server или npm i -g http-server).

http-server

В открывшейся вкладке браузера вы должны увидеть заголовок, гласящий «Как интересно!». Также обратите внимание на уменьшение размера файла.

Очистка

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

  • clean-webpack-plugin — удаляет/очищает директорию сборки проекта

// webpack.config.js
const HtmlWebpackPlugin = require('html=webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    // ...

    plugins: [
        // ...
        new CleanWebpackPlugin(),
    ],
}

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


Вебпак использует загрузчики для разбора файлов, загружаемых с помощью модулей. Это могут быть JavaScript-файлы, статические ресурсы, такие как изображения или стили и компиляторы, такие как TypeScript и Babel. Вебпак 5 имеет несколько встроенных загрузчиков ресурсов.

В нашем проекте имеется HTML-файл, который загружает некоторый скрипт, но больше он ничего не делает. Давайте подумаем, чего мы хотим от сборщика?

  • Компиляция новейших возможностей JavaScript в код, совместимый со всеми или большинством браузеров
  • Импорт стилей и преобразование SCSS в CSS
  • Импорт изображений и шрифтов
  • Настройка React или Vue (опционально)

Для начала настроим Babel для компиляции JavaScript.

Babel (JavaScript)

Babel — это инструмент, позволящий использовать будущий JavaScript сегодня.

Мы собираемся определить правило, согласно которому все файлы с расширением «js» в проекте (кроме файлов, содержащихся в директории «node_modules») будут транспилироваться с помощью babel-loader. Для работы Babel требуется несколько зависимостей:

  • babel-loader — транспиляция файлов с помощью Babel и вебпака
  • @babel/core — транспиляция ES2015+ в обратно совместимый JavaScript
  • @babel/preset-env — полезные стандартные настройки Babel
  • @babel/plugin-proposal-class-properties — пример кастомной конфигурации Babel (установка свойств экземпляров в теле класса, а не в его конструкторе)

yarn add -D babel-loader @babel/core @babel/preset-env @babel/babel-plugin-proposal-class-properties

// webpack.config.js
module.exports = {
    // ...

    module: {
        rules: [
            // JavaScript
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ['babel-loader'],
            },
        ],
    }
}

Если вы настраиваете TypeScript-проект, то вместо babel-loader следует использовать typescript-loader для всех JavaScript-файлов, нуждающихся в транспиляции. Вы проверяете файлы с расширением «ts» и используете ts-loader.

Итак, Babel настроен, но плагин еще нет. Вы можете убедиться в этом, добавив следующий код в начало index.js:

// index.js
// создание свойства класса без конструктора
class Game {
    name = 'Violin Charades'
}
const myGame = new Game()

// создаем параграф
const p = document.createElement('p')
p.textContent = `I like ${myGame.game}.`

// создаем элемент заголовка
const heading = document.createElement('h1')
heading.textContent = 'Как интересно!'

// добавляем параграф и заголовок в DOM
const root = document.querySelector('#root')
root.append(heading, p)

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/you/webpack-tutorial/src/index.js: Support for the experimental syntax 'classProperties' isn't currently enabled (3:8):

  1 | // Create a class property without a constructor
  2 | class Game {
> 3 |   name = 'Violin Charades'
    |        ^
  4 | }

Для того, чтобы это исправить, создаем файл ".babelrc" в корне проекта:

// .babelrc
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Запускаем сборку с помощью yarn build. Теперь все работает.

Изображения

Мы хотим импортировать изображения в JavaScript-файл, но JavaScript не умеет этого делать. Чтобы убедиться в этом, создаем директорию «src/images», помещаем туда изображение и пытаемся импортировать его в файле «index.js»:

// index.js
import example from './images/example.png'

// ...

При запуске сборки будет выброшено исключение:

ERROR in ./src/images/example.png 1:0
Module parse failed: Unexpected character '?' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

Как отмечалось ранее, вебпак обладает некоторыми встроенными загрузчиками для работы со статическими файлами. Для изображений следует использовать тип «asset/resource». Обратите внимание, что речь идет именно о типе (type), а не о загрузчике (loader):

// webpack.config.js
module.exports = {
    // ...

    module: {
        rules: [
            // изображения
            {
                test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
                type: 'asset/resource',
            },
        ],
    },
}

В директории «dist» появляется новый файл.

Шрифты и другие встраиваемые данные

Вебпак также имеет встроенный модуль для обработки некоторых встраеваемых данных, таких как шрифты и SVG. Для этого достаточно указать тип «asset/inline»:

// index.js
import example from './images/example.svg'

// ...

// webpack.config.js
module.exports = {
    // ...

    module: {
        rules: [
            // шрифты и SVG
            {
                test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
                type: 'asset/inline',
            },
        ],
    },
}

Стили

Использование загрузчиков стилей является необходимым условием использования строк наподобие «import 'file.css'» в скрипте.

Многие люди используют CSS-in-JS, стилизованные компоненты (styled components) и другие инструменты, позволяющие использовать стили в JavaScript.

Порой мы можем ограничиться загрузкой одного CSS-файла. Но, возможно, вы хотите использовать PostCSS, позволяющий использовать последние возможности CSS в браузере. Или вы хотите использовать препроцессор Sass.

Я хочу использовать все три — писать код на Sass, обрабатывать его с помощью PostCSS и компилировать в CSS.


yarn add -D sass-loader postcss-loader css-loader style-loader postcss-preset-env node-sass

Как и для Babel, для PostCSS требуется отдельный файл настроек:

// postcss.config.js
module.exports = {
    plugins: {
        'post-css-preset-env': {
            browsers: 'last 2 versions',
        },
    },
}

Для проверки работоспособности названных инструментов создадим файл «src/styles/main.scss», содержащий переменные Sass и пример использования PostCSS (lch):

// src/styles/main.css
$font-size: 1rem;
$font-color: lch(53 105 40);

html {
    font-size: $font-size;
    color: $font-color;
}

Импортируем этот файл в index.js и добавляем 4 загрузчика. Загрузчики используются вебпаком справа налево, так что последним должен быть sass-loader, затем PostCSS, затем CSS и, наконец, style-loader, который применяет скомпилированные стили к элементам DOM:

// index.js
import './styles/main.css'

// ...

// webpack.config.js
module.exports = {
    // ...

    module: {
        rules: [
            // CSS, PostCSS, Sass
            {
                test: /\.(scss|css)$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
            },
        ],
    },
}

После сборки вы заметите, что Sass и PostCSS применились к DOM.

Обратите внимание, что мы установили настройки для режима разработки. Для продакшна следует использовать MiniCssExtractPlugin вместо style-loader, который экспортирует минифицированный CSS.

Разработка


Каждый раз набирать команду yarn build (npm run build) при необходимости пвторной сборки проекта может быть утомительным. Чем больше проект, тем дольше он будет собираться. Поэтому необходимо иметь два файла настроек вебпака:

  • Настройки для продакшна, включающие минификацию, оптимизацию и удаление всех карт ресурсов (source maps)
  • Настройки для разработки, включая запуск сервера, обновление при каждом изменении и карты ресурсов

Вместо создания директории «dist», режим разработки может предполагать хранение нужной информации в памяти.

Для этого необходимо установить webpack-dev-server.


yarn add -D webpack-dev-server

В целях демонстрации принципов использования сервера для разработки мы можем определить соответствующие настройки в файле «webpack.config.js». На практике лучше иметь два файла настроек: один с mode: production и другой с mode: development. В специально подготовленной для вас webpack 5 boilerplate я использую webpack-merge для получения базовых настроек в виде одного файла, а специальные требования содержатся в файлах «webpack.prod.js» и «webpack.dev.js».

// webpack.config.js
const webpack = require('webpack')

module.exports = {
    // ...
    mode: 'development',
    devServer: {
        historyApiFallback: true,
        contentBase: path.resolve(__dirname, './dist'),
        open: true,
        compress: true,
        hot: true,
        port: 8080,
    },
    plugins: [
        // ...
        // применять изменения только при горячей перезагрузке
        new webpack.HotModuleReplacement(),
    ],
}

Мы добавили mode: development и свойство «devServer». В данном свойстве содержится несколько стандартных настроек — номер порта (8080), автоматическое открытие браузера, использование hot-module-replacement, для которого нужен webpack.HotModuleReplacement(). Это позволит модулям обновляться без полной перезагрузки страницы, т.е. если изменятся отдельные стили, только они будут обновлены, вам не потребуется перезагружать JavaScript, что очень сильно ускоряет разработку.

Для запуска сервера используется команда «webpack serve»:

// package.json
"scripts": {
    "start": "webpack serve"
}

yarn start

После запуска этой команды браузер откроется по адресу localhost:8080. Теперь вы можете изменять Sass и JavaScript и они будут обновляться на лету.

Заключение


Надеюсь, данная статья помогла вам начать знакомство с вебпаком. Для облегчения решения ваших повседневных задач мной разработан webpack 5 boilerplate с Babel, Sass, PostCss, оптимизацией для продакшна и сервером для разработки. Взяв его за основу, вы легко сможете настроить вебпак для работы с React, Vue или TypeScript.