Typescript: isolatedModules refusing to export a type created in the same file

Created on 12 Nov 2018  ·  17Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.2.0-dev.20181110

Code

File 1:

export const myVar = {
   foo: 'foo',
   bar: 5
};

File 2:

import { myVar } from './file1.ts';

type MyType = typeof myVar;

export { MyType }

Expected behavior:
Code can transpile with --isolatedModules flag

Actual behavior:
Transpile is rejected with error:

Cannot re-export a type when the '--isolatedModules' flag is provided.

Related Issues:

21194

In that issue, in a comment it's explained that

We can't compile [...] without knowing whether MyType is a type. If it is, we compile to nothing. If it isn't, we compile to exports.MyType = module1_1.MyType;
The --isolatedModules flag is intended to prevent writing code that can't be transpiled without information about other modules, so it's working as intended in this case.

However, in this other case, MyType can only be a type. This also happens when using things like ReturnType, Pick, etc.

This raises up another question: What happens if in a file we have something like:

import { MyType } from './file2.ts';

type MyTypeAlias = MyType;

export { MyTypeAlias }

In this case, MyTypeAlias can also only be a Type - So by that logic we should be able to transpile this file in isolation (by just not emiting any export).

Working as Intended

Most helpful comment

OK, I found a really weird workaround, which is basically an extension of @brandontle's - Needs some clarification....

This isolated-modules warning/error will only appear when exporting through export { ... }. So, workaround, change the examples from the original issues for:

import { myVar } from './file1.ts';

export type MyType = typeof myVar;

And

import { MyType } from './file2.ts';

export type MyTypeAlias = MyType;

What I think is that on a export { .... } node, all the variables inside are being treated as possible values, which is what is being mentioned as #21194 is not possible to do for types (as types doesn't get exported when transpiling) and so the warning is shown.
But at the same time it's able to detect that it's indeed a type when exporting inline with export type or export interface, and so it just gets ignored when transpiling.

What's really weird though, is that you don't need to rename it or give it an alias:

import { MyType } from './file2.ts';

export type MyType = MyType;

This will work, although it reads as recursive type redefinition, but somehow tsc understands this as just reexporting that type.

All 17 comments

I'm having a similar issue which is preventing the "re-export" of an interface.

Case 1 — Doesn't work:

import IUser from './user.ts';

interface IStateProps {
    user: IUser;
}
interface IDashboardProps extends IStateProps{};

export { IDashboardProps };
//Error: Cannot re-export a type when the '--isolatedModules' flag is provided.

Case 2 — Works:

import IUser from './user.ts';

interface IStateProps {
    user: IUser;
}
export interface IDashboardProps extends IStateProps{};
//Transpiles successfully 

Is this an intentional constraint on how interfaces should be exported? Why would exporting as in Case 1 not be allowed?

Partly related, but maybe it would be useful if TypeScript will support import type as in Flow.
As much as I hated it when I've used Flow, it seems very useful for isolatedModules. Because, sometimes you have no choice to use isolatedModules, And not be able to re-export type is quite limited for me personally

OK, I found a really weird workaround, which is basically an extension of @brandontle's - Needs some clarification....

This isolated-modules warning/error will only appear when exporting through export { ... }. So, workaround, change the examples from the original issues for:

import { myVar } from './file1.ts';

export type MyType = typeof myVar;

And

import { MyType } from './file2.ts';

export type MyTypeAlias = MyType;

What I think is that on a export { .... } node, all the variables inside are being treated as possible values, which is what is being mentioned as #21194 is not possible to do for types (as types doesn't get exported when transpiling) and so the warning is shown.
But at the same time it's able to detect that it's indeed a type when exporting inline with export type or export interface, and so it just gets ignored when transpiling.

What's really weird though, is that you don't need to rename it or give it an alias:

import { MyType } from './file2.ts';

export type MyType = MyType;

This will work, although it reads as recursive type redefinition, but somehow tsc understands this as just reexporting that type.

Seeing something similar. In my case I have a couple things being exported from a file, including an interface. When I try to export select parts of the component file in the index.ts barrel, I run into this issue.

The component file looks something like this:

export interface ISubNavigationItem {
    ...
}

export class SubNavigationComponent extends React.Component<ISubNavigationComponentProps, ISubNavigationComponentState> {
        ...
}

export const SubNavigation = SubNavigationComponent;

In the index.ts file, because I don't want to export SubNavigationComponent I tried...

export { SubNavigation, ISubNavigationItem } from './sub-navigation.component';

...but get...

[ts] Cannot re-export a type when the '--isolatedModules' flag is provided. [1205]

My workaround was to just use:

export * from './sub-navigation.component';

