TypeScript 5.9 beta separates module loading from evaluation
The 5.9 beta supports import defer for delaying synchronous module initialization. Modules still load in advance, and TypeScript preserves the syntax for a supporting runtime or bundler.
A module can cost startup time even after its source has arrived. Top-level initialization may allocate data or register behavior that the application does not need immediately. Loading fewer bytes and evaluating less code are related optimizations, but they require different mechanisms.
The July 8 TypeScript 5.9 beta supports import defer, the syntax from the ECMAScript deferred module evaluation proposal. It lets a program express a static dependency whose synchronous evaluation waits until use of the imported namespace.
This is compiler support for proposed JavaScript behavior. TypeScript does not transform the syntax into an equivalent sequence for older runtimes. The beta permits it under module: "preserve" or "esnext", leaving execution to a runtime or bundler that implements the feature.
Initialization is separate from retrieval
The TC39 proposal describes the motivation in terms of unnecessary synchronous initialization. Dynamic import() can defer loading, but it returns a promise and changes how callers obtain the module's values.
That asynchronous boundary can propagate through an API even when the real goal is only to postpone CPU work. A function that was otherwise synchronous may need to become asynchronous because it waits for dynamic import.
Deferred evaluation preserves a different arrangement. The module graph is loaded so the required code is available, while selected evaluation waits. A later namespace property access can trigger the synchronous initialization and then return the binding.
The distinction matters for application architecture. Deferring network retrieval can introduce a later request at the moment a feature is first used. Deferring evaluation can keep that retrieval out of the interaction while still avoiding unnecessary startup computation.
I care about how comfortable a web interface feels, and initialization is part of that architecture. A page can have all its code available while doing too much work before the user can interact. Explicit evaluation timing gives that problem a language-level representation.
Only namespace imports provide the trigger
The syntax uses a namespace import. The later property access identifies the point at which the deferred module's value is required.
import defer * as formatter from "./formatter.js"
export function renderReport(value: number): string {
return formatter.format(value)
}
This example requires a supporting runtime or transformation. It is not an assertion that every environment capable of running TypeScript 5.9 output already understands the import form.
The namespace object provides an interception point before a value is returned. Named and default imports are therefore excluded from this syntax. They would not provide the same explicit namespace-property access through which evaluation begins.
That makes the dependency visible at the top of the file while moving its initialization into the use path. A reader can still identify the module relationship without searching for a dynamic import inside a function.
The initialization may have observable side effects. Delaying it changes when those effects occur, so the source declaration expresses a semantic choice. A module that must register something during startup cannot be deferred casually just because its exported values are used later.
Asynchronous dependencies need different treatment
The proposal explains a limitation arising from synchronous property access. A namespace access cannot wait for an arbitrary top-level asynchronous computation without changing its own calling convention.
Consequently, dependencies that use top-level await, together with their transitive dependencies, are evaluated eagerly. The synchronous portions of the graph remain the part that can be deferred. This is more precise than claiming that no dependency code runs until the first property access.
That distinction matters when examining a real module graph. A deferred root can still have dependencies whose initialization belongs to the earlier phase. The program's evaluation schedule follows both the deferred edge and the asynchronous requirements within the graph.
Cycles and shared dependencies also make module evaluation more than a textual move of statements. The proposal defines behavior through the module system, rather than asking each application to implement its own cache and initialization guard.
The beta also changes generated configuration
The same beta replaces the large commented tsc --init template with a smaller, more prescriptive configuration. It includes explicit module detection and an empty types array, along with stricter indexed access and optional-property settings.
These are defaults for a newly generated configuration. Installing the beta does not rewrite every existing project's settings. That distinction prevents a template change from being mistaken for a universal compiler-default change.
A generated configuration still needs to match the application. The template uses nodenext, while import defer requires preserve or esnext in this beta. Related release features do not necessarily combine under one unchanged configuration.
My view of TypeScript is shaped by the quality of applications its ecosystem can support when those conventions are explicit. The useful part of this beta is the clearer division of responsibilities. The compiler recognizes a deferred dependency, the module implementation controls evaluation, and project configuration states which execution environment should receive the preserved syntax.
The release remains a beta. Its concrete capability is support for expressing deferred synchronous module evaluation, with early loading and explicit runtime requirements. That provides a way to move initialization out of startup without automatically converting the consuming API into an asynchronous one.