По мотивам серии статей Webpack From Zero to Hero

Я очень долго боялась webpack и всячески оттягивала его изучение. Как оказалось, зря – он совсем не страшный. Теперь я хочу убедить в этом вас, всех тех, кто все еще боится.

Вместо долгих вступлений прыгнем сразу в пекло сборки модулей. Создайте пустую директорию и назовите ее как-нибудь (например, i-am-not-afraid-of-webpack). Здесь мы будем знакомиться с популярным инструментом.

Без конфигурации

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

Внутри вашей директории создайте папку src и положите в нее файл index.js. В нем вы можете написать что угодно, например, вот эти бессмысленные строки:

const hello = subject => console.log(`Hello, ${subject}!`);
hello('Dolly');

Примечание: важно соблюдать предложенную мной структуру файлов (src/index.js). Почему, станет понятно очень скоро.

Теперь создадим в нашей папке комфортную среду для разработки. Начнем с файла package.json. Чтобы создать его, воспользуйтесь командой yarn init -y (npm init -y).

Примечание: здесь и далее я буду использовать пакетный менеджер yarn, но вы с таким же успехом можете применять npm. На всякий случай буду в скобочках писать альтернативную команду.

Теперь установим сам webpack и инструмент webpack-cli для работы с ним из терминала.

yarn add webpack webpack-cli --dev
(npm i -D webpack webpack-cli)

А теперь просто запустим webpack!

yarn webpack
(npx webpack)

В консоли появилось предупреждение (терпение, мы к нему очень скоро вернемся), но тем не менее работа выполнена. У нас появилась новая папка dist, а в ней файл main.js, в котором вы найдете минифицированную версию вашего index.js.

Что произошло?

Webpack взял исходник, который вы ему предоставили, и «собрал» его в один файл. Пока ничего впечатляющего: один исходник – один файл результата, но это лишь начало, поверьте.

Как webpack узнал, где искать ваш исходник? Очень просто, он по умолчанию начинает с файла src/index.js. Это конвенция, которая позволяет нам не говорить лишних слов, а просто сразу начинать работать. Точно так же по умолчанию webpack кладет получившуюся сборку в dist/main.js.

Давайте теперь вынесем функцию hello в отдельный файл, чтобы webpack мог показать, на что он действительно способен.

export default (subject) => console.log(`Hello, ${subject}!`);
import hello from './hello.js';
hello('Dolly');

Запустите webpack (yarn webpack), и вы получите тот же самый результат!

Таким образом, главный талант webpack – собирать отдельные модули в один большой бандл. Но это не единственное, что он может.

Режимы сборки

Вернемся к предупреждению, которое вы получили в консоли:

От нас требуют какую-то опцию mode.

Дело в том, что webpack не просто тупо копирует содержимое файлов в один бандл, но и еще проводит над ним некоторые действия, например, минифицирует. Просто загляните в dist/main.js – тут все аккуратненько сжато. Это очень удобно для кода, который должен быть выложен на продакшн. И webpack по умолчанию делает именно продакшн-сборку. Но можно изменить это поведение, указав тип конфигурации напрямую:

yarn webpack --mode development
(npx webpack --mode development)

Теперь посмотрите на dist/main.js – он изменился. Это сборка для разработки, гораздо более читаемая, но и гораздо менее оптимизированная.

Всего есть три варианта опции mode: production, development и none.

NPM-скрипты

yarn webpack --mode development – это уже довольно длинная команду, которую не хочется набирать много раз. Давайте сразу перенесем ее в package.json, а то она ведь может и разрастись.

Просто добавьте секцию scripts, если ее еще нет, и пару новых скриптов:

"scripts": {        
  "build": "webpack --mode production",        
  "build:dev": "webpack --mode development"
}

Теперь можно вызывать эти команды гораздо проще:

yarn build 
(npm run build)

и

yarn build:dev
(npm run build:dev)

Обработка файлов

Итак, наши JS-модули уже собираются в один бандл, но ведь этого нам недостаточно. Как минимум нужно преобразовать формат ES6, в котором мы пишем наши скрипты, в формат ES5 – менее классный, но зато понятный всем браузерам.

Webpack этого не умеет, увы. Ну и что теперь, расходимся? Конечно, нет, webpack не умеет это сам, но знает тех, кто умеет. Познакомьтесь – лоадеры.

Лоадеры – это скрипты, которые принимают на вход содержимое файла, обрабатывают его и возвращают красивый и удобный результат. Webpack мастерски умеет работать с этими ребятами. От вас требуется только выбрать подходящий лоадер и указать, какой тип файлов он должен обрабатывать.

Начнем с самого важного – транспиляции ES6 в ES5. Знаете, кто умеет это делать? Babel. Нам нужно просто подключить babel-loader в webpack. Ну, не только babel-loader…

