DevSurge 💦

Как настроить Webpack — Config, Loaders, Plugins и многое другое

Cover Image for Как настроить Webpack — Config, Loaders, Plugins и многое другое
Mark Nelyubin
Mark Nelyubin

Из статьи вы узнаете про окружение, процесс сборки проекта и соберете собственный проект с Webpack с нуля. Расскажу как настроить конфиг, лоадеры, плагины, кэширование, source maps, поднять локальный сервер и проанализировать размер сборки.

Окружение — Development и Production

Окружение (environment) — это контекст, в котором выполняется код:

  • В контексте “Development” вы создаете и запускаете приложение локально на своём компьютере.
  • В контексте “Production” происходит процесс подготовки приложения к развертыванию (deployment) и использованию реальными пользователями.

Так как каждая среда имеет свои особенности и цели, нужно многое сделать для переноса приложения из development в production. Код приложения необходимо скомпилировать, собрать, минифицировать и разделить. Далее расскажу про этап сборки и самый популярный инструмент для решения этой задачи — WebPack.

Article Image

Что такое сборка

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

Article Image
Сборка (bundling) — это процесс разрешения сети зависимостей. Файлы и модули объединяются в оптимизированные пакеты для браузера. Цель — сократить количества запросов на сервер, увеличить производительность веб-страниц и упростить развертывание приложения.

Проект с WebPack

Лучше всего познакомиться со сборщиком WebPack на примере небольшого проекта. Мы построим небольшое приложение, которое загружает “отцовские” шутки с внешнего API, отображает их, и подгружает новые шутки при нажатии на кнопку.

Article Image

Инициализация проекта

Ссылка на репозиторий с проектом

Создадим папку у себя на компьютере, назвать можно как угодно, в моем случае это будет “webpack”.

В корне создадим папку src и файл index.js

console.log(123);

Создадим еще одну папку в корне dist/index.html

<!DOCTYPE html>
<html lang="en">
<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>Webpack App</title>
</head>
<body>
  <script src="../src/index.js"></script>
</body>
</html>

Вот как должен выглядеть проект на данный момент:

Article Image

Запустим проект с помощью расширения для VSCode — Live Server. Увидим результат — в консоли появилась запись:

Article Image

Если на каком-либо из этапов у вас возникнут ошибки в проекте — сравните свой код с репозиторием по названию коммита:

Article Image

Установка WebPack

Создадим файл с функцией, которая возвращает строку и импортируем в index.js.

src/generateJoke.js

const generateJoke = () => {
  return "I don't trust stairs because they're always up to something, and I don't trust lifts because they let you down.";
};

export default generateJoke;

src/index.js

import generateJoke from "./generateJoke.js";
console.log(generateJoke);

В консоли вы увидите ошибку: Uncaught SyntaxError: Cannot use import statement outside a module (at index.js:1:1)

Инициализируем стандартный package.json с помощью команды npm init -y

Можно решить проблему через добавление строчки "type":"module", но мы разберемся с этим с помощью Webpack.

Установим вебпак: npm i -D webpack webpack-cli

Добавим скрипт для сборки в package.json

{
  "name": "webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
		// добавим строчку для продакшен сборки
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  }
}

Вот как должен выглядеть файл с информацией о проекте и пакетах (версия вебпака может отличаться). Лучше всего использовать такую же Major-версию, для этого используйте команду npm i webpack@5 webpack-cli@5

Запустим команду npm run build, чтобы собрать проект. В папке dist появится новый js файл.

dist/main.js

(()=>{"use strict";console.log("I don't trust stairs because they're always up to something, and I don't trust lifts because they let you down.")})();

Подключим скрипт в index.html

dist/index.html

<!-- omitted code -->
<body>
  <script src="./main.js"></script>
</body>

Теперь мы увидим, что функция успешно импортирована и вернула строку в консоль:

Article Image

Webpack успешно объединил два файла index.js и generateJoke.js в один файл main.js и разрешил зависимости (импорт и экспорт).

Webpack конфиг

Создадим в корне проекта файл webpack.config.js и укажем базовые параметры — окружение разработки (development), путь к файлу index.js, имя и путь к файлу для бандла.

const path = require("path");
module.exports = {
	mode: "development",
  entry: path.resolve(__dirname, "src/index.js"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
};
  • path — встроенный модуль node.js, который предоставляет утилиты для работы с путями к файлам и каталогам
  • path.resolve — преобразовывает последовательность путей или сегментов пути в абсолютный путь. В нашем случае — получится абсолютный путь к index.

Скорректируем команду в package.json, так как мы указали mode в конфиге:

Article Image

Запустим команду npm run build. В папкe dist должен появиться файл bundle.js

Article Image

Скорректируем путь в index.html

Article Image

Запустим проект через liveserver — все должно работать. Ссылка на коммит.

Мы также можем задавать динамическое имя файла в webpack.config.js:

const path = require("path");
module.exports = {
  mode: "development",
  entry: {
    bundle: path.resolve(__dirname, "src/index.js"),
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
};

Удалим bundle.js, запустим npm run build, увидим, что снова появился файл bundle.js (по названию entry point).

Style Loaders

Допустим, мы хотим работать со стилями, используя пре-процессор sass и иметь возможность работать со стилями в JavaScript (как во фреймворках React/Vue). Для этого установим и настроим загрузчики (loaders) стилей для вебпака.

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

Установим пре-процессор sass и загрузчики стилей: npm i -D sass style-loader css-loader sass-loader

Создадим файл со стилями src/styles/main.scss и импортируем в src/index.js

import "./styles/main.scss";

Добавим еще одно свойство в объект конфигурации webpack.config.js, чтобы настроить загрузчики:

const path = require("path");
module.exports = {
  //omitted
  output: {
		//omitted
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
};

Запустим npm run build и увидим, что стили успешно загрузились и обработаны:

Article Image

HTML Webpack плагин

Если удалить папку ./dist и запустить npm run build, то webpack заново создаст папку ./dist с файлом bundle.js, но без html-файла. Чтобы решить эту проблему, создадим шаблон html-страницы, по которому webpack будет собирать файл index.html. Для этого нам также потребуется установить и настроить HTML-плагин. Снова удалим папку ./dist, чтобы протестировать процесс сборки.

Создаем шаблон ./src/template.html

<!DOCTYPE html>
<html lang="en">
<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>
  <div class="container">
    <h3>Don't Laugh Challenge</h3>
    <div id="joke" class="joke">
    </div>
		<button id="joteBtn" class="btn">Get Another Joke</button>
  </div>
</body>
</html>

Обратите внимание, в шаблоне мы не подгружаем скрипт. Webpack сделает это за нас.

Здесь Webpack создаст динамический заголовок, который мы укажем в конфиге:

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

Установим HTML плагин, чтобы создать индексную страницу из нашего шаблона: npm i html-webpack-plugin

Загрузим класс HtmlWebpackPlugin, создадим новую опцию в webpack.config.js, создадим экземпляр класса и в опциях укажем дополнительные параметры:

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

module.exports = {
  // omitted
  module: {
		//omitted
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack App", // это попадет в <title/>
      filename: "index.html", // название файла после сборки
      template: "src/template.html", // где брать шаблон
    }),
  ],
};

Запустим npm run build и увидим, что теперь создается всё, что нужно для работы сайта — html, скрипт, и импорт скрипта в html файле

Article Image

В index.html появилась такая строчка для импорта скрипта, причем она идет в head, а не в конце body, и содержит атрибут defer:

<head><script defer src="bundle.js"></script></head>

Атрибут deferсообщает браузеру, что он может обрабатывать страницу, строить DOM дерево, а скрипт грузить в фоне. Когда DOM дерево будет полностью построено — запустится скрипт.

Ссылка на коммит

Кэширование

Чтобы повысить производительность веб-сайта за счет уменьшения количества сетевых запросов, можем использовать кэширование файла скрипта.

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

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

Чтобы настроить кэширование, добавим в webpack.config.js динамическое свойство для названия. Когда содержимое файла изменится, изменится его название.

Article Image

Соберем проект заново с помощью команды npm run build. Webpack создал новую версию скрипта с ключом, нужным для кэширования. Если вы измените содержимое скрипта index.js и заново соберете проект — увидите новое значение в названии.

Article Image

Ссылка на коммит

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

Сейчас мы поднимаем проект с помощью расширения Live Server. Webpack тоже может запустить локальный сервер. Сервер обслуживает ваши скомпилированные ресурсы в памяти, не записывая их на диск. Он предназначен для использования во время разработки, когда вы хотите видеть, как изменения в коде немедленно отражаются в браузере без необходимости каждый раз вручную пересобирать проект.

Установим пакет для запуска сервера: npm i —D webpack-dev-server

Добавим команду “dev” в ./package.json

// omitted
"scripts": {
    "build": "webpack",
    "dev": "webpack serve"
  },

Добавим опцию devServer в webpack.config.js

// omitted
module.exports = {
	// omitted
  devServer: {
		// директория, в которой хранятся статичные файлы, которые обслуживает сервер
    static: {
      directory: path.resolve(__dirname, "dist"),
    },
    port: 3000,
		// должен ли автоматически открываться браузер при запуске сервера
    open: true,
		// включает горячую замену модулей (HMR). HMR позволяет обновлять отдельные модули в браузере без перезагрузки всей страницы.
    hot: true,
		// ресурсы обслуживаются со сжатием gzip,
    compress: true,
		// сервер будет обрабатывать несуществующие роуты, возвращая index.html. Позволяет обрабатывать роутинг с помощью HTML5 history API
    historyApiFallback: true,
  },
};

Теперь можем запустить сервер с помощью команды npm run dev и увидеть его по указанному порту

Article Image

Source Map

Добавим еще одну строчку в конфиг:

Article Image

Обеспечивают соответствие между скомпилированным кодом и исходным кодом. Упрощает отлаживание исходного кода в devtools браузера. Будет понятно, на каких строчках исходного кода возникла ошибка.

Теперь после сборки мы увидим еще один файл, который простраивает связь между исходным и скомпилированным кодом:

Article Image

Загрузчик Babel

Для совместимости кода со старыми версиями браузера нужно использовать транспайлер, такой как Babel. Установим и настроим babel с помощью webpack.

npm i -D babel-loader @babel/core @babel/preset-env

Добавим в правила конфига webpack параметры для babel

module: {
    rules: [
      //omitted
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
      },
    ],
  },

Здесь мы говорим: используй для всех javascript файлов (кроме тех, что в папке node_modules) babel-loader с пресетом preset-env

Загрузчик ресурсов

Чтобы использовать изображения в JS-файле, нужно настроить загрузчик ресурсов. На этот раз нам не нужно ничего устанавливать, только настроить и протестировать.

Добавим в правила конфига вебпак информацию о том, как обрабатывать расширения изображений:

module.exports = {
  //omitted
  output: {
		// omitted
		// назови файл также, как и исходный — имя и расширение
    assetModuleFilename: "[name][ext]",
  },
	// omitted
  module: {
    rules: [
			// omitted
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      },
    ],
  },
	// omitted
};

Осталось только протестировать, что всё работает. Создадим папку ./src/assets и добавим изображение, которое можно взять из ссылки на коммит.

Создадим новый html-элемент в нашем шаблоне, но не укажем ссылку на картинку. Вместо этого, подставим ее в javascript файле.

Article ImageArticle Image

Запустим сервер и проверим, отображается ли картинка npm run dev

Картинка есть в ./dist и отображается на странице, значит все настроено корректно:

Article ImageArticle Image

Функционал приложения

Webpack мы настроили, осталось реализовать функционал приложения. Для этого сделаем get-запрос на сервис "https://icanhazdadjoke.com", получим контент с шуткой, выведем, и сделаем генерацию новых шуток при клике на кнопку.

Сначала установим axios, чтобы было проще работать с запросами.

npm i axios

Отправим запрос в src/generateJoke.js и вставим текст ответа в интерфейс

import axios from "axios";
const generateJoke = async () => {
  const config = {
    headers: {
      Accept: "application/json",
    },
  };
  try {
		// отправляем запрос
    const res = await axios.get("https://icanhazdadjoke.com", config);
    const {
      data: { joke },
    } = res;
		// подставляем текст шутки в ui
    document.getElementById("joke").innerHTML = joke;
  } catch (error) {
    console.error(error.message);
  }
};

export default generateJoke;

Повесим event-listener на кнопку и вызовем функцию при загрузке скрипта, чтобы при загрузке страницы подгружалась первая шутка и менялась при клике на кнопку

index.js

import generateJoke from "./generateJoke";
import "./styles/main.scss";
import laughing from "./assets/laughing.svg";

const laughEl = document.getElementById("laughImg");
laughEl.src = laughing;
console.log(generateJoke());

const jokeBtn = document.getElementById("jokeBtn");
jokeBtn.addEventListener("click", () => {
  generateJoke();
});

generateJoke();

Проверяем работу функционала:

Article Image

Ссылка на коммит

Анализатор размера пакета

Чем меньше размер сборки — тем лучше производительность приложения. Webpack может показать размер сборки в целом и с разбивкой по файлам. Для этого нужно установить Bundle Analyzer Plugin

npm i webpack-bundle-analyzer

Сконфигурируем в webpack.config.js

//omitted
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  //omitted
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack App",
      filename: "index.html",
      template: "src/template.html",
    }),
    new BundleAnalyzerPlugin(),
  ],
};

Запустим билд npm run build и после исполнения команды увидим в браузере информацию по размеру и содержимому пакета:

Article Image

Итого

  • Перед деплоем код приложения необходимо скомпилировать, собрать, минифицировать и разделить
  • Сборка (bundling) — это процесс разрешения сети зависимостей с целью сокращения количества запросов на сервер, увеличения производительность веб-страниц и упрощения развертывания приложения.
  • Установить webpack: npm i -D webpack webpack-cli
  • Чтобы сконфигурировать Webpack нужно создать файл webpack.config.js в корне проекта
  • Файл конфигурации, как минимум, содержит следующую информацию: режим (mode), путь к исходным файлам (entry), информация о скомпилированных файлах (output), devtool для отладки, devServer для локального сервера, module для правил и загрузчиков, plugins для плагинов

Другие материалы

Cover Image for TypeScript Utility Types — вспомогательные типы и области их применения

TypeScript Utility Types — вспомогательные типы и области их применения

что такое Utility Types в TypeScript, расскажу про основные вспомогательные типы, и покажу, как применять их на реальных проектах.

Mark Nelyubin
Mark Nelyubin
Cover Image for Принципы SOLID с примерами на JS и Vue

Принципы SOLID с примерами на JS и Vue

Расскажу про принципы SOLID с актуальными примерами на JavaScript, Vue, React.

Mark Nelyubin
Mark Nelyubin