Всем привет! Меня зовут Иван Вахаев, я Frontend‑разработчик digital‑интегратора 5 УГЛОВ.
В этой статье я расскажу, как мы решили проблему так называемого «белого экрана», вызванного «не определенным» методом в старых версиях webView (размонтирование всего дерева React) в SPA приложении на React, внутри мобильного приложения написанного на Flutter. В момент, когда пользователь переходил с экрана авторизации на экран регистрации или после успешной авторизации переходил на главную страницу, то юзера встречал «белый» экран. Данный баг поймали, к сожалению, не на стадии разработки и не на стадии тестирования, а когда приложение уже вышло в свет и попало на прилавки всеми известных магазинов приложений:( , но, как всегда, сроки были сжатыми, поэтому решение пришлось искать очень быстро.
Расскажу, какие способы мы рассматривали и по какой причине выбрали итоговый (спойлер: пришлось написать собственный полифил).
Структура статьи, чтоб вам было поудобнее:
В чем причина бага
Какие варианты решения мы рассматривали
Наш способ — полифил
Итог
![](https://habrastorage.org/getpro/habr/upload_files/eae/338/04d/eae33804d0ee60b2513aa9cc9b105dec.jpeg)
Причина бага: использование метода at()
Причина была довольно интересной, всему виной был метод at(). Данный метод применяется как на строки, так и на массивы, принимает индекс и возвращает значение по этому индексу. Если мы внимательно обратимся к документации (массивы, строки), то увидим, что метод очень новый и поддерживается только обновленными версиями браузеров и версиями webView.
![Поддержка браузерами метода .at() Поддержка браузерами метода .at()](https://habrastorage.org/getpro/habr/upload_files/b82/a62/76f/b82a6276fc2ae8d4c2954743f6691740.png)
Открыв 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, я сделал поиск по проекту, и НИЧЕГО не нашел.
![Открыв VSCode, я сделал поиск по проекту, и НИЧЕГО не нашел Открыв VSCode, я сделал поиск по проекту, и НИЧЕГО не нашел](https://habrastorage.org/getpro/habr/upload_files/481/8af/08e/4818af08e75a77e6f54aa30e0a207d38.png)
Выводы простые — дело в билде. Открываю папку с билдом, ищу в ней, и нахожу то, что искал. К сожалению, скринов старых билдов не осталось, но единственную связь которую нашел — это лицензия MIT, и подумал что дело в какой‑то из библиотек.
Варианты решений, которые нам не подошли
Начнем с того, что нужно понять, что же можно делать с полученной информацией.
Итак, варианты решения:
Tsconfig.json — это первое, о чем нужно и стоит подумать.
Настройка сборщика.
Узнать что за библиотека, а далее принять решение о замене или отказе.
Библиотека с полифилами.
Стоит начать по порядку и рассказать, почему эти варианты не подошли.
Tsconfig.json — самое верное решение — в первую очередь обратиться к нему, ведь в нем мы можем задать настройки для компиляции TS в JS. Зайдя в файл, обнаружил, что настройка стоит на ESnext, что как раз и приводит к тому, что наш метод не заменяется на что‑то более давно известное. Помним, что в нашем коде не было использовано данного метода, поэтому настройку «target» в этом файле трогать особого смысла нет. Пишем на всякий случай ES5 — все собирается, но в билде мы снова увидим наш неподдерживаемый метод. Поэтому стоит смотреть на настройку «lib» и «module», вижу, указан ESnext, ставлю ES6 — на выходе получаю кучу ошибок. Одна из них — это ошибка на динамический импорт, который мы используем для codesplitting‑a. А значит настройка нашего tsconfig нам уже не подходит.
![Пример: ошибка при изменении настроек tsconfig.json, в коде использовались динамические импорты для codesplitting Пример: ошибка при изменении настроек tsconfig.json, в коде использовались динамические импорты для codesplitting](https://habrastorage.org/getpro/habr/upload_files/f21/1fe/f03/f211fef03d77f84886e738e17eeae2d0.png)
Второй вариант — возможность дополнительно настроить сборщик. На тот момент это казалось очень разумной идей. Перебрав возможные плагины для webpack, я осознал, что это не поможет. Решил переехать на модный Vite+Rollup. В дальнейшем лишь смог ускорить время сборки, но основную проблему это не решило и дело было не в сборщике.
Поиск библиотеки занял большее количество времени, чем я думал. В этом проекте открыл для себя колесо сансары — комментируя одно, делал билд, снова встречал метод. Пройдя так по кругу несколько раз и не найдя проблему, ушел думать над следующим пунктом. (На момент написания статьи я нашел эту библиотеку, у нас установлена версия 6.2.2 и в документации указано что она поддерживает более поздние версии).
![Поддержка версий браузеров библиотекой для просмотра PDF Поддержка версий браузеров библиотекой для просмотра PDF](https://habrastorage.org/getpro/habr/upload_files/96a/cce/d3d/96acced3de645fcd71a18e3dedbb2c88.png)
Тянуть целую библиотеку не было в наших интересах, ведь это дополнительный вес к приложению. А по ТЗ разрабатывали приложение, в котором учитывалась низкая скорость интернета.
Наш вариант и почему мы его выбрали
И последний вариант, на котором я остановился это написание собственного полифила и его подключение в нашу сборку.
Для написания собственного полифила в первую очередь нам потребуется внимательно прочитать документацию того «инструмента», для которого мы собираемся написать полифил.
![Описание метода .at() Описание метода .at()](https://habrastorage.org/getpro/habr/upload_files/adf/21f/518/adf21f518e1a2ce798f88f63faf10a84.png)
Из описания понятно, что мы принимаем в качестве аргумента только целое число, оно может быть как положительным, так и отрицательным. Далее нам нужно определиться с тем, что возвращает массив.
![Описание возвращаемого значения методом .at() Описание возвращаемого значения методом .at()](https://habrastorage.org/getpro/habr/upload_files/194/56c/249/19456c249ab623c26f6ad9fe161eb184.png)
Как мы видим, возвращается значение под данным индексом или же undefined. Нам этого достаточно для написания нашего полифила.
Итак, готовый код:
![Готовый полифил Готовый полифил](https://habrastorage.org/getpro/habr/upload_files/7b8/a77/6e0/7b8a776e067941eeea38f3a2410cd805.png)
В созданном файле я просто определяю метод. В данном случае нам обязательно нужно использовать function declaration, чтобы иметь возможность обращаться к this. Ведь здесь мы можем обратиться к свойствам нашего массива или строки. Как мы уже видели в документации, метод at(0) эквивалентен вызову элемента с индексом array[0].
Далее я лишь определил, что если не передается никакой аргумент при вызове, определить его как 0 (это следует из задачи), в дальнейшем нам просто нужно определить не выходит ли наш индекс за пределы нашего массива, а значит он должен всегда быть в диапазоне от 0 до array.length -1, как для положительных, так и для отрицательных значений индекса.
Возвращаемое значение у нас будет существующим элементом или undefined. Также, я бы советовал при написании любого полифила обращаться напрямую к спецификации ECMA, чтобы узнать, как именно устроен метод.
![Документация ECMA c описанием работы метода at() Документация ECMA c описанием работы метода at()](https://habrastorage.org/getpro/habr/upload_files/23d/8b0/d5b/23d8b0d5bccde1ff2316553bb1474195.png)
Повторюсь, что создание полифила требовалось нам только для использования его самим билдом.Также можно написать проверку на существование данного метода, чтобы не переопределять существующий, если у нас свежая версия браузера или webView.
Данный файл c полифилом подключается в html до подключения основных скриптов самого приложения.
И после написания полифила и его успешного подключения, естественно стоит сначала протестировать его в ручную, написать пару тестов. И после завершения успешных тестов нужно проверить работоспособность на нашем «проблемном» устройстве.
Могли сделать иначе
За сжатые сроки я опробовал несколько вариантов, а затем принял решение в пользу самого быстрого и затрагивающего минимальное количество времени.
Если бы на тот момент было больше времени, то я еще тогда нашел бы библиотеку, в которой использовался данный метод, и взял бы более старую версию, либо нашел бы альтернативную библиотеку.
Хоть сроки были и сжатыми, проблему удалось рассмотреть под разными углами. По итогу я написал свой первый полифил.
Комментарии (9)
MiT_73
03.07.2023 06:18+1Вы использовали хаб Flutter чтобы люди которые подписаны на хаб узнали об:
В этой статье я расскажу, как мы решили проблему так называемого «белого экрана», вызванного «не определенным» методом в старых версиях webView (размонтирование всего дерева React) в SPA приложении на React, внутри мобильного приложения написанного на Flutter.
Простите но это единственная инфа во всей статье об Flutter...
mayorovp
Читал-читал я это и поражался: известно что проблема в методе
.at
. Известно, что в коде он не используется. Зачем вообще играться с tsconfig и форматами модулей не выходе, особенно при использовании бандлера? Что в принципе это могло дать?Разумеется, полифил тут является правильным решением, с него нужно было начинать а не заканчивать.
Кстати, у вас в полифиле ошибка.
[1,2,3].at("-2")
даёт 2, а у вас будет undefined. Если вы не в состоянии глядя на спецификацию написать соответствующий ей код — лучше брать полифилы из пакета core-js. Впрочем, если вы в состоянии писать код — всё равно лучше брать полифилы из core-js.IvanVakhaev Автор
Привет!
Если я правильно вас понял, вы имеете ввиду что не будет работать при передачи аргумента в виде строки. Если мы обращаемся к документации то там написано что передаваемый аргумент должен быть числом, а так же TS при подобном вызове так же будет давать ошибку что тип "string" нельзя назначить типу "number".
mayorovp
Мало ли что в документации написано, реализацию надо писать по спецификации. А в спеке используется операция ToIntegerOrInfinity, которая для строки возвращает записанное внутри число.
AYastrebtsov
Ну в TS config сходить - мысль которая не лишена смысла, глянуть настройки компиляции, во что будет код сгенерирован - очень даже ок. Бывает понизить target вполне срабатывает.