Разбитое сердце
- 11 марта 2026
Или: как одна нелепая строка кода дала ускорение в 100 раз.
Всегда знаешь, что попался хороший баг, когда первая реакция — «как такое вообще возможно?»
Я дорабатывал панель управления одного веб-приложения и заметил, что она стала загружаться вечность. Раньше страница открывалась за секунду — теперь за десять. Что-то явно шло не так.
Естественно, я обвинил React.
Ну, конечно, в современном веб-приложении источников проблем с производительностью хватает: сторонний JavaScript, перегруженные серверы, раздутые ресурсы, отсутствующие индексы в базе данных — длиннющий список. Но десятилетия разработки для веба подсказывали: это проблема фронтенда. Я просто чувствовал это. Страница выглядела дёргающейся при загрузке. А экосистема React, при всей своей относительной приемлемости сегодня, полна способов превратить кодовую базу в запутанный тормозящий клубок.
Чтобы подтвердить теорию, я объяснил Claude, что панель загружается медленно, что наверняка виноват React, и попросил проанализировать проблемы и отсортировать их по серьёзности. И действительно, Claude нашёл кучу подозрительного в React: лишние перерисовки, пропущенные мемоизации. Выяснилось, что мы ещё не перешли на React Compiler. Я попросил Claude сделать первый проход по самым простым и серьёзным проблемам, и…
Это почти ничего не дало. Может, дело не в React?
Засучил рукава и начал разбираться по-настоящему.
- Может, сервер медленный? Немного — но фронтенд он не блокирует.
- Проблема во всех браузерах? Нет. Почему-то только в Safari.
- Значит, виноват сторонний JavaScript? Intercom? Нет. PostHog? Нет.
- Ладно, роем глубже — смотрим временную шкалу производительности.
Инспектор производительности Safari за последние годы заметно разошёлся с инструментами на основе Chromium и на этой странице работал капризно. Но картину он нарисовал вполне отчётливую: 7+ секунд тратились не на разбор JavaScript, не на вычисление стилей и не на загрузку сети. 94% процессора на M1 Max уходило на… компоновку вёрстки?
В подробностях было видно несколько проходов компоновки длительностью более 1600 мс каждый. Для сравнения: это примерно в 100 раз медленнее нормы. Что-то было серьёзно не так с тем, как браузер выстраивал страницу. Flexbox может чуть замедляться, но не настолько же.
Пора разбирать по кусочкам
Я взялся за проверенный инструмент, ставший ещё полезнее в эпоху ИИ: бинарный поиск. Объясняешь симптом своему агенту-программисту, затем просишь его поочерёдно удалять фрагменты кода, которые могут быть причиной проблемы, и проверять, помогает ли это. Когда находишь что-то, что устраняет проблему, итеративно возвращаешь всё обратно — пока не останется минимальное изменение, указывающее на суть проблемы и обходной путь.
Это особенно быстро, когда агент сам видит проблему. Инструмента профилирования Safari для командной строки у меня не было, но уже через 10 минут — подсказывая Claude, помогло ли очередное изменение, и объясняя, что мы узнали на каждом шаге — мы нашли виновника.
Эмодзи сердечка. ❤️
Стоило убрать эмодзи с кнопки «Отправить отзыв» (я добавил его совсем недавно) — и Safari укладывал компоновку страницы в 2 мс. Стоило вернуть — 1600 мс на каждый из нескольких проходов компоновки.
Я люблю использовать эмодзи в прототипах интерфейсов: добавить их проще простого, а грузятся они быстрее картинок. Так ведь? Один символ шрифта не должен рендериться в 100 раз дольше, чем всё остальное динамическое React-приложение. Похоже, я напоролся на баг Safari.
Обычно это момент «пора выпить». Но до полудня ещё было далеко, так что я просто налил ещё кофе.
Когда находишь что-то похожее на баг браузера, нужно о нем сообщить. Для этого нельзя просто приложить весь проект. «Запустите это целое приложение — Safari ведёт себя странно» не подойдёт. Нужно создать минимальный воспроизводящий пример: простой файл, на котором проблема воспроизводится. Это ещё и помогает полностью разобраться в проблеме — а значит, найти обходной путь получше, чем «никогда не использовать эмодзи в этом приложении».
Раньше сделать минимальный пример было мучительно: нужно вычистить весь проприетарный код и свести всё к минимуму — огромная работа. Но агенты-программисты отлично справляются именно с такими задачами: они могут редактировать много кода сразу, и здесь почти не нужно творческого подхода — просто итеративно удаляй всё, не ломая воспроизведение бага.
Вскоре у меня был очень простой пример для команды Safari. И, глядя на него, стало совершенно ясно, что на самом деле виновато. На моём Mac Safari 26.2 тратит 1600 мс на компоновку следующего HTML:
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji" rel="stylesheet">
<style>
body { font-family: "Noto Color Emoji"; }
</style>
</head>
<body>
????
</body>
</html>Проклятый Noto Color Emoji.
Теперь шрифты умеют быть цветными!
Традиционно шрифты — это просто контуры. Символ отображается чёрным, белым или любым другим выбранным цветом. Но в 2008 году Apple выпустила Apple Color Emoji в iPhone OS 2.2, а в 2011 году принесла её на Mac OS X. Рост популярности эмодзи породил спрос на шрифты с собственной цветностью.
Поначалу цветные эмодзи Apple были, по сути, хаком: PNG-изображения, упакованные в шрифт. Нестандартно и не масштабируемо. За пределами определённого диапазона размеров они выглядели отвратительно. Это привело к тому, что четыре конкурирующих стандарта цветных шрифтов (от Apple, Mozilla, Google и Microsoft) были поданы на рассмотрение в OpenType 1.7. Согласно Википедии, Microsoft и Apple добавили поддержку этих разных подходов — и всё.
Но это — как это часто бывает — было совсем не всё.
Noto Color Emoji — это шрифт Google, дающий стабильный вид эмодзи на разных платформах. Мы подключили его ранее, чтобы нормально отображать эмодзи на Linux (где мы делаем отрисовку HTML в видео в облаке — звучит страшно, но бывает очень полезно). Шрифт основан на стандарте COLRv1 — Google уверяет, что он ускоряет загрузку, так как результирующие эмодзи легче растровых изображений, а для других браузеров предусмотрен откат к SVG.
«Другой браузер» — это Safari. И, судя по всему, «откат к SVG» означает 1600 мс «компоновки» ради одного символа. Чтобы увидеть, как это выглядит в масштабе, можно попробовать открыть страницу Noto Color Emoji на Google Fonts на iPhone — там показаны все глифы шрифта. По состоянию на iOS 26.2 это проходит крайне неудачно.
После того как я упомянул баг в рабочем чате, Daniel Jalkut занёс его в трекер Safari, и Simon Fraser из команды WebKit уже прокомментировал: медлительность, судя по всему, связана с CoreSVG. Скорее всего, это исправят.
А пока я хочу внести свой скромный вклад в поисковые индексы: не используйте Noto Color Emoji на платформах Apple — ставьте на первое место «Apple Color Emoji». По крайней мере, до тех пор, пока баг не исправят и исправленная версия Safari не распространится достаточно широко.
Хочу также признаться в маленьком секрете. При всей неоценимой помощи Claude в отладке — проблему я решил раз в десять быстрее, чем без него, — именно Claude в своё время подсказал нам про Noto Color Emoji. Подозреваю, что без агента-программиста мы бы решили проблему с эмодзи на Linux более скучным способом — например, через библиотеку иконок — и не получили бы странную тормозящую реализацию.
С каждым месяцем это становится всё очевиднее: агенты-программисты очень похожи на циркулярную пилу. Невероятно полезны — и пропорционально опасны.
Что ж, выпьем за Claude. Причину — и одновременно решение — всех проблем стартапов.
- На момент публикации мой основной инструмент — Claude 4.5 Opus в Cursor или Claude Code.
- Спецификация COLRv1 формально предназначена для поддержки разных новых техник шрифтов — градиентов, палитр, вложенных глифов и прочего, — но пока используется почти исключительно для эмодзи.
- Дополнение от 11 февраля: Несколько сотрудников Google неофициально обратились насчёт этого бага. Dominik Röttsches отметил, что ???? — один из самых сложных для рендеринга глифов, однако в моих тестах он баг не вызывает: ❤️ и ???? компонуются за 1600 мс, а ???? и ???? — за 0,2 мс. Это говорит о том, что проблема специфична для конкретных глифов. Tom Roggero упомянул, что шрифт можно исправить патчем для улучшения производительности.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.