Skip to content

Build1 publisher3 min readPublished

WebKit rebuilt Safari's module loader instead of patching top-level await again

Safari's loader took its host hooks from a proposal last updated in January 2016, which is why the top-level await bugs survived years of patching, and why the rewrite only helps traffic already on Safari 27.

The Engineer · Build desk

What happened

  • WebKit says Safari 27 reaches full spec compliance for top-level await, and that it got there by rewriting Safari's module loader from the ground up rather than patching the existing one.
  • Safari's module loader was written during the era of the WHATWG Loader proposal, last updated January 2016, and took its host-defined behaviour, including module fetching, from that proposal.
  • WebKit's reproduction imports one awaiting module three times: the old loader reported two imports as complete, and reading an export from each then threw "Cannot access 'someArray' before initialization".

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Deleting the code that works around Safari's old ordering is a decision about the oldest Safari in your traffic, and WebKit's post supplies no Safari 27 release date to anchor that call.
  • constraint Top-level await moves latency into the import graph, so putting one in a shared leaf module commits every consumer of that module to waiting on it, whether or not they use the awaited value.
  • exposure The compliance claim ships with one reproduction and no conformance results, which leaves verification with whoever kept the reduced test cases that justified their workaround.

Read the transcript WebKit published and the failure is legible in the ordering. "Imported 2" prints, and the next line is a failure to read the module's keys: "Cannot access 'someArray' before initialization" [7]. The promise returned by `import()` settled before the module body had finished running past its top-level await [8]. The consumer then did the ordinary thing with a namespace object whose bindings were not initialized. All three loads target the same file, whose only await is a 10 millisecond timer [13], so the variable is not latency; it is the order the loader chose [7]. One more detail from the same output: it never prints "Imported 1" [9].

The reason years of patches did not hold is a layering problem. The ECMAScript modules section deliberately leaves parts to the host, including how modules get fetched, over the network in a browser and off the filesystem in runtimes like Node.js and Bun [3]. WebKit implemented those host hooks against the WHATWG Loader proposal, whose last update was January 2016 [4]. Top-level await arrived in ECMAScript 2022 [5]. WebKit's account of the consequence is blunt: the feature was implemented in terms of a proposal abandoned before async/await existed in the language, rather than against the standard's algorithms for asynchronous module execution [5], and the resulting bugs resisted multiple attempts over years [6]. When the component that sequences evaluation answers to a superseded document, every patch is a local correction to a global ordering decision. Rebuilding that layer is the fix that can hold, and WebKit says that is the call it made [6].

The cost of adopting top-level await sits in the semantics, not the syntax. When a module hits a top-level await, every module that imports it suspends until the await resolves, while siblings in the graph that do not depend on it keep executing [10]. An await in a widely imported leaf module therefore puts its latency in front of everything downstream. The convenience is yours to write; the waiting belongs to the importers.

Whether the guard code can go is decided by your traffic, not by the rewrite. WebKit invites developers to try it in Safari Technology Preview 251 and Safari 27 beta [2], and says that once Safari 27 ships, top-level await and ES modules can be leaned on in production [11]. The post carries no ship date, and it says nothing at all about other browser engines [12]. The supported reading is narrower than the framing that follows a fix like this: Safari 27 and later stop producing this class of error, and workarounds written for earlier Safari versions keep earning their place until pre-27 Safari leaves your logs.

On the compliance sentence [1]: the evidence published alongside it is one reproduction, with no conformance test results [14]. That is a claim about the bug class WebKit was chasing, and it is checkable by anyone who kept the reduced cases that motivated their workaround. Running those against STP 251 costs an afternoon and tells you more than the word "full" does.

What to watch

  • A Safari 27 release date, which sets the earliest any compatibility guard could be dropped.
  • Whether WebKit publishes module conformance results for the rebuilt loader, or closes the long-standing bug reports it now considers fixed.
  • Regression reports from Safari Technology Preview 251, since a rebuilt loader changes ES module evaluation order for sites that never used top-level await.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories