Всем привет! Меня зовут Иван Вахаев, я Frontend-разработчик digital-интегратора 5 УГЛОВ.
Мы специализируемся на продуктовой разработке и развитии сложных интерфейсных проектов, к которым относятся: кросс-платформенные мобильные приложения, e-com и маркетплейсы, корпоративные порталы и CRM на базе Битрикс24 и др.
В этой статье я расскажу, как мы решили проблему так называемого «белого экрана», вызванного «не определенным» методом в старых версиях webView (размонтирование всего дерева React) в SPA приложении на React, внутри мобильного приложения написанного на Flutter. В момент, когда пользователь переходил с экрана авторизации на экран регистрации или после успешной авторизации переходил на главную страницу, то юзера встречал «белый» экран. Данный баг поймали, к сожалению, не на стадии разработки и не на стадии тестирования, а когда приложение уже вышло в свет и попало на прилавки всеми известных магазинов приложений:( , но, как всегда, сроки были сжатыми, поэтому решение пришлось искать очень быстро.
Расскажу, какие способы мы рассматривали и по какой причине выбрали итоговый (спойлер: пришлось написать собственный полифил).
Структура статьи, чтоб вам было поудобнее:
Причина бага: использование метода at()
Причина была довольно интересной, всему виной был метод at(). Данный метод применяется как на строки, так и на массивы, принимает индекс и возвращает значение по этому индексу. Если мы внимательно обратимся к документации (
массивы,
строки), то увидим, что метод очень новый и поддерживается только обновленными версиями браузеров и версиями webView.
Открыв VSCode, я сделал поиск по проекту, и НИЧЕГО не нашел По сути, все эти версии браузеров и версии webview выпущены в июле 2021 года и позднее. Данный метод во время написания кода (разработки) не использовался, поскольку в разработке внутри компании мы сразу используем «старые» методы.
Самое интересное, что 92 версия webView на Android была обновлена на 5+ версию самой операционной системы, т. е. они являются старыми устройствами, и если они обновлялись, то данной проблемы у них не наблюдалось. В связи с этим во время проведения тестирования эта проблема не была обнаружена, потому что тестировалось начиная с 8 версии Android, и на iOS была версия, которая уже получила эти обновления (15.4).
На вопрос: «А как вы узнали, что именно ЭТО послужило ошибкой, вы ведь ее как то нашли?», мы ответим — поскольку вопрос стоял не в приоритете, то список устройств, на которых приложение не работало, сначала узнали, к сожалению, у живых пользователей. Оказалось, что это в основном устройства с iOS.
По всем собранным комментариям мы поняли, что это версии начиная с 15.2 (15.3 в обращениях не было) и ниже.
Написав всему своему окружению, я нашел версию только 15.3. Попросил устройства на время и начал опыты.
Взял устройство, разрешил для Safari доступ к консоли и JS в настройках телефона. Подключив его к Mac, открыл devtools Safari данного устройства в консоли. Зашел в Safari на наш домен (т.к. версия webView что у Safari, что у приложения будет одинаковой и зависеть от OS) и, не успев увидеть даже загрузку, сразу понял, что ошибка воспроизвелась.
В девтулз Safari на MAC я вижу нашу виновницу. Сама по себе ошибка достаточно банальна, а именно — «Uncaught TypeError:.at() is not a function». Первым делом я подумал, что, возможно, это действительно есть в коде. Открыв VSCode, я сделал поиск по проекту, и НИЧЕГО не нашел.
Выводы простые — дело в билде. Открываю папку с билдом, ищу в ней, и нахожу то, что искал. К сожалению, скринов старых билдов не осталось, но единственную связь которую нашел — это лицензия MIT, и подумал что дело в какой‑то из библиотек.
Варианты решений, которые нам не подошли
Начнем с того, что нужно понять, что же можно делать с полученной информацией.
Итак, варианты решения:
-
Tsconfig.json — это первое, о чем нужно и стоит подумать.
-
Настройка сборщика.
-
Узнать что за библиотека, а далее принять решение о замене или отказе.
-
Библиотека с полифилами.
Стоит начать по порядку и рассказать, почему эти варианты не подошли.
-
Tsconfig.json — самое верное решение — в первую очередь обратиться к нему, ведь в нем мы можем задать настройки для компиляции TS в JS. Зайдя в файл, обнаружил, что настройка стоит на ESnext, что как раз и приводит к тому, что наш метод не заменяется на что‑то более давно известное. Помним, что в нашем коде не было использовано данного метода, поэтому настройку «target» в этом файле трогать особого смысла нет. Пишем на всякий случай ES5 — все собирается, но в билде мы снова увидим наш неподдерживаемый метод. Поэтому стоит смотреть на настройку «lib» и «module», вижу, указан ESnext, ставлю ES6 — на выходе получаю кучу ошибок. Одна из них — это ошибка на динамический импорт, который мы используем для codesplitting‑a. А значит настройка нашего tsconfig нам уже не подходит.
- Второй вариант — возможность дополнительно настроить сборщик. На тот момент это казалось очень разумной идей. Перебрав возможные плагины для webpack, я осознал, что это не поможет. Решил переехать на модный Vite+Rollup. В дальнейшем лишь смог ускорить время сборки, но основную проблему это не решило и дело было не в сборщике.
- Поиск библиотеки занял большее количество времени, чем я думал. В этом проекте открыл для себя колесо сансары — комментируя одно, делал билд, снова встречал метод. Пройдя так по кругу несколько раз и не найдя проблему, ушел думать над следующим пунктом. (На момент написания статьи я нашел эту библиотеку, у нас установлена версия 6.2.2 и в документации указано что она поддерживает более поздние версии).
- Тянуть целую библиотеку не было в наших интересах, ведь это дополнительный вес к приложению. А по ТЗ разрабатывали приложение, в котором учитывалась низкая скорость интернета.
Наш вариант и почему мы его выбрали
И последний вариант, на котором я остановился это написание собственного полифила и его подключение в нашу сборку. Для написания собственного полифила в первую очередь нам потребуется внимательно прочитать документацию того «инструмента», для которого мы собираемся написать полифил.
Из описания понятно, что мы принимаем в качестве аргумента только целое число, оно может быть как положительным, так и отрицательным. Далее нам нужно определиться с тем, что возвращает массив.
Как мы видим, возвращается значение под данным индексом или же undefined. Нам этого достаточно для написания нашего полифила.
Итак, готовый код:
В созданном файле я просто определяю метод. В данном случае нам обязательно нужно использовать function declaration, чтобы иметь возможность обращаться к this. Ведь здесь мы можем обратиться к свойствам нашего массива или строки. Как мы уже видели в документации, метод at(0) эквивалентен вызову элемента с индексом array[0].
Далее я лишь определил, что если не передается никакой аргумент при вызове, определить его как 0 (это следует из задачи), в дальнейшем нам просто нужно определить не выходит ли наш индекс за пределы нашего массива, а значит он должен всегда быть в диапазоне от 0 до array.length -1, как для положительных, так и для отрицательных значений индекса.
Возвращаемое значение у нас будет существующим элементом или undefined. Также, я бы советовал при написании любого полифила обращаться напрямую к спецификации ECMA, чтобы узнать, как именно устроен метод.
Повторюсь, что создание полифила требовалось нам только для использования его самим билдом. Также можно написать проверку на существование данного метода, чтобы не переопределять существующий, если у нас свежая версия браузера или webView.
Данный файл c полифилом подключается в html до подключения основных скриптов самого приложения.
И после написания полифила и его успешного подключения, естественно стоит сначала протестировать его в ручную, написать пару тестов. И после завершения успешных тестов нужно проверить работоспособность на нашем «проблемном» устройстве.
Могли сделать иначе
За сжатые сроки я опробовал несколько вариантов, а затем принял решение в пользу самого быстрого и затрагивающего минимальное количество времени.
Если бы на тот момент было больше времени, то я еще тогда нашел бы библиотеку, в которой использовался данный метод, и взял бы более старую версию, либо нашел бы альтернативную библиотеку.
Хоть сроки были и сжатыми, проблему удалось рассмотреть под разными углами. По итогу я написал свой первый полифил.