October 3, 2023·4 min read·engineeringtypescriptmodulesjavascriptcompiler

TypeScript 5.3 beta passes import expectations to the host

Import attributes replace assertion syntax with a mechanism that can guide module interpretation. TypeScript preserves the host-specific attributes while adding resolution control for type imports.

A filename ending in json does not by itself establish how a module loader will interpret the response. An importing program may need to state that it expects data rather than executable JavaScript. TypeScript 5.3 beta supports the updated import-attributes syntax for expressing that expectation to the host.

The October 3 beta announcement presents the change from assert to with. Its implementation record tracks the compiler work. The important difference is broader than the keyword. Attributes can guide resolution and interpretation, while the earlier assertion model checked characteristics after loading.

I find the module boundary one of the harder parts of using TypeScript across web tooling. The same source import can pass through a checker, bundler and runtime. Making an expectation explicit helps only if those tools agree about which one is responsible for enforcing it.

The compiler preserves a host-specific request

The beta supports static import attributes using a with clause. A host that implements the corresponding JSON import behavior can use the type attribute when processing the module.

import settings from "./settings.json" with { type: "json" };

TypeScript does not validate the meaning of every attribute value. The announcement explains that the contents are host-specific and remain available for browsers or runtimes to handle, including rejecting them. A successful check therefore does not establish that the deployment host recognizes the requested behavior.

This separation permits different hosts to define their own attribute vocabulary. It also limits what an application can infer from compiler acceptance. A syntactically valid import can still ask the host for an unsupported module interpretation.

Dynamic imports receive attributes through the second argument to import. That object contains a with property, and TypeScript describes its expected shape through ImportCallOptions. The request can therefore accompany imports whose loading occurs during execution as well as static declarations.

The practical contract remains between importing code and the host. TypeScript represents and preserves the request. The host decides how the attribute affects module resolution or loading. A bundler that consumes the source becomes part of that interpretation path.

Resolution guidance changes the type-import discussion

TypeScript previously experimented with a resolution-mode attribute on type-only imports in nightly versions. The beta announcement explains why it remained experimental. Import assertions were not intended to guide resolution, so using one to choose import or require semantics conflicted with that model.

The updated attribute proposal permits resolution guidance. TypeScript 5.3 consequently supports resolution-mode on import type as a regular feature. A declaration can explicitly request the import-side or require-side view of a package.

This matters for packages exposing different entry points under those conditions. A type-only import does not execute a runtime import, but it still has to choose which declarations describe the intended module relationship. Leaving that choice implicit can select the wrong side of a dual-format package.

The beta also supports the attribute on import types used inside type expressions. This gives declaration authors the same control when they describe a type through a module lookup instead of a top-level import statement. The requested resolution behavior belongs with the lookup it qualifies.

I prefer that local expression of intent to relying entirely on the surrounding file format to explain every type dependency. A declaration can need to compare or combine interfaces from different package conditions. Explicit resolution makes the chosen relationship available to the next reader.

Narrowing follows more existing JavaScript forms

The beta separately improves control-flow analysis for switch statements whose discriminant is true. Conditions in case clauses can narrow a variable in the corresponding branch. Fallthrough still matters, because reaching a later branch can happen through more than one condition.

Boolean comparisons also preserve narrowing information. An explicit comparison of a type-guard result with true can now narrow the checked value. The checker recognizes the relationship even when the source uses that longer conditional form.

Custom Symbol.hasInstance methods gain corresponding treatment when declared with a type predicate. An instanceof test can narrow to the predicate type, which need not be the class's own instance type. The announcement illustrates a structural point-like result that permits coordinate access without implying the existence of every class method.

That precision is useful because JavaScript permits the operator's runtime behavior to be customized. Assuming every successful test proves a conventional prototype relationship would overstate what such a method actually checks. The predicate supplies the narrower fact the implementation promises.

Prototype access gains a correctness check

TypeScript 5.3 beta also rejects super property access referring to an instance field. A field containing an arrow function lives on the instance, while super looks through the prototype arrangement. Treating those as interchangeable can produce an undefined access at runtime.

The new diagnostic catches that mismatch at the declaration relationship. It is another example of the beta improving its description of existing JavaScript behavior. Import attributes add a way to communicate with a module host, while the narrowing and super checks make the compiler more precise about the operations already present in application code.