1.6Kпросмотров
91.8%от подписчиков
7 мая 2025 г.
Score: 1.8K
Привет! Продолжая кейс с высоким потреблением памяти, обнаружили таки утечку в приложении, и она довольно интересная. Есть такая либа day.js, и ее система плагинов - https://day.js.org/docs/en/plugin/plugin В этой документации рекомендованный подход к расширению - перезапись прототипа класса dayjs: // overriding existing API
// e.g. extend dayjs().format()
const oldFormat = dayjsClass.prototype.format dayjsClass.prototype.format = function(arguments) { // original format result const result = oldFormat.bind(this)(arguments) // return modified result
} У приложения - своя обертка над day.js где как раз есть такой плагин, перезаписывающий метод прототипа. Далее, что мы имеем:
- эта обертка и сам day.js вынесены в shared Module Federation чанк
- хостовое приложение поставляет этот чанк
- в приложении загружается код микрофронта, который использует этот чанк Код микрофронта имеет такой жизненный цикл на сервере:
- загружаем код микрофронта как строку
- компилируем строку и получаем JS модуль с экспортами
- сохраняем этот модуль в LRU кэш (микрофронтов может быть много, новые версии появляются, старые становятся не нужны)
- инициализируем Module Federation Проблема - код микрофронта и его скомпилированный результат многократно дублируются в памяти, при этом прослеживается очень длинный трейс - ссылка на метод dayjs. После долгой отладки мы обнаружили, что:
- код микрофронта в какой-то момент вытесняется из кэша и загружается заново
- код микрофронта инициализируется и вызывает shared модуль - обертку над day.js, что бы получить его экспорты
- shared модуль переиспользуется тот же самый, но заново выполняется код плагина через dayjs.extend(plugin) Только на этом вызове, метод в прототипе const oldFormat = dayjsClass.prototype.format - это уже тот метод, который один раз мы заманкипатчили на предыдущем вызове плагина! В итоге на каждое вытеснение микрофронта из кэша мы получаем матрешку перезаписи метода в прототипе, если визуализировать это в псевдокоде:
dayjsClass.prototype.format = myFnc
- dayjsClass.prototype.format = myFnc(myFnc) - dayjsClass.prototype.format = myFnc(myFnc(myFnc)) - dayjsClass.prototype.format = myFnc(myFnc(myFnc(myFnc))) ... Наглядный кейс про сайд-эффекты, shared зависимости и синглтоны. Что осталось не до конца понятным - как именно утекает ссылка на исходники микрофронта в контекст условной myFnc в коде shared чанка.