Skip to content
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

export type * from 'somewhere' #48508

Closed
5 tasks done
trusktr opened this issue Apr 1, 2022 · 13 comments
Closed
5 tasks done

export type * from 'somewhere' #48508

trusktr opened this issue Apr 1, 2022 · 13 comments
Labels
Duplicate An existing issue was already created

Comments

@trusktr
Copy link
Contributor

trusktr commented Apr 1, 2022

Suggestion

πŸ” Search Terms

typescript export type star

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Just like export * from 'foo', we should be able to export type * from 'foo' to re-export all types (and only types).

πŸ“ƒ Motivating Example

This is useful in cases where we re-export certain things for runtime, but still wish to re-export all types:

export {one, two, three} from 'foo' // re-export certain runtime things.
export type * from 'foo' // re-export all types.

πŸ’» Use Cases

☝️

@whzx5byb
Copy link

whzx5byb commented Apr 1, 2022

Related: #36966

@RyanCavanaugh
Copy link
Member

@andrewbranch any concerns?

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Apr 4, 2022
@andrewbranch
Copy link
Member

Hm, yeah, kind of. If this syntax were allowed, it wouldn’t do what the suggestion says (β€œre-export all types (and only types)”) because that’s not how type-only imports/exports work. Consider named exports as an analogy:

export type { someValue } from "foo"

While not particularly useful, this is allowed. When imported from here, someValue is only allowed to be used in a non-emitting position because it’s type-only, but it’s also only allowed to be used in a value position because it’s only a value. I.e., the only valid usage is in a TypeQuery: let x: typeof someValue.

The way to think about type-only imports and exports is that they have identical resolution semantics to their non-type-only counterparts, but impose restrictions on any references to them. So export type * from "foo" would export everything that export * from "foo" exports, values included, but those values would only be referenceable in non-emitting positions. It would be overridden, in a sense, by other non-type-only exports, like in the OP’s Motivating Example. If I’m importing from the file in that example, it might look like this:

import { one, two, three, four, TypeOne, TypeTwo, TypeThree } from "./foo-reexporter"

one;   // ok
two;   // ok
three; // ok
four;  // Error: 'four' cannot be used as a value because it was exported using 'export type'.

type T1 = TypeOne; // ok
// ...

It sounds like @trusktr would have expected the import of four to fail, whereas the reality is that the usage of four would fail, unless used in a typeof query. Not the end of the world, but a little confusing.

@RyanCavanaugh
Copy link
Member

@trusktr given the above, would that still fit your use case?

@bbrk24
Copy link

bbrk24 commented Apr 14, 2022

I ran into this just now, in a slightly different way:

foo.d.ts:

export type T1 = // ...

bar.ts:

import { T1 } from './foo';

export function f(arg: T1): boolean {
    // ...
}

index.ts:

export * from './foo';
export * from './bar';

This crashes at runtime for a slightly different reason -- there is no foo.js to re-export, so the export * fails entirely. (The import in bar.ts is correctly removed.)

@andrewbranch
Copy link
Member

While it’s true that this feature request would get around that, it’s not a bugβ€”a module foo.d.ts implies the existence of a module foo.js. If you’re hand-authoring a file that’s just types, you should generally name it .ts.

@bbrk24
Copy link

bbrk24 commented Apr 14, 2022

a module foo.d.ts implies the existence of a module foo.js. If you’re hand-authoring a file that’s just types, you should generally name it .ts.

@andrewbranch Is that documented anywhere? I haven't heard of this before, and I've seen standalone .d.ts files before (if nowhere else, at least in type-fest).

@andrewbranch
Copy link
Member

I don’t know if that’s explicitly stated anywhere, it’s just kind of a natural consequence of what declaration files are. When you compile a .ts file, you get a .js and a .d.ts file out. The .d.ts represents everything TypeScript needs to know about what’s in the JS file, while the JS file is (obviously) what the runtime needs to execute. They’re two sides of the same coin. This really has nothing to do with export *, as you can observe the same thing with imports:

import * as types from "type-fest"
console.log(types)

