Asset Modules в Webpack 5: замена file-loader и url-loader

До появления Webpack 5 обработка изображений, шрифтов, SVG-файлов и других статических ресурсов выполнялась через отдельные загрузчики:

  • file-loader
  • url-loader
  • raw-loader

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

Webpack 5 внедрил встроенный механизм Asset Modules, который заменяет большую часть возможностей этих загрузчиков без необходимости их отдельной установки.

Asset Modules позволяют:

  • импортировать файлы напрямую из JavaScript и CSS;
  • автоматически генерировать файлы в директории сборки;
  • преобразовывать ресурсы в Base64;
  • получать исходный текст файлов;
  • гибко управлять именами и путями ресурсов.

Основные типы Asset Modules

Webpack 5 поддерживает пять основных типов ресурсов:

Тип Назначение
asset/resource Генерация отдельного файла
asset/inline Встраивание файла в Base64
asset/source Получение содержимого файла как строки
asset/bytes Получение бинарных данных
asset Автоматический выбор между inline и resource

asset/resource

Назначение

Тип asset/resource заменяет file-loader.

Webpack копирует файл в директорию сборки и возвращает путь к нему.


Базовая конфигурация

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

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

import logo fr om './images/logo.png';

console.log(logo);

Webpack вернёт строку:

/dist/8f3a2c1e.png

Результат сборки

dist/
├── main.js
├── 8f3a2c1e.png

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

Использование generator.filename

Webpack позволяет управлять структурой файлов через generator.

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

Плейсхолдеры

Плейсхолдер Значение
[name] Имя файла
[ext] Расширение
[hash] Хеш файла
[contenthash] Хеш содержимого
[query] Query-строка

Пример

Исходный файл:

src/assets/logo.png

После сборки:

dist/images/3f2c1d8a.png

asset/inline

Назначение

Тип asset/inline заменяет url-loader в режиме Base64.

Файл не создаётся отдельно — он встраивается прямо в bundle.


Конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        type: 'asset/inline'
      }
    ]
  }
};

Импорт SVG

import icon fr om './icon.svg';

console.log(icon);

Webpack вернёт:

data:image/svg+xml;base64,...

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

Подходит для:

  • маленьких SVG;
  • иконок;
  • tiny images;
  • небольших шрифтов;
  • ресурсов размером до нескольких килобайт.

Недостатки Base64

Base64 увеличивает размер данных примерно на 30%.

Поэтому крупные файлы:

  • ухудшают размер bundle;
  • увеличивают время загрузки JavaScript;
  • ухудшают кэширование.

asset/source

Назначение

Тип asset/source заменяет raw-loader.

Webpack загружает содержимое файла как строку.


Конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        type: 'asset/source'
      }
    ]
  }
};

Импорт текста

import text fr om './info.txt';

console.log(text);

Результат

"Hello Webpack"

Использование asset/source

Частые сценарии:

  • загрузка markdown-файлов;
  • импорт HTML-шаблонов;
  • работа с GLSL-шейдерами;
  • импорт SQL-запросов;
  • текстовые конфигурации.

asset/bytes

Назначение

Тип asset/bytes предоставляет доступ к бинарным данным.

Webpack возвращает Uint8Array.


Конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.bin$/,
        type: 'asset/bytes'
      }
    ]
  }
};

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

import data fr om './file.bin';

console.log(data);

Результат

Uint8Array(...)

asset

Автоматический режим

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

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

Конфигурация

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

Поведение по умолчанию

Webpack анализирует размер файла:

  • маленькие файлы → inline;
  • большие → отдельный resource.

Настройка лимита inline

parser.dataUrlCondition.maxSize

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        }
      }
    ]
  }
};

Что делает maxSize

maxSize: 8 * 1024

означает:

8 KB

Если файл меньше:

  • используется Base64.

Если больше:

  • создаётся отдельный файл.

Сравнение asset/resource и asset

Характеристика asset/resource asset
Всегда создаёт файл Да Нет
Может inline Нет Да
Аналог file-loader Да Частично
Аналог url-loader Нет Да

Настройка output.assetModuleFilename

Глобальное имя ресурсов

module.exports = {
  output: {
    assetModuleFilename: 'assets/[hash][ext][query]'
  }
};

Результат

dist/
└── assets/
    ├── a12bc3.png
    ├── f9de21.svg

Приоритет generator.filename

Если указан generator.filename, он имеет приоритет над:

output.assetModuleFilename

Разделение ресурсов по каталогам

Изображения

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

Шрифты

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

SVG

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

Импорт ресурсов в CSS

Asset Modules работают не только в JavaScript.


Пример CSS

.logo {
  background-image: url('./images/logo.png');
}

