Dan Prince
Dan Prince

Reputation: 30009

typescript export type and module value

I want to export a type with the same name as a value. Here's an example module:

// foo.ts

export type Foo = string;

export function create(): Foo {
  // ...
}

What's the reason that this pattern works:

// index.ts
import * as _Foo from "./foo";
export const Foo = _Foo;
export type Foo = _Foo.Foo;

But these patterns don't?

// No compiler error but value is not exported
export type Foo = import("./foo").Foo;
export * as Foo from "./foo";

// Duplicate identifier error
import type { Foo } from "./foo";
import * as Foo from "./foo";
export { Foo };

// Individual declarations in merged declaration 'Foo' must be all exported or all local. [2395]
import * as Foo from "./foo";
export { Foo };
export type Foo = Foo.Foo;

Upvotes: 5

Views: 9345

Answers (1)

Shlang
Shlang

Reputation: 3230

Type aliases do not produce a value.

Let me explain what is going on in your examples

// Types (Foo) and values (create) are imported into a single variable
import * as _Foo from "./foo"; 

// The module is reexported with another name
export const Foo = _Foo;

// The type is reexported with another name
export type Foo = _Foo.Foo;

Compiled version does not contain a type of course

var _Foo = require("./foo");
exports.Foo = _Foo;

After that, if Foo is used as a type it is correctly resolved to string, but if Foo is used as a value it is an object with create field.

The fun fact is that you can use typeof Foo now and it will be { create: () => string }

Moving forward, the following is pretty the same as previous but because the first import explicitly states it is a type it is possible to use the same name for the value export

export type Foo = import("./foo").Foo; // does not produce a variable
export * as Foo from "./foo"; // named value export

Compiles into

exports.Foo = require("./foo");

The next one is a bit tricky, to be honest, I don't know why it does not work. I guess it may be a bug since import type is a shiny new feature but I have not found it.

import type { Foo } from "./foo";
import * as Foo from "./foo";
export { Foo };

Locally defined type works:

type Foo = string;
import * as Foo from "./foo";
export { Foo };

And the last one, since Foo is declared in this import cannot be merged with it. You can take a look at this issue for details.

import * as Foo from "./foo";
export { Foo }; 
export type Foo = Foo.Foo; // <- local definition

It can be rewritten using a temporary identifier, just you did it in the first example: (it does not work, see update below)

import * as _Foo from './Foo'
export { _Foo as Foo }
export type Foo = _Foo.Foo

Update

The last listed snippet produces correct js output but throws an error when the export is used as a value:

import * as _Foo from './merged'
const x: Foo = '1234'; // Ok
const y = Foo.create(); // <- TS2693: 'Foo' only refers to a type, but is being used as a value here.

It seems like a bug but I cannot find it posted

Upvotes: 3

Related Questions