Types and declarations

Declaration files

In kdts, .d.ts files retain their usual TypeScript role as declaration files, but they also carry an additional optimization meaning: types declared in .d.ts files are treated as external types, so their property names are preserved rather than minified.

This extension lets kdts aggressively minify property names on internal types while keeping externally visible data shapes stable. In practice, that means smaller JavaScript and lower download and parse costs.

As an example, consider

user.d.ts
interface UserDto {
  firstName: string,
  age: number,
}

export { UserDto };
user.ts
import { UserDto } from "./user.d";

interface User {
  firstName: string
  age: number
}

const serialize = (user: User): string => {
  const userDto: UserDto = {
    firstName: user.firstName,
    age: user.age,
  };
  return JSON.stringify(userDto);
}

const user: User = {
  firstName: "Abc",
  age: 20,
};

console.log(user);
console.log(serialize(user));

When compiled with kdts user.ts, we get

Note that the properties of the User interface got minified as firstName->h and age->g however the properties of UserDto interface are preserved since it is defined in a d.ts file.

In kdts each type that needs to have its properties preserved should be defined in a d.ts file.

Nominal and structural types

In kdts two object types having the same shape are assignable to each other, just like in regular TypeScript.

By contrast, classes and interfaces are nominal in kdts, so assignability requires an explicit subtype-supertype relationship.

Two nominal types that would be assignable to each other in regular TypeScript can still be assigned, though an explicit cast is required in kdts:

Making classes and interfaces nominal gives kdts more room to perform type-driven optimizations.

Object literals

In kdts, interfaces are nominal rather than structural, so a value with the same shape is not automatically considered to be of an interface type. There is one important convenience rule, though: when an object literal is used as the initializer of a binding with an explicit type annotation, kdts treats that object literal as having the declared type of the binding. For example, in

the expression { id: "id" } starts out as a plain object literal, but because it initializes a binding declared as User, it is automatically treated as a User. This makes nominal interfaces ergonomic to construct without requiring an explicit cast at each declaration.

The same rule applies to return values in a function with a declared return type:

Last updated