Webpack 5, кеш и его проблемы



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



Сам по себе, кеш в вебпаке появился достаточно давно: 5 альфа версия вышла почти 3.5 года назад, а офицальный релиз пару лет назад. Но судя по всему, это достаточно непопулярная (а зря) фича, т.к. в процесе настройки я столкнулся с достаточно базовыми проблемами.



1. Кеширование по относительным путям

Вебпак использует абсолютные пути для кеширования. Т.е. кеш для модуля будет записан как /some/abosulte/path/project/src/module.ts, а не относительный src/module.ts. Из-за этого становится невозможно шарить кеш между разными машинами разработчиков, но вы вряд ли хотите это делать. Но так же это становится проблемой в некоторых CI. Jenkins (а мы используем именно его), например, использует пути /home/jenkins/agent/workspace/$PROJECT_NAME_$GIT_BRANCH. Т.е. на каждую ветку мы получаем новую директорию и кеш перестаёт работать. Такой подход удобен, когда у вас есть некий общий хост (например какой-нибудь Mac mini для сборки iOS приложения) и для каждой ветки нужна отдельная директория, т.к. это самый дешевый способ изоляции билдов. В случае веб-разработки почти везде используется докер и это разделение становится бесмыссленым. Благо, в каждом CI пути можно настроить (в дженкинсе это customWorkspace) и это решает проблему. В самом вебпаке всё решили оставить как есть, т.к. относительные пути для кеша будут сильно влиять на перформанс.



2. Инвалидация кеша при использовании import/require.ensure

Тут всё немного проще: кеш инвалидровался на динамических импортах типа import(`./flags/${country}.svg`). Особенно неприятно становится, когда такие импорты где-то в самом начальной точке графа зависимостей. Например, в storybook используются require.ensure (это такой webpack-specific модуль, который придумали ещё до import()) для импорта сторис прям в точке входа, что делало кеш особенно бесполезным, т.к. рекомпилировалось всё и вся. Этот баг уже починили.



3. Ре-экспорты из пустого файла в сочетании с опцией usedExports

Кажется, что этот пункт будет объяснить сложнее всего, но я попробую. В вебпаке есть опция optimization.usedExports. Она отвечает за три-шейкинг, т.е. определяет какие экспорты из модуля действительно используются и маркирует их, позволяя не тащить неиспользуемые в бандл.



А в сторибуке активно используется ре-экспорт модулей, чтобы импортировать модули было удобней. Но есть модули, в которых лежат только типы и при компиляции в JS такой модуль получается пустым. Если взглянуть на пул-ревест, то станет понятней о каком кейсе идёт речь.



В итоге сочетание usedExports и ре-экспортов из пустых модулей вело к инвалидации. Этот баг тоже уже починили.



4. webpack-virtuals-modules не совместим с кешом

Сторибук использует webpack-virtuals-modules. Этот плагин позволяет создавать файлы в оперативной памяти, а вебпак видит их как обычные модули. Надо ли говорить, что оно плохо работает с кешом? Непредсказуемые инвалидации, двойные компиляции и т.д. На самом деле тут даже несколько проблем: раз, два, три.



Но для начала давайте разберемся зачем оно в принципе нужно. Сторибук использует этот плагин, чтобы на основе шаблонов сгенерировать точки входа, которые зависят от текущей конфигурации (путей до сторис, фреймворка и т.д.). Генерация файла происходит в памяти, ничего на диск не попадает, пользователям не нужно ничего добавлять в .gitignore и т.д. Всё просто и красиво.



Но команда webpack не рекомендует использовать такие плагины в принципе. По этой причине мы решили избавиться от этого плагина: каждый билд мы пробегаемся по опциям этого плагина и просто сохраняем файлы на диск. Вроде как, в сторибуке была инициатива об уходе от использования этого плагина, но я найти её не смог.