skip to main content

Webpack, краткий обзор

js

Кратко 🔗

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

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

Базовое использование 🔗

В базовом случае использовать Webpack довольно просто. Представим, что в нашем приложении есть файл application.js с функциями, необходимыми для работы:

function sayHello() {
console.log("Hello!")
}

function sayBye() {
console.log("Bye!")
}

// экспортируем эти функции, чтобы воспользоваться ими в другом месте
export { sayHello, sayBye }

:::callout 💡

Входная точка — файл, с которого начинается исполнение программы. Обычно в нём содержиться логика старта приложения.

:::

Теперь создадим файл `index.js`, который будет использоваться как входная точка, и вызовем в нем функции приложения:

```js
import { sayHello, sayBye } from "./application"

sayHello()
sayBye()

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

npm install --dev webpack webpack-cli
  • webpack — основная зависимость, в которой храниться весь код, нужный для работы бандлера;
  • webpack-cli (Command Line Interface) — обертка для запуска Webpack из командной строки.

Теперь достаточно создать простой конфигурационный файл webpack.config.js:

// path — встроенный в Node.js модуль
const path = require("path")

module.exports = {
// указываем путь до входной точки
entry: "./src/index.js",
// описываем куда следует поместить результат работы
output: {
// путь до директории (важно использовать path.resolve)
path: path.resolve(__dirname, "dist"),
// имя файла со сброкой
filename: "bundle.js"
}
}

Почти готово, осталось только добавить скрипт для сборки в package.json и вызвать его:

"scripts": {
"build": "webpack"
}
npm run build

После выполнения, в директории dist окажется файл bundle.js, который уже можно подключать на страницу в браузере.

В корне проекта нужно создать файл index.html и добавить в него следующее содержимое:

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<script src="./dist/bundle.js"></script>
</body>
</html>

Если открыть этот файл в браузере, то в консоли появится приветствие и прощание.

Открытая в браузере страница

Отслеживание изменений в проекте 🔗

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

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

Добавим новую команду в package.json:

"scripts": {
"build": "webpack",
"watch": "webpack --watch"
}

Теперь достаточно открыть файл index.html в браузере и обновлять страницу после сохранения файлов с исходным кодом.

Для большего удобства можно воспользоваться пакетом webpack-dev-server.

Расширение 🔗

Webpack — невероятно мощный инструмент, в первую очередь за счёт своей расширяемости. В базовом случае он только собирает несколько JS-файлов в один, но с помощью лоадеров и плагинов можно сильно изменить функциональность этого инструмента.

Webpack начинает свою работу со входной точки — первого JS-файла. В нем он находит все импорты, по которым собирает файлы с исходным кодом проекта. Во всех найденных файлах снова ищет импорты, и так, пока импорты не закончатся. В итоге у Webpack оказывается список всех файлов проекта и информация, как эти файлы связаны. Затем в дело вступают плагины и лоадеры. Для каждого файла Webpack найдет все подходящие (на основе конфигурации) лоадеры и обработает ими файл.

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

Обратная сторона расширяемости Webpack — сложность его конфигурации. В больших проектах файл с настройками сборки может занимать тысячи строк. Часто такую конфигурацию разбивают на несколько файлов, чтобы ее было проще читать.

Лоадеры 🔗

Лоадер — это функция, которая принимает содержимое какого-то файла и должна вернуть изменённое содержимое.

Например, ts-loader превратит любой TypeScript-код в обыкновенный JavaScript-код.

Для Webpack написано огромное число лоадеров — для работы со стилями, разными языками, для обработки изображений и много других. Вот несколько, которые можно встретить почти в любом проекте:

  • style-loader — импортирует CSS-файлы и внедряет стили в DOM;
  • css-loader — позволяет работать с @import и url() внутри CSS;
  • babel-loader — позволяет писать код на современном JS, но исполнять его даже в старых браузерах.

Чтобы добавить новый лоадер, нужно расширить файл webpack.config.js:

module.exports = {
// в этом массиве будут перечислены все применяемые лоадеры
module: {
rules: [
{
// это правило будет применяться ко всем файлам,
// имя которых подойдет под это регулярное выражение
test: /\.css$/,
// список лоадеров, которые применяться к файлу
use: [
{ loader: "style-loader" },
{
loader: "css-loader",
// лоадеру можно передать параметры
options: { modules: true }
}
]
}
]
}
}

Плагины 🔗

Плагин — мощный способ расширить или изменить функциональность Webpack. Если лоадер ограничен только одной функцией (принимает содержимое файла и должен вернуть изменённое содержимое), то плагин может делать все что угодно.

Для Webpack написано огромное число плагинов — для работы со стилями, повышения удобства разработки и упрощения жизни инженеров, для автоматизации рутинных операций и много других. Вот несколько, которые можно встретить почти в любом проекте:

  • MiniCssExtractPlugin — по умолчанию все стили, которые обработал Webpack попадают в JS-файл и потом вставляются в тег <style>, этот плагин извлекает все стили в отдельный CSS-файл, которые можно подключить к странице через тег <link>;
  • HotModuleReplacementPlugin — позволяет делать изменения в коде и видеть изменения в браузере без полной перезагрузки страницы, это делает разработку более кофмортной;
  • CompressionWebpackPlugin — сжимает все ресурсы, сгенерированные Webpack, чтобы передавать пользователям по сети меньший объём данных.

Чтобы добавить новый плагин в сборку, нужно расширить файл webpack.config.js:

// Webpack предоставляет несколько плагинов в основном пакете
const { ProgressPlugin } = require("webpack")

module.exports = {
plugins: [
// при сборке этот плагин будет отображать прогресс в консоле
new ProgressPlugin()
]
}

В работе 🔗

HtmlWebpackPlugin 🔗

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

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

npm install --save-dev html-webpack-plugin
const { HtmlWebpackPlugin } = require("html-webpack-plugin")

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

При сборке плагин сгенерирует пустой index.html в папку с собранным проектом и добавит туда ссылки на финальные JS и CSS файлы. При необходимости плагин можно кастомизировать.

path 🔗

path — это встроенный в Node.js модуль для работы с путями. Чтобы не беспокоиться об особенностях разных операционных систем, лучше всегда при работе с путями в файловой системе использовать модуль path.

В нём есть несколько полезных для конфигурации Webpack функций:

  • path.resolve — функция, которая принимает любое число сегментов пути и возвращает абсолютный путь, работает так:
path.resolve("/foo/bar", "./baz") // "/foo/bar/baz"
path.resolve("/foo/bar", "/tmp/file/") // "/tmp/file"

// Если текущая рабочая директора /home/user
path.resolve("www", "static_files/png", "../gif/image.gif")
// "/home/use/www/static_files/gif/image.gif"
  • path.sep — строка разделителя путей в текущем окружении (/ или \);
  • path.extname — функция, которая принимает строку с именем файла и возвращает расширение этого файла.

Подробная документация по модулю path.