Как настроить Webpack — Config, Loaders, Plugins и многое другое
Из статьи вы узнаете про окружение, процесс сборки проекта и соберете собственный проект с Webpack с нуля. Расскажу как настроить конфиг, лоадеры, плагины, кэширование, source maps, поднять локальный сервер и проанализировать размер сборки.
Окружение — Development и Production
Окружение (environment) — это контекст, в котором выполняется код:
- В контексте “Development” вы создаете и запускаете приложение локально на своём компьютере.
- В контексте “Production” происходит процесс подготовки приложения к развертыванию (deployment) и использованию реальными пользователями.
Так как каждая среда имеет свои особенности и цели, нужно многое сделать для переноса приложения из development в production. Код приложения необходимо скомпилировать, собрать, минифицировать и разделить. Далее расскажу про этап сборки и самый популярный инструмент для решения этой задачи — WebPack.
Что такое сборка
Разработчики разделяют свои приложения на модули, компоненты и функции, которые могут быть использованы для создания более крупных частей приложения. Экспорт и импорт этих внутренних модулей, а также внешних пакетов сторонних разработчиков создает сложную сеть зависимостей файлов.
Сборка (bundling) — это процесс разрешения сети зависимостей. Файлы и модули объединяются в оптимизированные пакеты для браузера. Цель — сократить количества запросов на сервер, увеличить производительность веб-страниц и упростить развертывание приложения.
Проект с WebPack
Лучше всего познакомиться со сборщиком WebPack на примере небольшого проекта. Мы построим небольшое приложение, которое загружает “отцовские” шутки с внешнего API, отображает их, и подгружает новые шутки при нажатии на кнопку.
Инициализация проекта
Ссылка на репозиторий с проектом
Создадим папку у себя на компьютере, назвать можно как угодно, в моем случае это будет “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>
Вот как должен выглядеть проект на данный момент:
Запустим проект с помощью расширения для VSCode — Live Server. Увидим результат — в консоли появилась запись:
Если на каком-либо из этапов у вас возникнут ошибки в проекте — сравните свой код с репозиторием по названию коммита:
Установка 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>
Теперь мы увидим, что функция успешно импортирована и вернула строку в консоль:
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 в конфиге:
Запустим команду npm run build
. В папкe dist
должен появиться файл bundle.js
Скорректируем путь в index.html
Запустим проект через 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
и увидим, что стили успешно загрузились и обработаны:
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 файле
В index.html появилась такая строчка для импорта скрипта, причем она идет в head, а не в конце body, и содержит атрибут defer
:
<head><script defer src="bundle.js"></script></head>
Атрибут defer
сообщает браузеру, что он может обрабатывать страницу, строить DOM дерево, а скрипт грузить в фоне. Когда DOM дерево будет полностью построено — запустится скрипт.
Кэширование
Чтобы повысить производительность веб-сайта за счет уменьшения количества сетевых запросов, можем использовать кэширование файла скрипта.
Когда файл кэшируется, браузер хранит его копию локально, чтобы не загружать его снова при последующих загрузках страницы. Если содержимое файла изменяется, важно, чтобы браузер получал новую версию файла, а не использовал кэшированную версию.
Таким образом, мы, с одной стороны улучшим производительность сайта, а с другой — всегда предоставляем пользователю последнюю версию файла.
Чтобы настроить кэширование, добавим в webpack.config.js
динамическое свойство для названия. Когда содержимое файла изменится, изменится его название.
Соберем проект заново с помощью команды npm run build
. Webpack создал новую версию скрипта с ключом, нужным для кэширования. Если вы измените содержимое скрипта index.js и заново соберете проект — увидите новое значение в названии.
Сервер для разработки
Сейчас мы поднимаем проект с помощью расширения 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 и увидеть его по указанному порту
Source Map
Добавим еще одну строчку в конфиг:
Обеспечивают соответствие между скомпилированным кодом и исходным кодом. Упрощает отлаживание исходного кода в devtools браузера. Будет понятно, на каких строчках исходного кода возникла ошибка.
Теперь после сборки мы увидим еще один файл, который простраивает связь между исходным и скомпилированным кодом:
Загрузчик 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 файле.
Запустим сервер и проверим, отображается ли картинка npm run dev
Картинка есть в ./dist
и отображается на странице, значит все настроено корректно:
Функционал приложения
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();
Проверяем работу функционала:
Анализатор размера пакета
Чем меньше размер сборки — тем лучше производительность приложения. 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 и после исполнения команды увидим в браузере информацию по размеру и содержимому пакета:
Итого
- Перед деплоем код приложения необходимо скомпилировать, собрать, минифицировать и разделить
- Сборка (bundling) — это процесс разрешения сети зависимостей с целью сокращения количества запросов на сервер, увеличения производительность веб-страниц и упрощения развертывания приложения.
- Установить webpack:
npm i -D webpack webpack-cli
- Чтобы сконфигурировать Webpack нужно создать файл
webpack.config.js
в корне проекта - Файл конфигурации, как минимум, содержит следующую информацию: режим (
mode
), путь к исходным файлам (entry
), информация о скомпилированных файлах (output
),devtool
для отладки,devServer
для локального сервера,module
для правил и загрузчиков,plugins
для плагинов