Webpack автоматически обработает файл через Asset Modules.


Работа с publicPath

Глобальный publicPath

module.exports = {
  output: {
    publicPath: '/static/'
  }
};

Результат

/static/images/logo.png

Настройка publicPath для конкретного правила

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

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

generator: {
  filename: 'images/[hash][ext]',
  publicPath: 'https://cdn.site.com/'
}

Отключение обработки через JavaScript

Иногда необходимо получить URL файла без его автоматической обработки.


new URL()

Webpack 5 поддерживает:

const image = new URL('./logo.png', import.meta.url);

Результат

URL {
  href: '/dist/logo.png'
}

Asset Modules и tree shaking

Webpack может исключать неиспользуемые ресурсы из сборки.


Пример

import './logo.png';

Если импорт нигде не используется и не имеет side effects, ресурс может быть удалён.


Влияние на производительность

Плюсы Asset Modules

Меньше зависимостей

Не требуется:

npm install file-loader url-loader raw-loader

Проще конфигурация

Webpack 4:

{
  test: /\.png$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        lim it: 8192
      }
    }
  ]
}

Webpack 5:

{
  test: /\.png$/,
  type: 'asset'
}

Быстрее сборка

Встроенная реализация работает быстрее сторонних loader-пакетов.


Миграция с file-loader

Webpack 4

{
  test: /\.(png|jpg)$/i,
  loader: 'file-loader',
  options: {
    name: '[name].[hash].[ext]',
    outputPath: 'images'
  }
}

Webpack 5

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

Миграция с url-loader

Webpack 4

{
  test: /\.svg$/,
  loader: 'url-loader',
  options: {
    lim it: 4096
  }
}

Webpack 5

{
  test: /\.svg$/,
  type: 'asset',
  parser: {
    dataUrlCondition: {
      maxSize: 4 * 1024
    }
  }
}

Миграция с raw-loader

Webpack 4

{
  test: /\.txt$/,
  use: 'raw-loader'
}

Webpack 5

{
  test: /\.txt$/,
  type: 'asset/source'
}

Совместное использование с loaders

Asset Modules можно комбинировать с загрузчиками.


Пример с image-webpack-loader

{
  test: /\.(png|jpg)$/i,
  type: 'asset/resource',
  use: [
    {
      loader: 'image-webpack-loader'
    }
  ]
}

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

Оптимизация правил

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

Inline SVG против отдельного файла

Inline SVG

Преимущества:

  • отсутствие HTTP-запросов;
  • быстрый рендер;
  • удобно для иконок.

Недостатки:

  • увеличивает bundle;
  • хуже кэшируется.

SVG как resource

Преимущества:

  • независимое кэширование;
  • меньше размер JS.

Недостатки:

  • отдельный запрос.

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

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

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

Почему contenthash важен

При изменении файла:

logo.png

изменяется:

logo.a13d82.png

Браузер получает новую версию вместо кэшированной.


Типичная структура конфигурации

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        },
        generator: {
          filename: 'images/[contenthash][ext]'
        }
      },
      {
        test: /\.svg$/i,
        type: 'asset/inline'
      },
      {
        test: /\.(woff2?|ttf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name][ext]'
        }
      },
      {
        test: /\.txt$/i,
        type: 'asset/source'
      }
    ]
  }
};

Частые ошибки

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

Ошибка:

test: /\.png/

Лучше:

test: /\.png$/i

Конфликт loader и type

Неверно:

{
  test: /\.png$/,
  loader: 'file-loader',
  type: 'asset/resource'
}

Asset Modules заменяют старые loaders, поэтому совместное использование обычно не требуется.


Слишком большой inline lim it

Ошибка:

maxSize: 500 * 1024

Проблемы:

  • огромный JS bundle;
  • медленная загрузка;
  • высокий расход памяти.

Рекомендации по использованию

Тип ресурса Рекомендуемый тип
Большие изображения asset/resource
Иконки SVG asset/inline
Шрифты asset/resource
Markdown asset/source
Небольшие картинки asset

Asset Modules и production-сборка

Для production чаще всего используются:

type: 'asset'

и:

contenthash

Это обеспечивает:

  • автоматическую оптимизацию;
  • эффективное кэширование;
  • уменьшение числа запросов;
  • удобную структуру сборки.

Полная production-конфигурация

const path = require('path');

module.exports = {
  mode: 'production',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash].js',
    clean: true,
    assetModuleFilename: 'assets/[hash][ext][query]'
  },

  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024
          }
        },
        generator: {
          filename: 'images/[contenthash][ext]'
        }
      },

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

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

      {
        test: /\.(txt|md)$/i,
        type: 'asset/source'
      }
    ]
  }
};