5

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;

1 Answer 1

3

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

Sign up to request clarification or add additional context in comments.

2 Comments

The JavaScript output is as expected, but I can't get the re-export workaround you mentioned working: github.com/microsoft/TypeScript/issues/…
regarding the import type problem, I just raised github.com/microsoft/TypeScript/issues/50880

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.