You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
exporttype{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;// oktwo;// okthree;// okfour;// Error: 'four' cannot be used as a value because it was exported using 'export type'.typeT1=TypeOne;// ok// ...
It sounds like Joe Pea (@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.
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.)
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.
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.
Andrew Branch (@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).
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*astypesfrom"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.)
Andrew Branch (@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.
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.
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.
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 "...".
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 Nicolò Ribaudo (@nicolo-ribaudo).
Activity
whzx5byb commentedon Apr 1, 2022
Related: #36966
RyanCavanaugh commentedon Apr 4, 2022
Andrew Branch (@andrewbranch) any concerns?
andrewbranch commentedon Apr 4, 2022
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:
While not particularly useful, this is allowed. When imported from here,
someValueis 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 thatexport * 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:It sounds like Joe Pea (@trusktr) would have expected the import of
fourto fail, whereas the reality is that the usage offourwould fail, unless used in atypeofquery. Not the end of the world, but a little confusing.RyanCavanaugh commentedon Apr 4, 2022
Joe Pea (@trusktr) given the above, would that still fit your use case?
bbrk24 commentedon Apr 14, 2022
I ran into this just now, in a slightly different way:
foo.d.ts:bar.ts:index.ts:This crashes at runtime for a slightly different reason -- there is no
foo.jsto re-export, so theexport *fails entirely. (The import inbar.tsis correctly removed.)andrewbranch commentedon Apr 14, 2022
While it’s true that this feature request would get around that, it’s not a bug—a module
foo.d.tsimplies the existence of a modulefoo.js. If you’re hand-authoring a file that’s just types, you should generally name it.ts.bbrk24 commentedon Apr 14, 2022
Andrew Branch (@andrewbranch) Is that documented anywhere? I haven't heard of this before, and I've seen standalone
.d.tsfiles before (if nowhere else, at least intype-fest).andrewbranch commentedon Apr 14, 2022
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
.tsfile, you get a.jsand a.d.tsfile out. The.d.tsrepresents 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 withexport *, as you can observe the same thing with imports: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 withoutimport 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.tsfor their type-only modules in their own projects is because.d.tsfiles 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 commentedon Apr 15, 2022
Andrew Branch (@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 commentedon May 29, 2022
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 commentedon Jun 20, 2022
I would also welcome an
export type *since the compiler no longer automatically removesexport *statements from a.d.tsfile (since version 3.9 I believe), which made barrel files really difficult, including type re-exports.In my opinion,
.d.tsfiles have a right to exist even without a corresponding.jsfile. for example for the definition of a protocol.We are defining the events, socket messages, rest data, .... in own
.d.tsfiles 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.tswas wrong, but anexport type *might work around the problem (at least for my team). And since there is already anexport type {}this solution makes sense to me.nicolo-ribaudo commentedon Jun 26, 2022
Duplicate of #37238, I'm cross-posting my use-case:
andrewbranch commentedon Jun 27, 2022
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 Nicolò Ribaudo (@nicolo-ribaudo).