Гидратация относится к процессу на стороне клиента, в течение которого Vue берёт статический HTML, отправленный сервером, и превращает его в динамический DOM, который может реагировать на изменения данных на стороне клиента. Подробнее тут.«Прод» просто падал, а dev-версия сообщала, что имеются расхождения в dom. А так как dev-версия не падает при попытке гидратации, а только сообщает об этом в консоли, ошибка была неочевидна и пока мы ее нашли, прошло довольно много времени.
Очень интересная стратегия от Vue – подождать продакшена и там упасть!Полторы сотни компонентов разной сложности задачу не упрощали. В итоге удалось увидеть проблему, найти подходящее устройство и подружить его с консолью разработчика.
В итоге выяснилось, что падает наше приложение при подключении компонента футера. И когда нашли нужную строку, просто не поверили глазам. Ожидали все, что угодно, только не этого.
Оказалось, что когда удаляешь номер телефона, гидратация проходила без проблем. Когда начали копаться, выяснилось, что Safari после получения html-верстки подставлял рядом с телефоном тег a, который вызывал, собственно, приложение набора номера.
Естественно, когда начиналась гидратация, dom-а пришедшей с сервера страницы и вновь построенного виртуального не совпадали. Приложение падало без объявления войны.
Решилась это проблема тоже довольно неожиданно. До сего момента телефон мы вставляли обычным образом:
<div>8 (800) 111 2 333</div>
Решением проблемы стал биндинг v-text:
<div v-text=”8 (800) 111 2 333”></div>
У меня есть теория на этот счет. Если кто-то сможет подтвердить ее или опровергнуть (предложив новую), буду очень признателен. Как я понимаю, после того, как Safari получил документ, Vue строит виртуальный dom и сравнивает его с этим документом и пока он этот документ гидратирует, Safari занимается своим тёмным делом и меняет телефон на ссылку. Когда доходит дело до этого поля, Vue с помощью v-text снова заменяет содержимое нашего дива на нужное нам. В итоге на момент сравнения dom-а совпадают, полет нормальный.
Комментарии (28)
staticlab
21.11.2018 12:14+1<meta name="format-detection" content="telephone=no">
не спасёт отца русской демократиии?
a_e_tsvetkov
21.11.2018 12:38+1А завтра они решат адреса в ссылки на карте превращать. Что тогда делать?
ZaEzzz
21.11.2018 12:41Думаю, спасет.
Мы тоже недавно наткнулись на необходимость использования этого метатега — при отображении статистики некоторые цифры превращались в кликабельные телефонные номера.
UPD. Кстати, сейчас задумался. Никогда такой необходимости не возникало, но все же. А что делать, если нужно, чтобы сафари поменял запись на кликабельный номер, и при этом не упало само приложение на стадии гидратации. Сходу на ум приходит только забить на элемент и не трогать его совсем.HAGer2000 Автор
21.11.2018 13:26На мой взгляд, в текущей реализации этого функционала в Сафари, это вряд ли возможно. Он меняет дом, меняет атрибуты, создает новые ноды в дереве. При таком раскладе гидратация невозможна.
Ну либо эту часть сайта генерить только на клиенте, чтобы сервер вообще не знал о наличии телефоновahmpro
21.11.2018 13:42А нельзя просто исключить этот кусок через no-ssr? Так ли критично, чтобы он гидрировал? :)
HAGer2000 Автор
21.11.2018 13:50по идее, эта информация может быть полезной для индексации
по крайней мере, в нашем случае
ну а так, наверное можно так сделать
даже любопытно, что получится в результате? будет ли сафари потрошить такой, сгенерированный, код. но страница, скорее всего не обрушится
mayorovp
21.11.2018 18:23Очевидно, в такой ситуации нужно превратить его в кликабельный номер самостоятельно.
Fragster
21.11.2018 16:33В документации прямо так и написано:
В режиме разработки Vue будет проверять, что виртуальное дерево DOM, сгенерированное на стороне клиента, совпадает с структурой DOM созданной на сервере. При нахождении несоответствия, он будет считать гидратацию неудачной, отказываться от существующего DOM и рендерить всё с нуля. В режиме production, эта проверка отключена для достижения максимальной производительности.
Но вообще если apple так делает, то, вероятно, надо завести issue, чтобы vue изменил это поведение (не падал, а перерендерил компонент просто).
gli
22.11.2018 11:56Зашел только чтобы поругать за «желтушность» заголовка. Давайте хабр не будет превращаться в стандартные желтые издания, которые привлекают аудиторию не значимым контекстом, а заманчивым заголовком.
«И вы не поверите, что мы увидели»
«Когда он открыл дверь, он не поверил своим глазам»
«Чтобы никогда не иметь проблем с потенцией надо всего лишь купить ...»
Тьфу, расстроился.
CyberAP
22.11.2018 13:09То что вы описали это ошибка Vue. Гидратация должна всегда быть failsafe. Safai конечно тоже молодец что без спроса меняет DOM, но такие вещи не должны ломать всё приложение.
HAGer2000 Автор
22.11.2018 15:10полностью согласен
правда слабо представляю себе, как это на практике починить
aamonster
Я правильно понял, что ваше решение ломает «кликабельность» имеющихся на страничке номеров телефонов? Или вы их делаете кликабельными самостоятельно, и «искуственный интеллект» вам не нужен?
HAGer2000 Автор
Надо поэкспериментировать, пока под рукой нет подходящего девайса. Вообще, от «бизнеса» не было задачи делать телефоны кликабельными.
По идее, если ссылки сами по себе уже содержат указание на кликабельность телефона, вряд ли сафари будет в это дело вмешиваться. Другое вопрос, это, как бы, вообще не его дело, кликабельны телефоны или нет. Хочешь сделать такой функционал, решай вопрос на уровне DOM-свойств и событий, зачем подменять html?
aamonster
Погодите. Как, где и зачем Safari подменяет html? Если я правильно понял, оно его просто парсит чуть иначе, чем «гидратация» — т.е. отличия именно на уровне DOM. Вроде в пределах допустимого (более того: аналогичный пример прописан в руководстве по данной вами ссылке).
Но решение vue.js, конечно, феерическое: сделать обход этой ситуации в дебаге — это додуматься надо. Всю жизнь принято делать в дебаге assert — чтобы fail fast и всё такое, а если уж делается обход для кривого случая — в релизе он 100% обязан использоваться. Дебаг может (и должен) упасть раньше релиза, но не упасть там, где релиз сломается — большой косяк.
HAGer2000 Автор
ну как… у нас есть dom-узел, у него есть свойства, которые появляются при парсинке html-разметки. То есть, в разметке мы прописываем тегу атрибуты (attributes), которые в итоге отражаются в свойства (properties) dom-узла. Если нам надо навесить обработчик события на какой-то элемент, в данном случае номер телефона, то нам надо работать именно с dom-элементом. Я об этом.
Сафари поступает иначе. Он получает html. Во вкладке Network видно, какой был ответ от сервера. Затем меняет текстовую ноду с телефоном на ссылку с href=«tel: тра-та-та» затем парсит это в dom-ноду с соответствующими свойствами. То есть, можно было бы (если я нигде не ошибся) повесить обработчик на эту ноду на уровне dom-properties не меняя состава dom-дерева. Тут, я правда, не уверен, при сравнении во время гидратации, не будет ли расхождением разные свойства этой самой ноды…
aamonster
Ну так пример с tbody — ровно то же самое. Не было никакого tbody в html, но браузер его втыкает, потому что считает, что так будет лучше =).
Очень плохо иметь два независимых парсера html.
А насчёт расхождения при гидратации — возможно, и это будет ещё фееричнее: дебаг и релиз будут просто работать на разном DOM.
HAGer2000 Автор
Возможно и то же самое. Но лично для меня такая подстава с номером телефона была неожиданной
aamonster
Естественно, неожиданной
А теперь время для стука в дно: на десктопных браузерах есть энное количество расширений, правящих DOM. И в идеале бы с ними не конфликтовать...
Zavtramen
Так телефоны надо через href=«tel:» делать вроде.
aamonster
Я так понял, на сайтах очень часто такой URI не прописан, и Сафари умничает — чтобы в написанный на сайте телефон всегда можно было кликнуть.