Webpack 5 — Asset Modules

в 23:04, , рубрики: javascript, webpack, webpack 5

Доброго времени суток. Этим постом хочу начать серию статей про новые возможности грядущего 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) можно будет наблюдать в моем телеграмм-канале.

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

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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js