TypeScript 5.0 makes the module host an explicit choice
Bundler resolution and verbatim module syntax separate type-checking assumptions from JavaScript emission. The release also formalizes decorator and literal-inference contracts for libraries.
An import can be valid for a browser bundler and invalid for Node's native ECMAScript module loader. TypeScript has to model the environment that will consume the file, even when another tool performs the eventual transformation. TypeScript 5.0 gives bundler-based applications their own resolution mode and makes the distinction between runtime imports and type-only imports easier to control.
The March 16 release includes standard-proposal decorators, const type parameters and several module-system changes. The bundler resolution implementation explains the intended host. It targets tools that combine Node-like package lookup with more permissive import rules than Node's native ESM loader.
I am more interested in TypeScript's web ecosystem than in accumulating language features. Correctly representing the tool that builds a web application helps the compiler describe the program that actually runs. A configuration that models the wrong loader can either reject useful code or accept imports that fail after publication.
Bundlers combine rules that older modes separated
Native Node ESM requires file extensions in relative imports. Many bundlers allow extensionless paths while also understanding package.json export conditions. That combines behavior associated with older CommonJS-style resolution and modern ESM package definitions.
The older node resolution mode did not fully model those modern package definitions. The newer node16 and nodenext modes modeled Node more precisely but imposed restrictions that a bundler did not necessarily need. Choosing either could therefore describe only part of the application's actual resolution behavior.
The new bundler option models this hybrid arrangement. In the final 5.0 release it requires module set to esnext, preserving import statements for the tool that will resolve them. The release announcement identifies this restriction as a change since the beta.
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"noEmit": true
}
}
This configuration asks TypeScript to check source under bundler lookup rules while leaving JavaScript production to another tool. It does not install or configure that tool. The bundler still has to resolve the same imports and produce output appropriate to the runtime.
The release recommends that most npm library authors retain node16 or nodenext. A library consumer may run its modules without a bundler. Checking the library only under more permissive rules can conceal an import that the consumer's loader rejects.
Application code and published library code consequently have different compatibility boundaries. An application can know that its own build processes every module before execution. A library cannot assume every consumer shares that arrangement unless its published format establishes the requirement.
Package conditions belong to the resolution model
TypeScript 5.0 consults package.json exports and imports by default under the modern resolution modes. These fields can map public package entry points and internal specifiers to files under conditions. Resolving a package name now involves more than looking for a filename in a directory.
The customConditions setting adds conditions to the resolver's defaults. This lets a project describe a host that selects a particular export branch. If the compiler and runtime select different branches, a successful type check may describe a different implementation from the one that executes.
That makes the setting a compatibility declaration. Adding a condition because it exposes convenient types is insufficient if the runtime never uses that branch. The useful configuration is the one that matches how the actual tool interprets the package definition.
The release also adds allowImportingTsExtensions. In 5.0 it is permitted with noEmit or emitDeclarationOnly, because a .ts import retained in ordinary JavaScript output would require a host capable of understanding it. Another resolver or runtime is expected to make those source imports work.
This restriction connects source-level convenience to output behavior. TypeScript can understand the target file without promising that every JavaScript loader can load the original extension. A checker and a module host perform related but different jobs.
Non-JavaScript assets have a similar arrangement through allowArbitraryExtensions. The release describes a CSS import with declarations in a file named app.d.css.ts. That declaration describes the value supplied by a configured loader, while the compiler option acknowledges the unusual extension. TypeScript does not become a CSS loader by accepting the import. Its declaration models the result that another tool must supply, preserving the distinction between understanding an imported value and implementing the operation that produces it.
Verbatim syntax makes import erasure visible
Historically, TypeScript can erase an import used only in type positions. That avoids requesting a runtime export that may not exist. It can also remove evaluation of the imported module entirely, including any side effects associated with loading it.
Determining emission could depend on more than local usage. The release announcement describes decisions that also consult how an imported or exported name is declared elsewhere. Other compilers processing files independently may lack the same information. Several earlier flags attempted to control these differences.
The new verbatimModuleSyntax option establishes a simpler local rule. Type modifiers identify what can be erased, while ordinary imports and exports remain. An entire import type statement disappears. In a mixed import, type-marked names disappear while value names remain.
There is a subtle distinction between a type-only statement and an ordinary import containing only type-marked bindings. The announcement shows that the latter can become an empty import statement. Its module still participates in evaluation. This makes statement structure relevant when a module has side effects.
Under the same option, ECMAScript import and export syntax does not silently transform to CommonJS when the file's configuration implies that format. The compiler reports an error. Code requiring CommonJS output can use TypeScript's import = require and export = syntax.
This helps expose configuration mistakes such as an unintended package.json module type. A file cannot quietly retain ESM-looking source while the compiler chooses a different module arrangement. The output contract becomes easier to inspect through the source and its configuration.
Decorator spelling still needs a compatible implementation
The release supports the newer decorator proposal without experimentalDecorators. Method decorators receive a target function and context, and can return a replacement. The context also permits initialization callbacks, allowing behavior such as binding methods during instance construction.
Legacy decorators remain available through the existing flag. The release warns that checking and emission differ enough that old decorator functions are unlikely to support the new behavior automatically. Parameter decorators and emitDecoratorMetadata remain outside the new proposal's supported contract.
For framework users, this is another host-like dependency. A decorator belongs to a runtime library contract as well as a syntax feature. The application's compiler configuration has to match the implementation expected by that framework.
I prefer making that dependency explicit to assuming a major-version upgrade has unified every decorator ecosystem. A framework's use of emitted metadata can be architecturally significant. Keeping its requirements visible helps explain why two projects using the same TypeScript version may legitimately select different decorator modes.
Literal inference moves into the API declaration
Const type parameters let a generic declaration request const-like inference for expressions written directly in a call. An object literal can retain more precise property values, and an array literal can retain tuple information without requiring the caller to add as const each time.
The constraint still matters. A readonly tuple candidate cannot satisfy a mutable array constraint, so inference may fall back to the broader array type. The release demonstrates using a readonly array constraint to retain the intended precision.
Existing variables do not recover information already lost during their own inference. Passing a variable typed as string[] to a const generic does not reveal its original element literals. The feature controls inference at the call expression rather than changing every value into an immutable one.
For library authors, this moves a recurring convention into the declaration that requires it. The function can ask for literal precision where its return type depends on that information. Callers still need to understand the resulting types, but they no longer carry the entire burden of remembering a special assertion at each use.
Shared configuration has an ordered merge
The release also permits several entries in extends. Later entries win when inherited settings conflict. A project can combine a shared strictness configuration with its own organizational base without forcing one base to inherit the other for every consumer.
That ordering deserves the same attention as resolution mode. A later base can replace a setting that appeared in an earlier one, so reading either file alone does not establish the effective configuration. TypeScript 5.0 provides more direct ways to express a project's assumptions, while making the final combination of those assumptions the thing tools and maintainers need to inspect.