Webpack 5 — Asset Modules

  • Tutorial

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


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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Пробовали webpack 5?

  • 4,4%да8
  • 95,6%нет174
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 14

    +3

    Количество строк в конфиге осталось такое же, каких-то принципиальных улучшений тоже не добавилось. В чем польза от нового API?

      +1

      Выше уровень абстракции?

        +4
        AM заменяют собой основную функциональность сразу трех лоадеров: file-loader, url-loader и raw-loader, избавляя от необходимости их устанавливать, а в случае с limit, еще и настраивать. Так же, все новшества связанные с АМ будут подъезжать вместе с обновлениями самого вебпака
        АМ — это не про кардинальное уменьшение строк в конфиге
        0

        Сейчас есть ли поддержка webworkers при сборке? Проще говоря содать многопоточность. При больших размеров проекта приходится ждать долго

          0

          На данный момент из коробки это не поддерживается. Пока можно использовать сторонние решения типа https://github.com/trivago/parallel-webpack

            0
            Спасибо
          +5
          Звучит как введение новой абстракции без понятной на то причины, и с добавлением кучи вопросов.

          Если раньше было условно просто: вот есть лоадеры они приводят импорты в js так или иначе, в таком то порядке.

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

          >AM заменяют собой основную функциональность сразу трех лоадеров: file-loader, url-loader и raw-loader.

          Так а почему лоадер не написать который про это ?)
            0
            специальная сущность которая непонятно как с лоадерами взаимодействует

            Описанная логика AM применяется к правилу после запуска на нем лоадеров.
            Но да, наверное это стоит явно описать в документации. Спасибо
            Так а почему лоадер не написать который про это ?)

            Ответил тут habr.com/ru/post/488460 )
              0

              Не тот пост

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

                  Возможно можно добиться zero configuration.js расширяя уже существующие штуки а не вводя совсем новые абстракции.

                  Если я правильно понимаю, то дефолтный конфиг в вебпаке, вместе с глоабльно доступными для изменения дефолтными свойствами лоадеров решил бы проблему, и мне кажется порождал бы меньше вопросов.
                0

                Основная идея в том, что есть «повседневные» задачи, которые могут решаться из коробки, без применения доп.лоадеров. А если нужно что-то более сложное и эдакое, то, пожалуйста, используйте лоадер

              +1
              Всегда считал что одна из сильныз сторон Webpack это его модульность. А теперь модули начали заносится внутрь самого вебпака. Помоему это странный путь развития.
              Можно было бы сделать новые лоудеры, которые делалли бы тоже самое и были бы под крылом создателей вебпака.
                0
                Можно было бы сделать новые лоудеры, которые делалли бы тоже самое и были бы под крылом создателей вебпака.

                Указанные лоадеры итак под крылом команды разработки. Учитывайте, что webpack идет в сторону 0CJS (Zero Configuration JS) для того, чтобы для повседневных задач устанавливать и настраивать как можно меньше дополнительных модулей — установил вебпак и он с минимальными настройками (а в идеале без них) делает все, что нужно.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое