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

Встраивание изображений

Обычно каждое изображение на веб-странице представляет собой уникальный файл, для отображения которого браузер должен отправить HTTP-запрос. Чем больше запросов нужно сделать браузеру, тем дольше будет загружаться страница. И наоборот, уменьшение общего количества запросов, как правило, повышает производительность.

Встраивание изображений уменьшает количество дополнительных запросов, необходимых для загрузки страницы, путем встраивания данных изображения непосредственно в пакет HTML или Javascript. Как и везде, эта оптимизация не бесплатна: общее количество запросов изображений уменьшается за счет большей начальной полезной нагрузки. Это приводит к лучшей производительности, когда маленькие изображения встроены, но большие изображения обычно загружаются с дополнительными HTTP-запросами.

Привет! Не хотите читать все тонкости приведения Webpack в форму? Перейти к окончательной конфигурации веб-пакета.

Простое приложение React

Чтобы протестировать встраивание изображений, мы создали простое приложение React:

Файл index.html используется для начальной загрузки одного (скомпилированного) файла JSX:

<html>
    <header>
        <title>React With Inline Images</title>
    </header>
    <body>
        <div class="images-container"></div>
    </body>
    <script src="index.js"></script>
</html>
import React from "react"
import ReactDOM from "react-dom/client"

// Just to convince webpack to copy the file
import indexHtml from "./index.html"

function SomeImages(props) {
    return (
        <div>
            <h2>{props.title}</h2>
            <p>
                <h3>Some small images:</h3>
                <img src="images/small-bee.png" />
                <img src="images/small-chick.png" />
                <img src="images/small-puppy.png" />
                <img src="images/small-tree.png" />
            </p>
            <p>
                <h3>Some larger images:</h3>
                <img src="images/medium-duckling.jpg" /><br />
                <img src="images/medium-squirrel.jpg" />
            </p>
        </div>
    )
}

var containerDiv = document.querySelector(".images-container");
var root = ReactDOM.createRoot(containerDiv);
root.render(SomeImages({ title: "React with Inline Images" }));

Создание файлов React JSX с помощью Webpack

Во-первых, с помощью NPM необходимо установить зависимости Webpack и React:

npm install react react-dom
npm install --save-dev webpack webpack-cli babel-loader @babel/preset-react

Webpack не компилирует JSX из коробки. Добавление правила модуля к webpack.config.js указывает Webpack использовать Babel при компиляции файлов JSX. Существует дополнительное правило для копирования нашего загрузочного html в выходную папку. Подробнее о «модулях активов» позже:

var path = require("path");

module.exports = {
    mode: "development",
    entry: "./src/index.jsx",
    output: {
        filename: "index.js",
        path: path.resolve("dist/"),
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: "babel-loader",
                options: {
                    "presets": ["@babel/preset-react"]
                }
            },
            {
                test: /\.html$/i,
                type: "asset/resource",
                generator: {
                    filename: "[name][ext]"
                }
            }
        ]
    }
};

Запуск webpack из командной строки компилирует наш JSX в выходную папку с именем dist/, но есть некоторые проблемы, которые необходимо исправить.

Импорт/требование изображений

Что ж, вещи ПОЧТИ работают. Все наши теги изображений ломаются, когда мы загружаем скомпилированное приложение:

И никакие изображения не выводились в нашу папку dist/:

Изображения не отображаются, потому что Webpack не читает URL-адреса в атрибутах src. Ни один из наших файлов изображений не копируется в папку dist/, потому что Webpack предположил, что мы ссылаемся на внешнюю зависимость, о которой ему не нужно беспокоиться. JSX должен импортировать или запрашивать изображения, чтобы Webpack знал, что нам нужны эти изображения:

// BEFORE:
<p>
    <h3>Some small images:</h3>
    <img src="images/small-bee.png" />
    <img src="images/small-chick.png" />
    <img src="images/small-puppy.png" />
    <img src="images/small-tree.png" />
</p>
<p>
    <h3>Some larger images:</h3>
    <img src="images/medium-duckling.jpg" /><br />
    <img src="images/medium-squirrel.jpg" />
</p>

