ReferenceError: a is not defined / Safari class fields implementation is buggy



Напомню, переделывая компиляцию в монорепозитории, мы взяли самый современный таргет компиляции — esnext.



Само собой, у нас есть пользователи, у которых браузеры старее. Особенно сафари, который обновляется только вместе с операционной системой.



В проектах, которые используют такие пакеты, мы просто транспайлим все нод-модули в скоупе @aviasales — это позволяет не париться на счёт обновления таргета тайпскрипта в монорепе и транспайлить через Babel только то, что нужно на основе текущего конфига Browserslist.



Задеплоив, мы увидели всплеск ошибок «a is not defined» в Rollbar’е в сафари с крайне непонятным стек-трейсом.



Ну, начали разбираться. Честно говоря, даже немного стыдно, т.к. проблема оказалось совсем нелепой, а для того, чтобы понять в чём дело понадобилось аж 3 разработчика…



Начали с поиска проблемного места.



Беда в том, что у нас регулярно возникают проблемы с сорсмапами в ролбаре — то они не заливаются, то не подхватываются в веб-интерфейсе. Из-за этого в ролбаре стектрейс частенько указывает на минифицированные файлы, в которых, как вы понимаете, бесполезно что-то искать: 3 строка и 58375 колонка, хех! А в дев-режиме проблема, естественно, не воспроизводилась.



Несмотря на кажущуюся бесмысленность, я всё таки попробовал поискать нужную колонку — никакой переменной «a» там и рядом не было.



Решили попробовать выкатить неминифицированный код, чтобы убедиться, что проблема именно в минифакторе, т.к. это самое большое отличие дев и прод режимов. Выкатили — проблема ушла. Обновляем terser (минифактор, который использует вебпак) — не помогает.



Решаем попытаться отловить проблему локально в прод режиме с включенными сорсмапами, и у одного из нас получается! Но при этом стектрейс абсолютно бесмысленный, указывает чёрт знает куда. А ещё проблема очень плавающая — появляется при первом визите без кеша и то не всегда. Загадка!



Ищем ишью в терсере, бабеле — ничего не находим. Настроение упадническое, т.к. пытаемся разобраться уже дня 4, пробуем переписывать какие-то странные места со смесью UMD, ESM и CommonJS — ничего не помогает.



Я набираюсь сил и отправляюсь на поиски злосчастной переменной «a». Прогоняю минифицированный файл через преттиер включаю в поиске Match Whole Word и нахожу 100+ переменных. Проверяю каждую из них: где объявляется и как используется. Смотрю раз, смотрю два, смотрю три и нахожу подозрительное место — незатранспайленный class field. Примерно в это же время мой напарник скидывает мне ишью Safari class fields implementation is buggy - ReferenceError: Can't find variable. Чудесно.



Проблема выглядит так:

import helper from './helper'



class C {

property = helper(() => {})

}




Т.е. если в class field есть вызов функции, объявленной за пределами класса, то всё взрывается, а стектрейс указывает на абсолютно другое место.



Но почему так в принципе получилось? А всё просто — я забыл добавить ту самую транспиляцию нод-модулей со скоупом @aviasales… Стыдно!



Но ещё более стыдно за сам процесс дебага и сколько времени он занял (напомню, 4 дня).



Ретроспективно становится очевидно, что процесс дебага должен был быть другим:

1. Действительно имело смысл попробовать найти место в коде на основе строки и столбца, хоть этот пункт и не увенчался успехом.

2. Вместо деплоя неминифированного кода, нужно было выключить mangle. Эта опция отвечает за переименование переменных: yourVeryLongVariable → a — и тогда в ошибке мы бы увидели оригинальную переменную и сразу было бы понятно, на какой участок кода смотреть.



Вот так вот просто, да! А ещё мы почему-то не посмотрели на статистику браузеров в Ролбаре — тогда стало бы очевидно, что в 16-ом сафари проблему починили, и именно по этому только у одного из нас проблема иногда воспроизводилась — у него был 15-ый сафари.



В общем, ещё раз стыдно! Вроде бы опытные ребята, а так затупили! В оправдание остается сказать, что это была первая рабочая неделя после новогодних праздников — видимо мозги не включились ещё :D