-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add syntax hint to allow reexports of types in isolatedModules mode #34750
Comments
You can still re-export like so:
in flowtype you have a special keyword for importing types: |
Thanks for writing that up @BPScott! I have a few questions/comments:
Flow places the // @flow
import type Foo, {MyObject, MyInterface} from './exports'; In other words, they explicitly don’t do this:
I think I would lean toward mimicking Flow on this, because I think it would be misleading to write import type ThisOneIsTypeOnly, { ButThisOneHasValue } from './mod'; especially for users who are familiar with Flow. The restriction of splitting up type-only and regular imports/exports into separate declarations seems reasonable (maybe even desirable for grokability) to me.
// a.ts
class C {}
export { C as RegularClassExport };
export type { C as TypeOnlyClassExport }; // Is this allowed?
// b.ts
import type { RegularClassExport } from './a'; // Is this allowed?
import { TypeOnlyClassExport } from './a';
// Ok to use as type
declare let a: TypeOnlyClassExport;
declare let b: RegularClassExport;
// Can’t use as value
const c = new TypeOnlyClassExport();
// ^^^^^^^^^^^^^^^^^^^ 'TypeOnlyClassExport' only refers to a type,
// but is being used as a value here. ts(2693)
const d = new RegularClassExport();
// ^^^^^^^^^^^^^^^^^^ 'RegularClassExport' only refers to a type,
// but is being used as a value here. ts(2693) On one hand, I think this is kind of intuitive, but on the other hand, it’s not needed to solve the re-export issue in isolatedModules, since a class can safely be re-exported (since the export will resolve to something at runtime in the JavaScript emit).
I think the summarized top-priority questions that need to be answered are
|
Heya @andrewbranch, thanks for extra info on Flow usage. As for your questions:
Excellent point. I figured it'd make eliding the imports easier from the compiler's perspective, in the case where a type was both used and reexported but it seems babel is already smart enough to remove imports of types (see this babel repl). In that case I don't think there's any value in keeping the import changes. As you mentioned in point 3 they might be useful for type-only imports but that sounds like a separate feature and out-of scope for solving reexports in isolatedModules
Digging a little deeper it seems that both of the syntaxes we talk about are supported by Flow. The "on the declaration level" you talked about was there first, and then the "on the specifier" I agree that mimicing Flow is best though the fact that it does both styles muddies the water somewhat. Them adding the "on the specifier level" as sugar suggests that they've found the "on the declaration" style a little restrictive. Personally I have no strong opinion either way - I'd be happy to do the "on the declaration" style, though it would mean some repetition when you reexport values and types from the same file (this crops up when we reexport react components and their props quite a bit)
Also of interest regarding which syntax is chosen is this babel issue that talks about how these two syntaxes are handled differently, and that only the "on the declaration" level style gets stripped when compiling to JS: babel/babel#6300. But I don't think this is that relevant if we drop the
I was thinking this would be only applicable to type-only symbols. The meaning filtering stuff is nice but as you say it isn't needed right now. I think we could say
I don't think any of those are particulary compelling if we punt on the meaning filtering stuff, leave them as type errors?
Nothing that springs to mind right now, though I've not thought about this much since the original issue. It's a bit more verbose than the "per specifier" style but both enable the same things, and as both are implemented by Flow the "lets follow flow" approach doesn't really push us towards one style over the other.
|
Did some digging and found that there is another scenario that a lot of people have been asking about for a long time, which is basically the exact opposite of the motivation for this issue: people saying “please don’t remove my imports even if I only use them as a type, because those modules have side effects.” This seems to be most common in Angular, where one file exports a service class and registers it with Angular as a side effect, then other files import that class but only use it as a type, since Angular will inject an instance of that service where needed. The first real proposal (#2812) to address this proposed type-only imports and exports (with the same syntax proposed here) that could always be elided from emit, whereas any other imports would never be elided. Related: Make sure this import/export is elided
Make sure this import is not elided
The latter group is a compelling scenario purely by the number of people feeling confused or frustrated over such a long period of time. There are workarounds, but they’re generally regarded as verbose and non-obvious, and sometimes they break lint rules. The former group has collectively very low engagement. There are fewer 👍s on all those combined than on this issue—Webpack 5 is definitely the strongest argument for type-only anything. Thoughts so far: Type-only imports and exports (similar to the original proposal in #2812), probably combined with a compiler flag, could be used to solve all these issues at the same time. Maybe that makes sense, since the two requests are essentially complements. But on the other hand, it seems like the numerous people in the “stop eliding my imports” camp probably don’t care about having always-elided type-only imports. So I’m not yet fully convinced that type-only imports is appropriate as an umbrella fix for both scenarios, but it’s worth exploring. I’ll try to bring this to our next design meeting. |
Excellent spelunking! Looking at babel/babel#6300 (comment) it sounds like babel handles the two flow syntaxes in the same way as #2812 proposes so there's a bit of prior-art there:
|
I personally like the Another option is to disallow types imports without code example: import type { SomeType } from "./x";
import { IFoo, func } from "./b";
export {
// this is ok
SomeType,
// this is not
IFoo
} |
Search Terms
isolatedModules, type export, type re-export, babel
Context
In
--isolatedModules
mode you get an error when reexporting types, the reason why this breaks is documented in the new handbook. In the following example lines 2 and 5 both raise give "Cannot re-export a type when the '--isolatedModules' flag is provided." warnings.This leaves programme authors who wish to compile using a per-file compiler with some choices:
Use
export * from "someModule"
, which will deal with all exports, but won't work if you need to cherry pick exports. It's usually possibly to rearchitect your folder structure to work around this but not always.Or use a temporary renaming:
Prior to #31231 (to be released in TS 3.7) authors could export a type that matches the same name as an import, but that loophole has been closed:
Authors should not have to go through such renaming dances in order to get type reexports working in isolatedModules mode, instead it should be possible to hint that an import / reexport is of a type.
Suggestion
Add some kind of syntax hint to imports and reexports that denote that a particular identifier is a type and thus can be elided when compiling in isolatedModules mode.
Here I'm proposing the strawman of a
type
keyword before the ImportSpecifier / ExportSpecifier, as suggested by @Jessidhia in #31231 (comment). This is the same syntax Flow uses though I must admit I'm not familiar with Flow.Placing the hint on a per-specifier basis will allow mixing of type and value import/reexports in a single import/export statement:
Regarding unhappy paths:
type
hint to a value should raise a type error "Value export must not be tagged as a type"type
hint"type
hint. We could disable adding the hints by throwing a TypeError "Type exports withtype
hint are only required in isolatedModules mode" but that feels pretty heavy handed. I think a better path is allowing the type hint from the compiler's perspective, while having a linting rule to that can can say if these type hints should be present or not (defaulting to present for isolatedModules is enabled and absent if disabled). This will help projects who wish to migrate from usingtsc
to a per-file compiler.Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: