TypeScript: Declaration emit should not inline type definitions

TypeScript Version: 3.8.3, 3.8.1, probably others

Search Terms:

declaration inlining, dts inlining, declaration inline, inline literal, declaration literal

Code

// parent.ts
import { num, obj } from "./child"
export const reExportNum = num;
export const reExportObj = obj;
// child.d.ts
export declare const num: number;
export declare const obj: { a: 1 };

tsc index.ts --declaration

Expected behavior:

Declaration emit for parent.ts should not inline types.

// parent.d.ts
import { num, obj } from "./child"
export declare const reExportNum: typeof num;
export declare const reExportObj: typeof obj;

Actual behavior:

Today, declaration emit for parent.ts inlines the types and eliminates the import of the child.d.ts type definition.

// parent.d.ts
export declare const reExportNum: number;
export declare const reExportObj: {
    a: 1;
};

This is a correctness issue, because consumers of parent.d.ts will not get the correct types if the types in child.d.ts change.

In practice, this is most likely to happen when parent and child are in separate packages, because they are published independently, i.e. an application uses parent-package which uses types from child-package. This is exacerbated by the current practice on npm of parent-package depending on an unpinned version, using package.json dependency syntax "child-package": "*".

This issue was co-authored with @robpalme

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 7
  • Comments: 18 (14 by maintainers)

Most upvoted comments

I’m here because inlining everything caused my .d.ts file to be over 6.5MB long. The type checks started failing and I am wondering if the compiler simply ignores the end of the file because of some limitation.

@aleksey-ilin the main solution I have found to solve huge declarations is to identify the root type that is inlined and then, assuming it is a statically known object type, create an interface from it and then refer to that interface at all usage sites.

export interface WrappedProblemType extends ProblemType {}

I have been experimenting with changing declaration emit so that shenanigans like this are not necessary. It kinda works and I’ll share that soon.


Separately, union and intersection types also get inlined. interface will not save you in this case - there is no reliable userland workaround for these. Thankfully there is work in progress to reduce the inlining of these specific types in https://github.com/microsoft/TypeScript/pull/42149

There’s a fairly fundamental tension here about what declaration emit means: Should declaration files represent the types as they existed when you compiled your program, or should they represent the types that a consuming library would have seen had your original program been compiled “in the context of” the consumer’s setup?

This gets really mind-bending if you think about conditional types or overloads, e.g.

declare var x: SomeType;
export const c = func(x);

Is the intent here that c should have the type that you saw when you invoked func? Or if a consuming library augments SomeType in a way that changes the resulting type of func(x), should c have some other new type? What if that causes some other use of c to break a contract?

I think one of these behaviors is much easier to reason about than the other, as you can probably tell from my descriptions of them.

Anyway the example in the OP is also in tension with people who want their end-result .d.ts file to be a single-ish artifact that doesn’t expose their entire program’s internal structure. This is a good goal anyway for performance - it’d be much better if we load 1 file per library instead of 12, and better if we handle some inline anonymous types instead of resolving typeof queries everywhere.

@cyberixae One mitigation to reduce inlining is to use interface rather than type when defining object shapes. This causes tsc to reference the original type by name, which may further cause it to generate type-only import() expressions.

If you’re thinking of https://github.com/microsoft/TypeScript/pull/37444, that’s still out for review.