yarn add @babel/core @babel/preset-env babel-loader --dev
(npm i -D @babel/core @babel/preset-env babel-loader)
  • babel core — содержит всю логику транспиляции;
  • babel preset env — основной набор плагинов с реализацией возможностей javascript, учитывающих поддержку нужных браузеров;
  • babel loader — собственно лоадер, обеспечивающий взаимодействие с webpack.

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

{  
  "presets": [
    "@babel/preset-env"
  ]
}

А в файл package.json добавьте указание на то, какие браузеры вы хотите поддерживать (секция browserslist):

"browserslist": [  
  "last 2 versions",
  "not dead"
]

Теперь babel знает, что нужно делать, но пока webpack не знает, что ему нужно сотрудничать с babel. Чтобы дать какие-то указания webpack, нам все-таки придется создать конфигурационный файл. Он должен называться webpack.config.js и находиться в корне проекта. Это обычный модуль, который экспортирует JavaScript-объект. В этом файле может быть много настроек, но нас сейчас интересуют только лоадеры. Добавьте в этот файл следующий код:

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

В поле test находится регулярное выражение, определяющее тип файла, в поле use — список лоадеров для обработки файлов этого типа. Таким образом, для всех подключаемых файлов с расширением .js мы будем использовать babel-loader.

Если теперь вы запустите yarn build (npm run build) и посмотрите внимательно на файл dist/main.js, то увидите, что ES6 синтаксис превратился в ES5. Вот так и работают лоадеры.

Сервер для разработки

Во время разработки приходится постоянно обновлять страницу с проектом, что довольно утомительно. У webpack есть прекрасный инструмент, автоматизирующий эту рутинную задачу – webpack-dev-server.

Установим его:

yarn add  webpack-dev-server --dev
(npm install -D webpack-dev-server)

Чтобы запустить его в режиме разработки, используйте следующую команду:

yarn webpack-dev-server --mode development
(npx webpack-dev-server --mode development)

Можем заменить npm-скрипт build:dev

"scripts": {
  "build": "webpack --mode production",
  "build:dev": "webpack-dev-server --mode development"
}

Однако нужно что-то отображать в браузере – html-страничку, которой у нас пока нет. На ней необходимо подключить скрипт dist/main.js. Можно создать ее вручную, а можно поручить эту работу webpack, точнее его одному из его плагинов – html-webpack-plugin. Он просто создаст в папке dist файл index.html и подключит туда созданный билд.

yarn add html-webpack-plugin --dev
npm i -D html-webpack-plugin

Чтобы этот плагин заработал, нужно добавить его в конфигурационный файл.

Плагины – это почти то же самое, что и лоадеры, только они не привязаны к определенному типу файлов. Грубо говоря, лоадеры работают с отдельными файлами, а плагины уже с целым билдом. Их нужно подключать в отдельную секцию конфига, которая так и называется – plugins.

Сначала импортируем этот модуль, а затем создадим его экземпляр:

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

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

Если вы теперь запустите yarn build (npm run build), в папке dist появится файл index.html с уже подключенным скриптом main.js.

Если вы запустите yarn build:dev (npm run build:dev), то запустится сервер для разработки (по умолчанию на порте 8080). Страница будет обновляться при каждом внесенном изменении.

Тонкая настройка

Мы можем тонко настраивать webpack для любого режима (продакшн или девелопмент). Для этого воспользуемся возможностью возвращать вместо объекта конфигурации функцию:

module.exports = (env={}, argv={}) => {
  return {
    module: {},
    plugins: []
  }
};

В параметре env передаются переменные окружения, а в переменной argv – параметры командной строки (например, опция mode). Благодаря этому мы можем подключать/отключать нужные плагины и лоадеры, например, вот так:

plugins: [
  argv.mode === "development"
    ? new HtmlWebpackPlugin() 
    : null
].filter(plugin => !!plugin)

Неплохо, правда?

Карты кода

Запустите сервер разработки командой yarn build:dev (npm run build:dev) и посмотрите в консоль. Наш код выводит туда строку Hello, dolly!

Если вы кликнете по адресу скрипта hello.js:3 и перейдете в панель Sources, то увидите файл сборки. Сейчас он небольшой, но когда разрастется, в нем сложно будет ориентироваться. Было бы лучше отлаживать приложение, имея доступ к исходным файлам. Для этого мы используем карты кода. Чтобы webpack включил их, нужно передать ему опцию devtool.

module.exports = (env, argv) => ({
  devtool: 'source-map',
  // …
})

Теперь остановите сервер и снова запустите его. Адрес скрипта в консоли изменился, теперь это исходник, а не билд.

Обработка стилей

Webpack у нас уже умеет собирать JS-модули в один файл, попутно транспилируя ES6 в ES5. Для более-менее работоспособного приложения осталось добавить обработку CSS стилей и картинок.

Мы привыкли подключать стили CSS отдельными файлами в html-документе с помощью тегов link или style. С webpack больше не нужно заниматься этим вручную. Вы можете просто подключать нужные файлы прямо в JavaScript-модули. Сборщик сам найдет их, обработает и вставит в документ (в тег style).

Для демонстрации создадим в папке src файл style.css и добавим туда какой-нибудь заметный стиль, например:

body {
   background: teal;
   color: white;
}

Импортируем этот файл в src/index.js:

import './style.css';

Теперь подберем нужный лоадер. Их нужно сразу два: css-loader и style-loader. Первый превратит css-файл в js-модуль, а второй вставит на страницу тег style с содержимым этого файла.

yarn add css-loader style-loader --dev
(npm i -D css-loader style-loader)

Теперь нужно добавить новые лоадеры в секцию module.rules в конфигурационном файле:

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

Перезапустите сервер – yarn build:dev (npm run build:dev) – вуа-ля – стили применились. Откройте документ в панели разработчика и найдите в секции head добавленный тег style.

Обратите внимание, в секции use у нас теперь не просто строка, а массив строк. Таким образом можно задать сразу несколько обработчиков для одного типа файлов. Применяться они будут с конца, то есть сначала css-loader, а потом style-loader. Порядок очень важен, так как каждый лоадер работает с определенным состоянием файла.

Это лишь минимально возможная обработка css-стилей в проекте с webpack. Вы можете использовать разнообразные пре- и постпроцессоры, а также извлекать стили в отдельный файл вместо того, чтобы вставлять прямо в документ. Но это выходит за рамки туториала для начинающих, поэтому поговорим об этом в следующей статье.

Обработка изображений

Для полноты картины добавим в проект обработку изображений. Да-да, изображения точно так же можно подключить в JavaScript-модуль, значит, их импорт необходимо обрабатывать.

Добавим в папку src изображение image.jpg (выберите сами картинку, которая вам нравится, она может быть совершенно любой). Импортируйте ее в файле index.js и добавьте в документ:

import src from './image.jpg';
const image = new Image();
image.src = src;
document.body.appendChild(image);

Нужный нам лоадер называется file-loader, и он очень прост – просто переносит файлы из одного места в другое (из папки src в папку dist). Установите его и добавьте в конфигурационный файл для всех форматов изображений:

yarn add file-loader --dev
(npm i -D file-loader)
module: {
  rules: [
    // …
    {
      test: /\.(gif|png|jpe?g|svg)$/i,
      use: 'file-loader'
    }
  ]
}

Перезапустите сервер в режиме разработки – у нас появилась картинка. Обратите внимание на ее название – оно отличается от исходного. Это результат работы лоадера file-loader.

Изображение необязательно импортировать прямо в JavaScript-код. Куда чаще мы работаем с ним в CSS, например, устанавливая в качестве фона. Добавьте, например, в ваш style.css инструкцию:

body {
  background: url(./image.jpg);
}

Webpack обнаружит ее и правильно обработает подключение картинки.

Закончим наш вводный туториал оптимизацией изображений. Это очень важный момент, ведь тяжелые неоптимизированные картинки могут здорово ухудшить производительность вашего сайта. Скучную работу по оптимизации тоже можно свалить на webpack, достаточно лишь подключить image-webpack-loader:

yarn add image-webpack-loader --dev
(npm i -D image-webpack-loader)

Теперь добавим новый лоадер в конвейер обработки изображений:

module: {
  rules: [
    // …
    {
      test: /\.(gif|png|jpe?g|svg)$/i,
      use: [
        'file-loader',
        {
           loader: 'image-webpack-loader',
           options: {
              disable: argv.mode === 'development'
           }
        }
      ]
    }
  ]
}

Первое, на что нужно обратить внимание, – лоадер определен не просто строкой, а целым объектом. Его название указано в поле loader, кроме того, есть некоторые опции.

Второе – мы блокируем работу лоадера в режиме разработки, чтобы не тратить ресурсы. Помните, что переменная argv, содержащая параметры, переданные с консольной командой, доступна нам потому, что мы определили экспорт файла webpack.config.js как функцию.

Если вы запустите команду yarn build (npm run build) и сравните размеры картинки до (в папке src) и после (в папке dist), то увидите, как продуктивно поработал лоадер.


Итак, у нас есть вполне работоспособная, хоть и минимальная, конфигурация webpack, но главное – мы разобрались с основными принципами его работы и больше его не боимся!!

Вот полный код конфигурационного файла:

Версии используемых пакетов:

  • webpack: 4.41.5
  • webpack-cli: 3.3.10
  • webpack-dev-server: 3.10.1
  • @babel/core: 7.7.7

3 комментария

  • Ivan
    Ivan
    18/04/2022, 19:33
    Супер! Все заработало. Спасибо.
  • Fopko
    Fopko
    10/11/2021, 01:10
    Спасибо.
  • Павел
    Павел
    3/06/2021, 22:01
    Начиная с раздела о babel-loader, в npm-е посыпались эроры. Постоянно чего-то не хватает, отдельно доставлял с 3-4 компонентов пока babel-loader заработал. Следующий пункт webpack-dev-server то же самое. От такого желание пробовать что-то новое отпадает вообще. Может это просто на Винде такое творится, без понятия....

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован