Safari 27 получит переписанный загрузчик модулей и рабочий top-level await
WebKit заменил загрузчик модулей, написанный по заброшенному в 2016 году черновику WHATWG, реализацией по спецификации ECMAScript; ошибка «accessed before initialization» уходит в Safari 27.

Команда WebKit 2 сентября рассказала, что переписала загрузчик JavaScript-модулей Safari с нуля и тем самым закрыла многолетние ошибки top-level await, из-за которых модули с await на верхнем уровне падали с сообщением «Cannot access ... before initialization». Исправление уже есть в Safari Technology Preview 251 и бете Safari 27; в стабильный Safari оно попадёт с выходом Safari 27.
Для фронтендера это означает, что один из последних поводов не использовать top-level await в продакшене исчезает: в Chrome и Firefox функция работает давно, а Safari оставался браузером, ради которого библиотеки и сборщики держали обходные пути. По словам автора публикации, инженера WebKit Кая Тамкуна, вместе с загрузчиком в порядок приведены ES-модули в целом, так что после релиза Safari 27 на них «можно опираться, не задумываясь».
Ключевые выводы
- Старый загрузчик модулей Safari был написан по черновику WHATWG Loader, последний раз обновлённому в январе 2016 года, ещё до появления async/await в языке.
- Top-level await, добавленный в ECMAScript 2022, реализовали поверх этой устаревшей основы, отсюда ошибки порядка выполнения и обращения к неинициализированным экспортам.
- В январе 2026 года команда удалила старый загрузчик целиком и переписала его на C++ по алгоритмам спецификации ECMAScript, отказавшись от самохостируемого JavaScript.
- Проверка: тест-кейсы от команды Bun, чей рантайм на JavaScriptCore унаследовал те же баги, фаззер графов модулей со сравнением вывода с другими движками, все модульные тесты test262 и исправленные тесты WPT без регрессий.
- Попробовать можно сегодня в Safari Technology Preview 251 или Safari 27 beta; сроки стабильного Safari 27 в публикации не названы.
Что именно ломалось
Top-level await позволяет писать await прямо в теле ES-модуля: модуль приостанавливается до разрешения промиса, и вместе с ним ждут все модули, которые его импортируют, а независимые ветки графа зависимостей продолжают выполняться. В Safari нарушался именно порядок: загрузчик неверно выбирал, когда считать модуль выполненным. WebKit показывает это на минимальном примере, где один и тот же модуль с задержкой на верхнем уровне динамически импортируется три раза подряд.
Ожидаемый порядок завершения импортов 1, 2, 3. Старый загрузчик выдавал 2, 3, 1, причём второй и третий импорт завершались с ошибкой Cannot access 'someArray' before initialization, и только первый печатал список экспортов. Причина одна: когда первый импорт доходил до await и уступал управление, промис второго импорта должен был ждать окончания выполнения модуля, но из-за ошибки разрешался сразу. Код получал ещё не инициализированные экспорты и падал. С новым загрузчиком вывод становится ожидаемым: 1, 2, 3, и каждый раз с корректным списком ключей.
Почему баг не удавалось починить годами
Спецификация ECMAScript оставляет часть механики модулей на усмотрение хоста, в первую очередь загрузку по сети в браузере или с диска в Node.js и Bun. Загрузчик Safari писали в эпоху предложения WHATWG Loader, которое описывало эту хостовую часть и последний раз обновлялось в январе 2016 года. Тогда модули выполнялись строго синхронно, и черновика хватало. Затем предложение фактически умерло, вытесненное разделом о модулях в самом стандарте ECMAScript, а в 2022 году в язык вошёл top-level await. В WebKit его реализовали поверх алгоритмов заброшенного черновика, а не по асинхронным алгоритмам стандарта. Отсюда тонкие ошибки, которые, по словам Тамкуна, команда безуспешно пыталась закрыть несколько раз, пока не решила заменить основание.
Второе решение касается языка реализации. Старый загрузчик был самохостируемым встроенным кодом на JavaScript. У такого подхода есть плюсы: его можно инлайнить в пользовательский код и не платить за переход между JavaScript и C++. Но он медленнее стартует, потому что компилируется во время выполнения, а оптимизирующие JIT-компиляторы JavaScriptCore плохо используют его широкие паттерны применения; загрузчик к тому же не горячий путь, так что выигрыша от компиляции на лету почти нет. Новый загрузчик написан целиком на C++.

Как переписывали и проверяли
Работа началась в январе 2026 года с удаления файла со старым загрузчиком. Дальше команда переводила псевдокод операций из спецификации ECMAScript в C++ по одной, начав с листовых функций вроде ExecuteModule и ModuleRequestsEqual, у которых нет зависимостей, и двигаясь по графу вызовов. Через несколько недель черновая реализация справлялась с типичными случаями, и появился draft pull request.
Тестов было три слоя. Во-первых, инженеры Bun, чей рантайм построен на JavaScriptCore и унаследовал те же проблемы, передали собранные ими случаи неправильного поведения; их адаптировали под консольную оболочку jsc. Во-вторых, команда написала фаззер, который генерирует большие графы модулей, часть с top-level await, часть без, и сравнивает текстовый вывод JavaScriptCore с выводом других движков байт в байт; по словам WebKit, во всех проверенных примерах новый загрузчик отработал верно. В-третьих, все модульные тесты набора test262 стали проходить, а часть ранее падавших тестов WPT исправилась без регрессий. Перед слиянием разработчики несколько недель пользовались сборкой Safari с новым загрузчиком как основным браузером.
Что делать фронтендеру
- Проверить свои модули с top-level await в Safari Technology Preview 251 или Safari 27 beta; о найденных проблемах WebKit просит сообщать на bugs.webkit.org.
- Обходные пути для Safari пока не убирать: стабильный Safari 27 ещё не вышел, а старые версии Safari останутся у пользователей надолго.
- Если проект работает на Bun, следить за обновлениями рантайма: он использует JavaScriptCore и, по словам WebKit, унаследовал те же ошибки загрузчика; о сроках их исправления в Bun публикация не говорит.
Дата выхода стабильного Safari 27 в публикации не названа. Открытым остаётся и вопрос старых версий: о переносе исправления в уже вышедшие Safari WebKit не сообщает, так что доля пользователей на Safari 26 и старше будет определять, когда обходные пути можно убрать совсем.
Источник: WebKit Blog: Fixing Top-Level Await in Safari
Изображение на обложке: Изображение: логотип WebKit, Apple










