Skip to content

TypeScript to Dart

Jonathan Rezende edited this page Apr 11, 2023 · 3 revisions

This is a work in progress

The ts_ast app just reads a .d.ts file and, without too much filtering or business logic at all, dump the analyzed info into a JSON file.
The ts2dart reads this JSON file and generates an object from a class for each TS declared type in the file, even if the type can not be directly transpiled to Dart.
This strategy consider that the Dart interop may evolve and let us better transpile TS features into Dart code in the future.

Now here is how we handle TS types:

Union

The builder do some logic here to try to find a specific type that we can use, if not, we use dynamic or Object by default.

If all types are constants we ditch the union and use an enum instead:

export type Foo = 'one' | 'two';

Becomes an enum:

enum Foo {
  one, 
  two
}

We filter the undefined or null to make a nullable type:

export type Foo = Bar | undefined | null;

Transpiles to:

typedef Foo = Bar?;

Everything else becomes dynamic because we couldn't dissect an specific type definition, ie:

export type Foo = Bar | Daz;

Intersection

The builder will generate a new type if the intersection is used in typedefs, if not, then dynamic is used.

export type Foo = Bar & Daz;

Transpiles to something like:

class Inline123 implements Bar, Daz {}

typedef Foo = Inline123;

Now when used in arguments:

declare function foo(bar: A & B);

Transpiles to something like:

void foo(dynamic bar);

Method overloading

You can overload a method in TS but not in Dart. The transpiller generates one method for each version so we can still be sound, ie:

interface Document {
  createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
  createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;
}

From the above we generate a Record called createElement with two function properties: $1 and $2.
For the first version ($1) we identify that the return type is bound to an enum, thus we can then generate a signature that the return type can be infered:

// div is infered to the type HTMLDivElement
final div = js.document.createElement.$1(js.HTMLElementTagNameMap.div); 

The second version in Dart has no difference from the TS one.

Predicates (type guards)

A type guard in TypeScript returns a bool and promotes the argument to that type.

function isFoo(arg): arg is Foo;

When transpiled to Dart, the function returns the nullable type instead:

Foo? isFoo(arg);

void main(arg) {
  final foo = isFoo(arg); // foo is of type Foo?

  if (foo != null) {
    // now foo is of type Foo
  }
}

Directly to Dart

There are complex types that can be directly transpiled to Dart:

  • Classes/interfaces
  • Typedefs
  • Enums
  • Tuples
  • Functions

There are primitive types, like bool, that are the same in Dart, you can check the current mappings here.

Clone this wiki locally