- PVSM.RU - https://www.pvsm.ru -

Webpack 5 — Asset Modules

Доброго времени суток. Этим постом хочу начать серию статей про новые возможности грядущего 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 [1] и при помощи file-loader [2] помещены в директорию с собранным бандлом, а css, при помощи css-loader [3], превратится в нечто подобное:

.logo {
  background: url("eb4c5fa504857.svg") no-repeat;
  background-size: cover;
  width: 75px;
  height: 65px;
}

В какой-то момент нам может понадобиться оптимизировать нашу страничку и мы можем захотеть инлайнить изображения прямо в css. Для этого заменим file-loader на url-loader [4]:

      {
        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 [2]. Файлы, которые будут подпадать под правило с 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 [5]. Файлы, которые будут подпадать под правило с 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 позволяют отказаться от некоторых привычных лоадеров за счет поддержки их функциональности "из коробки".
Полноценный пример можно посмотреть здесь [6].

Когда выйдет webpack 5?

Пока нет точной даты выхода. На момент написания этого гайда, последней версией webpack 5 является beta.13, собирается обратная связь. Вы можете помочь в сборе обратной связи, попытавшись перенести свой проект на webpack 5 (конечно же, пока не используя его а production). Подробнее здесь [7]

P.S

Я и дальше планирую рассказывать о новых возможностях webpack 5 и о самом webpack. Некоторые из статей будут побольше, некоторые поменьше, а совсем мелкие заметки (не только про webpack) можно будет наблюдать в моем телеграмм-канале [8].

Спасибо за внимание.

Автор: Сергей Мелюков

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/346787

Ссылки в тексте:

[1] svgo: https://github.com/svg/svgo

[2] file-loader: https://github.com/webpack-contrib/file-loader

[3] css-loader: https://github.com/webpack-contrib/css-loader

[4] url-loader: https://github.com/webpack-contrib/url-loader

[5] raw-loader: https://github.com/webpack-contrib/raw-loader

[6] здесь: https://github.com/wdxlab/articles/tree/master/webpack-asset-modules

[7] здесь: https://github.com/webpack/webpack/issues/9802

[8] телеграмм-канале: https://t.me/wdxlab

[9] Источник: https://habr.com/ru/post/488464/?utm_source=habrahabr&utm_medium=rss&utm_campaign=488464