Тип resource: вынос файла на диск

Тип resource в системе Asset Modules предназначен для физического вынесения файла в директорию сборки. В отличие от asset/inline, который преобразует содержимое файла в Base64-строку и внедряет её в JavaScript или CSS, тип resource создаёт отдельный файл в папке output и возвращает путь к нему.

Такой механизм заменяет работу file-loader, использовавшегося в Webpack 4 и более ранних версиях.


Принцип работы resource

При импорте файла Webpack:

  1. обнаруживает ресурс;
  2. копирует его в каталог сборки;
  3. генерирует уникальное имя;
  4. возвращает URL к созданному файлу.

Пример импорта изображения:

import logo from './images/logo.png';

console.log(logo);

После сборки значение переменной logo может выглядеть так:

/images/a1b2c3d4.png

При этом сам файл физически появится в директории сборки:

dist/
└── images/
    └── a1b2c3d4.png

Базовая настройка

Простейшая конфигурация:

module.exports = {
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg|gif)$/i,
                type: 'asset/resource'
            }
        ]
    }
};

Теперь все изображения:

  • копируются в сборку;
  • получают автоматически сгенерированное имя;
  • доступны через импорт.

Что возвращает импорт

Тип resource не возвращает содержимое файла.

Он возвращает строку URL.

Пример:

import image from './image.png';

const img = document.createElement('img');

img.src = image;

document.body.append(img);

Webpack:

  1. создаст отдельный файл;
  2. сформирует URL;
  3. подставит его в src.

Отличие от asset

Тип asset автоматически выбирает:

  • inline;
  • resource.

Выбор зависит от размера файла.

Тип asset/resource всегда:

  • создаёт отдельный файл;
  • не внедряет Base64;
  • не анализирует размер ресурса.

Отличие от asset/inline

asset/inline

type: 'asset/inline'

Результат:

data:image/png;base64,...

Файл не создаётся.


asset/resource

type: 'asset/resource'

Результат:

dist/images/file.png

Файл существует физически.


Отличие от asset/source

asset/source возвращает текстовое содержимое файла:

import svg from './icon.svg';

console.log(svg);

Результат:

<svg>...</svg>

resource вместо содержимого возвращает URL.


Замена file-loader

Старый способ:

{
    test: /\.(png|jpg)$/i,
    use: [
        {
            loader: 'file-loader'
        }
    ]
}

Современный вариант:

{
    test: /\.(png|jpg)$/i,
    type: 'asset/resource'
}

Asset Modules:

  • проще;
  • быстрее;
  • встроены в Webpack;
  • не требуют отдельного пакета.

Генерация имён файлов

По умолчанию Webpack создаёт хэшированные имена:

a7d9f2e1.png

Это необходимо для:

  • кэширования;
  • предотвращения конфликтов;
  • обновления файлов при изменении содержимого.

Настройка имени файла

Для изменения имени используется generator.filename.

Пример:

module.exports = {
    module: {
        rules: [
            {
                test: /\.(png|jpg)$/i,
                type: 'asset/resource',
                generator: {
                    filename: 'images/[name][ext]'
                }
            }
        ]
    }
};

Результат:

dist/
└── images/
    └── logo.png

Основные шаблоны имени

[name]

Имя файла без расширения.

logo

[ext]

Расширение файла.

.png

[hash]

Хэш содержимого.

a1b2c3

[contenthash]

Хэш содержимого файла.

Используется чаще всего для долгосрочного кэширования.


[query]

Строка запроса.


Пример сложного шаблона

generator: {
    filename: 'assets/images/[name].[contenthash][ext]'
}

Результат:

assets/images/logo.a12f8e1d.png

Настройка структуры каталогов

Разные типы файлов можно раскладывать по разным директориям.

Изображения

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

Шрифты

{
    test: /\.(woff2?|eot|ttf|otf)$/i,
    type: 'asset/resource',
    generator: {
        filename: 'fonts/[name][ext]'
    }
}

Видео

{
    test: /\.(mp4|webm)$/i,
    type: 'asset/resource',
    generator: {
        filename: 'video/[name][ext]'
    }
}

Работа с CSS

При использовании css-loader ссылки из CSS автоматически обрабатываются Webpack.

Пример:

background-image: url('./images/bg.png');

Webpack:

  1. найдёт файл;
  2. обработает его через asset/resource;
  3. скопирует в сборку;
  4. подменит путь.

Результат:

background-image: url(/images/a1b2c3.png);

Обработка SVG

SVG часто обрабатываются через resource.

{
    test: /\.svg$/i,
    type: 'asset/resource'
}

Импорт:

import icon from './icon.svg';

Использование:

img.src = icon;

Импорт шрифтов

Типичный пример:

@font-face {
    font-family: 'Roboto';
    src: url('./fonts/Roboto.woff2') format('woff2');
}

Webpack:

  • вынесет файл;
  • заменит путь;
  • добавит ресурс в сборку.