There is no error here, and yet this will crash at runtime, because all the module .d.ts files in type-fest imply that there are modules, exporting nothing, all over that package, where there are none. (Also, any usage of type-fest with --importsNotUsedAsValues=preserve, imported without import type, will cause a runtime crash that we can’t predict.) While we have type-only imports and exports, we don’t currently have a concept of a type-only external module. Having that is a coherent suggestion, but not related to this issue. (The main reason I always suggest people use .ts for their type-only modules in their own projects is because .d.ts files will not get copied to outDir, so it just saves some pain. Type libraries on npm don’t have great optionsβ€”they have to choose between defining everything as globals, publishing just .d.ts files that imply the existence of missing .js files, or publishing otherwise useless empty JS files alongside those .d.ts files.)

@frank-dspeed
Copy link

frank-dspeed commented Apr 15, 2022

@andrewbranch type only external module would address a lot of my use cases where i abuse rollup to bundle generated typedefinitions for the correct bundels. i could avoid that and create a typescript file .ts that generates only the needed declarations directly matching the output

as i always generate the output from the list of entrypoints i get bundels for each entrypoint so every entrypoint needs exact the same type only .d.ts files

the entrypoints are most of the time and that needs to be the only supported case files that contain only import statements and export statements

when typescript would have a sense of a type only module it could generate the correct .d.ts files for that bundels via a .ts or .d.ts file that does type only reExports

that also would save a lot of tsconfig hustle.

by the way i invented type only files via checkJs allowJs containing jsdoc annotated import('./myType').TypeName

and building that it is my hacky workaround for that .js files can easy get excluded in typescript compile steps as also they can get easy compiled with emitDeclaration only which is in fact a external typeOnly module. (declaration)

the typescript project can this way stay untouched and behave like normal while i have outside of it a referencing project that only builds the .js entrypoints typedeclarations using the types from the typescript project. sure i could also use .ts files but that always produces wirred quirks and needs more config. Also the .js method worked before there was a type only import.

and makes clear that the file is only about types. as it contains no real code that would get bundled or compiled by typescript.

@trusktr
Copy link
Contributor Author

trusktr commented May 29, 2022

@trusktr given the above, would that still fit your use case?

In my case yeah. I didn't think that far into it: I had a bunch of interfaces, without any values in the module, and I simply wanted to re-export them all.

@Ulrikop
Copy link

Ulrikop commented Jun 20, 2022

I would also welcome an export type * since the compiler no longer automatically removes export * statements from a .d.ts file (since version 3.9 I believe), which made barrel files really difficult, including type re-exports.

In my opinion, .d.ts files have a right to exist even without a corresponding .js file. for example for the definition of a protocol.
We are defining the events, socket messages, rest data, .... in own .d.ts files which can be shared between different services.

the data are received and stored in an object. the corresponding class file could be different in the different service. most of the times the class differs a little bit, because it provides only required values and casts date stings to date objects.

that is only one example. I believe there are more.

I think TS should be flexible enough to deal with different teams approaches.
I also think that the decision back then not to let the compiler automatically remove an export * from a .d.ts was wrong, but an export type * might work around the problem (at least for my team). And since there is already an export type {} this solution makes sense to me.

@nicolo-ribaudo
Copy link

Duplicate of #37238, I'm cross-posting my use-case:

We have a file that only contains type annotations (https://github.com/babel/babel/blob/main/packages/babel-types/src/ast-types/generated/index.ts), and we currently re-export it internally using export * from "..." (https://github.com/babel/babel/blob/d4d08d906a8c5c53cdf5fb0024def91fbfa51698/packages/babel-types/src/index.ts#L101).

When publishing, we strip away TS types and compile to CJS. The types-only files becomes an empty CJS module, and our entry point ends up having some unnecessary code for export * from "...".

Apparently, that empty CJS file causes problems with some tools (babel/babel#14634, https://stackoverflow.com/questions/71045195/cannot-build-run-devserver-cause-of-babel-loader, https://openclassrooms.com/forum/sujet/babel-loader-not-working, kriszyp/lmdb-js#51). It's a bug with those tools, but it would be nice if we could entirely avoid it by marking that re-export as type-only since it's a file that is not needed at runtime.

@andrewbranch andrewbranch added Duplicate An existing issue was already created and removed Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Jun 27, 2022
@andrewbranch
Copy link
Member

I’m going to go ahead and close this as a duplicate of #37238 and redirect any further conversation there as that one has a lot more eyes on it. Thanks @nicolo-ribaudo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

8 participants