// AFTER:
<p>
    <h3>Some small images:</h3>
    <img src={require("./images/small-bee.png")} />
    <img src={require("./images/small-chick.png")} />
    <img src={require("./images/small-puppy.png")} />
    <img src={require("./images/small-tree.png")} />
</p>
<p>
    <h3>Some larger images:</h3>
    <img src={require("./images/medium-duckling.jpg")} /><br />
    <img src={require("./images/medium-squirrel.jpg")} />
</p>

Использование модулей активов для файлов изображений

И вещи все еще сломаны. Теперь Webpack знает о наших изображениях, но выдает ошибки:

ERROR in ./src/images/medium-duckling.jpg 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
(Source code omitted for this binary file)
 @ ./src/index.jsx 16:9-48

Webpack терпит неудачу, потому что не знает, что делать с нашими файлами изображений. Как и в случае с JSX, нам нужно правило модуля, говорящее Webpack, что делать, когда он встречает изображение.

Webpack 5 имеет новую функцию под названием Asset Modules, которая предназначена для замены url-loader, file-loader и raw-loader, используемых в Webpack 4 для этой ситуации. Просто чтобы все заработало, мы скажем Webpack всегда копировать файлы изображений в выходную папку:

// Added to webpack.config.js:
module: {
    rules: [
        // ...snip...
        {
            test: /\.(png|jpg)$/i,
            type: 'asset/resource'
        }
    ]
}

Наконец, webpack включает изображения в скомпилированный вывод:

И наша страница работает:

Поместите все изображения в их собственную папку

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

{
    test: /\.(png|jpg)$/i,
    type: 'asset/resource'
    // Added:
    generator: {
        filename: 'images/[name]-[hash][ext]'
    }
}

Теперь все изображения находятся в отдельной папке с понятными названиями. Сохранение хэша помогает при очистке кеша:

Автоматически очищать выходной каталог Webpack

Почему мой каталог dist такой загроможденный? Поскольку конфигурация веб-пакета изменилась, мы вручную удалили старые файлы из каталога dist/. По умолчанию Webpack никогда не удаляет старые файлы, которые больше не нужны. Мы можем настроить Webpack для автоматической очистки папки dist при каждой сборке:

output: {
    // ...snip...
    clean: true
},

Встроенные маленькие изображения

Наконец-то изображения заработали, и мы можем делать то, ради чего пришли сюда: встроенные маленькие изображения! Базовый модуль Webpack asset Asset автоматически обрабатывает встраивание для нас. Webpack по умолчанию будет встраивать все, что меньше 8 КБ, но мы также можем явно установить порог размера. Изображения сверх лимита будут выведены в папку dist/ как и раньше:

module: {
        rules: [
            // ...snip...
            {
                test: /\.(png|jpg)$/i,
                // Previously we had disabled inlining by using 'asset/resource'
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024 // Inline images under 10KB
                    }
                },
                generator: {
                    filename: 'images/[name]-[hash][ext]'
                }
            }
        ]
    }

Меньшие изображения встроены, а выходной каталог содержит только большие изображения:

Мы можем увидеть изображения в кодировке Base64, если проверим отрендеренную страницу:

TLDR: окончательная конфигурация Webpack

var path = require("path");

module.exports = {
    mode: "development",
    entry: "./src/index.jsx",
    output: {
        filename: "index.js",
        path: path.resolve("dist/"),
        clean: true
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: "babel-loader",
                options: {
                    "presets": ["@babel/preset-react"]
                }
            },
            {
                test: /\.html$/i,
                type: "asset/resource",
                generator: {
                    filename: "[name][ext]"
                }
            },
            {
                test: /\.(png|jpg)$/i,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024 // Inline images under 10KB
                    }
                },
                generator: {
                    filename: 'images/[name]-[hash][ext]'
                }
            }
        ]
    }
};

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

Заключение

Мы успешно убедили Webpack автоматически встраивать наши изображения. Это уменьшило количество сетевых запросов, которые необходимо сделать нашей странице, но сделало ли это нашу страницу быстрее? Именно для таких вопросов и была создана Request Metrics. Попробуйте сегодня, чтобы измерить, как ваш сайт работает для реальных пользователей в рабочей среде.

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

Первоначально опубликовано на https://requestmetrics.com.