Параметр publicPath

Иногда файлы должны загружаться не из корня сайта, а из CDN или другого пути.

Используется:

output: {
    publicPath: '/static/'
}

Теперь URL станет:

/static/images/logo.png

Локальный publicPath

Можно задавать путь отдельно для конкретного правила.

{
    test: /\.(png|jpg)$/i,
    type: 'asset/resource',
    generator: {
        publicPath: '/assets/'
    }
}

Использование CDN

Пример:

output: {
    publicPath: 'https://cdn.example.com/'
}

Webpack будет генерировать:

https://cdn.example.com/images/logo.png

Настройка outputPath

Можно управлять физическим расположением файлов.

generator: {
    outputPath: 'images'
}

Разница между publicPath и outputPath

outputPath

Определяет:

куда файл записывается на диске

publicPath

Определяет:

какой URL получит браузер

Пример совместного использования

{
    test: /\.(png|jpg)$/i,
    type: 'asset/resource',
    generator: {
        outputPath: 'static/images',
        publicPath: '/cdn/images/',
        filename: '[name][ext]'
    }
}

Кэширование ресурсов

Хэширование файлов позволяет браузеру:

  • хранить изображения в кеше;
  • не скачивать их повторно;
  • обновлять только изменённые файлы.

Обычно используется:

filename: 'images/[contenthash][ext]'

Content Hash и долгосрочное кэширование

Если файл не изменился:

logo.a1b2c3.png

останется прежним.

Если содержимое изменится:

logo.f8d9e2.png

Браузер увидит новый URL и скачает обновлённую версию.


Работа с большими файлами

resource особенно важен для:

  • видео;
  • аудио;
  • больших изображений;
  • архивов;
  • PDF-файлов.

Встраивание таких файлов через Base64 привело бы к:

  • огромному JS-бандлу;
  • увеличению памяти;
  • замедлению загрузки.

Импорт PDF

{
    test: /\.pdf$/i,
    type: 'asset/resource',
    generator: {
        filename: 'docs/[name][ext]'
    }
}

Использование:

import manual from './manual.pdf';

link.href = manual;

Импорт аудио

{
    test: /\.(mp3|wav)$/i,
    type: 'asset/resource',
    generator: {
        filename: 'audio/[name][ext]'
    }
}

Импорт видео

{
    test: /\.(mp4|webm)$/i,
    type: 'asset/resource',
    generator: {
        filename: 'video/[name][ext]'
    }
}

Автоматическая оптимизация

Webpack сам:

  • отслеживает зависимости;
  • удаляет неиспользуемые ресурсы;
  • обновляет ссылки;
  • учитывает хэши.

Использование с HtmlWebpackPlugin

При генерации HTML:

<img src="./logo.png">

Webpack:

  • обработает изображение;
  • создаст ресурс;
  • подставит новый путь.

Импорт через JavaScript

import avatar from './avatar.jpg';

document.querySelector('.avatar').src = avatar;

Webpack гарантирует:

  • корректный путь;
  • наличие файла в сборке;
  • отсутствие конфликтов имён.

Динамический импорт ресурсов

const image = await import('./image.png');

Результат:

image.default

содержит URL ресурса.


Совместимость с Code Splitting

Ресурсы автоматически связываются с чанками.

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

import('./gallery');

то ресурс загрузится только вместе с этим чанком.


Обработка путей в production

В production-сборке:

  • имена обычно хэшируются;
  • пути минифицируются;
  • ресурсы оптимизируются CDN.

Типичный шаблон:

filename: 'assets/[contenthash][ext]'

Типичные ошибки

Неправильный test

Ошибка:

test: /\.png$/

не обрабатывает JPG и GIF.

Лучше:

test: /\.(png|jpg|gif)$/i

Конфликт с loader

Нельзя одновременно использовать:

type: 'asset/resource'

и:

file-loader

Это приведёт к дублированию обработки.


Неверный publicPath

Если путь задан неправильно:

404 Not Found

в браузере.


Отсутствие расширения

Ошибка:

filename: 'images/[name]'

Результат:

logo

без .png.

Следует использовать:

filename: 'images/[name][ext]'

Производительность

Тип resource:

  • уменьшает размер JavaScript;
  • ускоряет парсинг;
  • улучшает кэширование;
  • снижает нагрузку на память браузера.

Особенно заметно это:

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

Когда использовать asset/resource

Тип resource подходит для:

  • изображений;
  • шрифтов;
  • видео;
  • аудио;
  • PDF;
  • архивов;
  • крупных SVG;
  • любых файлов, которые должны существовать отдельно.

Когда не стоит использовать resource

Не рекомендуется для:

  • маленьких иконок;
  • очень мелких SVG;
  • tiny-assets;
  • ресурсов, которые выгоднее встроить в bundle.

В таких случаях чаще используют:

type: 'asset/inline'

или:

type: 'asset'

с автоматическим выбором стратегии.