Any progress here, this behaviour is not expected, is it?

I'm running into this exact same issue...

interface path {
  path: string;
}

interface container {
  route: path;
}

export {
  container,
  path
}

This is the intended behavior.

The intent of the --isolatedModules flag is to say "Make sure my program can be correctly transpiled by a transpiler that doesn't do any kind of typechecking". In practice this means Babel, for example.

If you compile the OP example with TS, you'll see that MyType is elided from the output.

If you run the sample example in the Babel repl, you'll see this error because Babel doesn't see any value named MyType.

/repl: Export 'MyType' is not defined (5:9)

  3 | type MyType = typeof myVar;
  4 | 
> 5 | export { MyType }
    |          ^

So TypeScript is correctly identifying that this program can't be transpiled by e.g. Babel, which is the point of the flag.

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@RyanCavanaugh Thanks for the explanation, that makes sense.
Although, couldn't the Typescript compiler, just compile it the right way so Babel won't complain?

So if it detects any kind of interface in an export { }, it will just transpile it into an empty export. Like it would do with the import.

It's really weird and a little bit unorthodox to not to be able to export it the usual way on the bottom of the page along with the other exports, and as you probably can see this issue has gained plenty of attention in many other repositories.
But if it's hard enough to do such thing, it's alright I guess. But still weird and this issue will probably continue to gain traction in the future, because it will still be a small issue.

@Eksapsy The point of this flag is to be used on code that doesnt get transpiled by TypeScript.

TypeScript was first of all invented to add types to JavaScript, Polyfills and that stuff are secondary to that. The TypeScript transpiler was never meant to be doing much more than stripping TypeScript code of the types and adding a few select Polyfills.

If you want more freedom you want to use other transpilers, with Babel being the most popular one. Now, stripping the types from TypeScript code turns out to be easier and giving more freedom than queing both Babel and TypeScript transpilers together, thus in a lot of situations (to give one example, create-react-app with TypeScript code) what ends up happening is that your TypeScript code doesn't get transpiled by TypeScript at all, only type-tested, and the stripping of the types and Polyfills (often orienting by the Polyfills the TypeScript compiler adds) is done by Babel.

I guess Babel is not willing or at all able to figure out when you export a type and when a value, and thus wouldn't be able to handle that code without freaking out.

Anyways, why that doesn't work with Babel isn't really all that important, what's important is that if it can handle it or not is out of the hands of the guys at TypeScript, and thus providing this flag to guard against errors is the best thing they can do.

@9SMTM6 Yeah that makes sense. I didnt really know that in some situations the typescript code wasn't getting transpiled to regular javascript. Good to know.

I guess the flag is pretty important at that situation then and the whole issue is for Javascript to blame as an entity. :smile: We really need typescript to make its own web assembly based language so we can rid off JS :sweat_smile: Just kidding, or am I.

For anyone else who gets here, it looks like this part of @voliva's comment from 11 Jan 2019 is no longer true as of Typescript 3.7:

What's really weird though, is that you don't need to rename it or give it an alias:

import { MyType } from './file2.ts';

export type MyType = MyType;

This will work, although it reads as recursive type redefinition, but somehow tsc understands this as just reexporting that type.

This is explicitly addressed in the blog post announcing version 3.7 (here).

It's possible to do this now:

export type MyType = import("./file2.ts").MyType;

But generics should be duplicated

Thanks @oriSomething - just tested your suggestion and it works.

I can see the reasoning behind the flag, I just don't completely agree with it. --isolatedModules is useful when we use Babel to transpile our code, but still, for my code to check out from the types' perspective (which is why we use typescript), I still need to propagate type definitions through my codebase, so I need to import and export types.
Now because of this error, I cannot use this flag, which just means, I'd lose all the other aspects of safety this would guarantee when transpiling with babel.

I just ran into this and am still not clear about what happens. If I do

type _State = { ... };
...
export type State = _State"

then it works as expected. Why does that work but not exporting State directly?
People looking at this file will wonder what is going on. Or, I could just disable isolatedModules.

I am having an issue in version 3.9.3 with this error, I wanted to have a type declared but only export outside of the file the properties I want provided:

type Mutation<T extends { id: string }> = { docId: string; docPath: string; operation: 'add' | 'update' | 'remove'; data?: T | Partial<T>; merge?: boolean; created: Date; }

type MutationInterface<T extends { id: string }> = Pick<Mutation<T>, 'docId' | 'docPath' | 'data' | 'merge' | 'operation'>;

export {MutationInterface as Mutation};

It was working in my Stencil JS project but when I copied it over to a React version it suddenly was giving me this error.

Was this page helpful?
0 / 5 - 0 ratings