Доброго времени суток. Этим постом хочу начать серию статей про новые возможности грядущего webpack 5. Почему я хочу рассказывать про webpack? Как минимум потому, что я принимаю активное участие в его разработке и постоянно копаюсь в его внутренностях. В данном посте хочу рассказать про Asset Modules — экспериментальную фичу webpack 5, которая позволяет избавиться от нескольких привычных лоадеров, сохранив при этом их пользу.
Представим, что нам нужно собрать страницу с картинками и стилями.
Решение на webpack 4
Конфигурация webpack 4 под эту задачу может выглядеть следующим образом:
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.svg$/,
use: [
'file-loader',
'svgo-loader'
]
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};
src/index.js
import './styles.css';
// ...
src/styles.css
.logo {
background: url("/images/logo.svg") no-repeat;
background-size: cover;
width: 75px;
height: 65px;
}
Вывод:
/dist/main.js
/dist/eb4c5fa504857.svg
При таком подходе все svg-файлы будут обработаны при помощи svgo и при помощи file-loader помещены в директорию с собранным бандлом, а css, при помощи css-loader, превратится в нечто подобное:
.logo {
background: url("eb4c5fa504857.svg") no-repeat;
background-size: cover;
width: 75px;
height: 65px;
}
В какой-то момент нам может понадобиться оптимизировать нашу страничку и мы можем захотеть инлайнить изображения прямо в css. Для этого заменим file-loader
на url-loader:
{
test: /.svg$/,
use: [
- 'file-loader',
+ 'url-loader',
'svgo-loader'
]
},
Вывод:
/dist/main.js
Собранный css изменится следующим образом:
- background: url("eb4c5fa504857.svg") no-repeat;
+ background: url("data:image/svg+xml;base64,....") no-repeat;
Далее мы можем захотеть инлайнить только небольшие по размеру svg (например, до 8кб), а все остальные оставлять в виде отдельно лежащих файлов. Для этого, у url-loader
есть специальная настройка limit:
{
test: /.svg$/,
use: [
- 'url-loader',
+ 'url-loader?limit=8192',
'svgo-loader'
]
},
После этого, инлайниться будут только svg-файлы до 8кб, остальные svg будут помещены в директорию с собранным бандлом, для них url-loader
будет неявно использовать file-loader
.
Задача решена, но с использованием webpack 5 и фичи Asset Modules, она решается проще, позволяя избавиться от url-loader
и file-loader
(его url-loader
неявно использует для файлов, размером больше, чем указано в опции limit
).
Решение на webpack 5
Для начала, необходимо явно указать, что мы хотим использовать Asset Modules. Для этого добавим в конфигурацию следующее:
module.exports = {
// ...
+ experiments: {
+ asset: true
+ }
};
На данный момент это экспериментальная фича и мы ждем от пользователей обратную связь по ее использованию.
После этого, достаточно просто пометить svg-файлы как asset
и всё то, что было описано выше касаемо file-loader
и url-loader
— заработает само, из коробки, без каких-либо лоадеров:
{
test: /.svg$/,
- use: [
- 'url-loader?limit=8000',
- 'svgo-loader'
- ]
+ type: 'asset',
+ use: 'svgo-loader'
},
Вот и всё, для файлов, которые попадают под правило с type: 'asset'
будет применяться следующая логика: Если файл меньше 8кб (по умолчанию), то встроить его в собранный бандл, в ином случае поместить его в директорию с собранным бандлом.
Свойство use
так же учитывается.
Помимо asset
есть и другие встроенные типы модулей.
asset/inline
Это аналог url-loader
. Файлы, которые будут подпадать под правило с type: 'asset/inline'
будут всегда инлайниться в бандл в виде data-url:
{
test: /.svg$/,
- type: 'asset',
+ type: 'asset/inline',
use: 'svgo-loader'
},
Более того, для type: 'asset/inline'
можно задавать кастомный генератор data-url.
Например, для svg-файлов можно использовать mini-svg-data-uri
, который инлайнит svg как data-url, но без использования base64, что позволяет уменьшить размер встроенного фрагмента:
+ const miniSVGDataURI = require('mini-svg-data-uri');
// ...
{
test: /.svg$/,
type: 'asset/inline',
+ generator: {
+ dataUrl(content) {
+ content = content.toString();
+ return miniSVGDataURI(content);
+ }
+ },
use: 'svgo-loader'
},
В результате получим такой css:
- background: url("data:image/svg+xml;base64,....") no-repeat;
+ background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'....") no-repeat;
Таким образом можно совмещать использование лоадеров и кастомное поведение для генерирования data-url.
asset/resource
Это аналог file-loader. Файлы, которые будут подпадать под правило с type: 'asset/resource'
будут складываться в директорию с бандлом:
{
test: /.svg$/,
- type: 'asset/inline',
+ type: 'asset/resource',
- generator: {
- dataUrl(content) {
- content = content.toString();
- return miniSVGDataURI(content);
- }
- },
use: 'svgo-loader'
},
Указываем путь
По умолчанию, модули с типом asset/resource
складываются в директорию, которую вы указываете в output.path
(по умолчанию dist
), но при помощи output.assetModuleFilename
можно переопределить это поведение:
module.exports = {
+ output: {
+ assetModuleFilename: 'assets/[name][ext]'
+ },
// ...
};
Вывод:
/dist/main.js
/dist/assets/logo.svg
А заменив [name]
на [hash]
мы получим прекрасный вариант для long term caching:
module.exports = {
output: {
- assetModuleFilename: 'assets/[name][ext]'
+ assetModuleFilename: 'assets/[hash][ext]'
},
// ...
};
Вывод:
/dist/main.js
/dist/assets/eb4c5fa504857.svg
Более того, мы можем переопределить имя выходного файла для конкретного asset-правила. Например, можно складывать svg-иконки в директорию dist/icons
, а остальные asset-модули в директорию dist/assets
:
{
test: /.svg$/,
type: 'asset/resource',
+ generator: {
+ filename: 'icons/[hash][ext]'
+ },
use: 'svgo-loader'
Вывод:
/dist/main.js
/dist/assets/fd441ca8b6d00.png
/dist/icons/eb4c5fa504857.svg
asset/source
Это аналог raw-loader. Файлы, которые будут подпадать под правило с type: 'asset/source'
будут всегда инлайниться в бандл в неизменном виде:
file.txt
hello world
webpack.config.js
module.exports = {
// ...
{
test: /.svg$/,
type: 'asset/resource',
generator: {
filename: 'icons/[hash][ext]'
},
use: 'svgo-loader'
},
+ {
+ test: /.txt$/,
+ type: 'asset/source'
+ },
// ...
index.js
import './styles.css';
+ import txt from './file.txt';
+ console.log(txt); // hello world
Вывод:
/dist/main.js
/dist/icons/eb4c5fa504857.svg
asset
Объединяет в себе asset/resource
и asset/inline
, автоматически выбирая что-то одно, в зависимости от некоторых условий. По умолчанию, если размер файла больше 8кб, то применяется стратегия asset/resource
, в ином случае — asset/inline
.
module.exports = {
// ...
{
test: /.svg$/,
- type: 'asset/resource',
+ type: 'asset'
- generator: {
- filename: 'icons/[hash][ext]'
- },
use: 'svgo-loader'
},
{
test: /.txt$/,
type: 'asset/source'
},
// ...
Лимит для применения asset/inline
можно установить:
{
test: /.svg$/,
type: 'asset',
+ parser: {
+ dataUrlCondition: {
+ maxSize: 20 * 1024 // 20kb
+ }
+ },
use: 'svgo-loader'
},
Подводя итог: Asset Modules в webpack 5 позволяют отказаться от некоторых привычных лоадеров за счет поддержки их функциональности "из коробки".
Полноценный пример можно посмотреть здесь.
Когда выйдет webpack 5?
Пока нет точной даты выхода. На момент написания этого гайда, последней версией webpack 5 является beta.13
, собирается обратная связь. Вы можете помочь в сборе обратной связи, попытавшись перенести свой проект на webpack 5 (конечно же, пока не используя его а production). Подробнее здесь
P.S
Я и дальше планирую рассказывать о новых возможностях webpack 5 и о самом webpack. Некоторые из статей будут побольше, некоторые поменьше, а совсем мелкие заметки (не только про webpack) можно будет наблюдать в моем телеграмм-канале.
Спасибо за внимание.
Автор: Сергей Мелюков