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 * as namespace" also exports value #36966

Closed
whzx5byb opened this issue Feb 23, 2020 · 7 comments · Fixed by #37064
Closed

"export type * as namespace" also exports value #36966

whzx5byb opened this issue Feb 23, 2020 · 7 comments · Fixed by #37064
Assignees
Labels
Bug A bug in TypeScript

Comments

@whzx5byb
Copy link

TypeScript Version: 3.9.0-dev.20200222

Search Terms: export type * as namespace

Code

// provider.ts
export const foo = 42;
export interface Bar {};
// index1.ts
import type * as A from './provider';
export { A };
// index2.ts
export type * as A from './provider';
// 1.ts
import { A } from './index1';

const foo = A.foo;
type Bar = A.Bar;
// 2.ts
import { A } from './index2';

const foo = A.foo; // **expected an error, but succeed.**
type Bar = A.Bar;

Expected behavior:
index1.ts and index2.ts should have the same behaviour: export a namespace which only contain types, and the assignment to foo should result in an error.

Actual behavior:
In index2.ts the assignment succeed.

Playground Link:

Related Issues:

@postspectacular
Copy link

As per thi-ng/umbrella#209 it seems export type * from ... forms are also forbidden now, even though they worked fine in TS 3.8.2. Can we please have some more official clarity about which variations are allowed now?

@andrewbranch
Copy link
Member

they worked fine in TS 3.8.2

They were (accidentally) allowed, but they didn’t work fine—they were interpreted as a normal, non-type-only export, as the OP’s example shows.

The 3.8 blog post covers the feature in detail, but the TL;DR is:

Allowed imports:

  • import type Foo from 'mod'
  • import type { Foo } from 'mod'
  • import type * as foo from 'mod'

Not allowed imports:

  • A combination of the allowed three above: import type Foo, { Bar } from 'mod'
  • CommonJS imports: import type Foo = require('mod')

Allowed exports:

  • export type { Foo }
  • export type { Foo } from 'mod'

Not allowed exports:

  • export type * from 'mod'
  • export type * as foo from 'mod'
  • CommonJS exports: export type = Foo? Not even sure how you’d write the equivalent of an export = Foo

I will further say that I don’t personally have strong feelings against enabling export type *; rather, it simply didn’t contribute anything valuable to the problem set we were considering with this feature. I think it’s something we would consider adding if there’s high demand with compelling real-world use cases. I’m interested in hearing why a normal export * is problematic for you.

@postspectacular
Copy link

Thanks for the update @andrewbranch - I actually did read that blog post a while ago and could have sworn there was a mention of export type * from too, which I now can't find anymore... oh well... FWIW normal exports are going to be fine for my purposes, just wanted to better understand the valid options/reasoning before undoing the changes in my repo back to their pre-3.8.2 versions...

@postspectacular
Copy link

postspectacular commented Apr 27, 2020

@andrewbranch I'm sorry, but I will have to ask for some more guidance from you about this export type behavior, especially with isolatedModules enabled:

As an example, let's take this project in question: @thi.ng/api. This linked index.ts file re-exports dozens of type definitions located in subfolders/files.

  1. ❌ With isolated modules enabled, in its current form (using export * from ...) this will emit the full list of exports in JS, including (incorrectly, IMHO) dozens of empty JS files, which will be referenced from the transpiled index.js and are then breaking downstream builds (see note further below)
  2. ✅ With isolated modules, but using named exports (export type { Fn, Fn2 ... } from "./api/fn") those export statements are correctly elided from the JS version, but that solution is infeasible & brittle since it requires maintaining a separate list of re-exports for each of the ~50 submodules
  3. ✅ With isolated modules disabled (and still using export * from ...), the generated JS correctly only contains exports of the submodules which also contain actual JS values

The build issue mentioned above has to with that current form (1), my published project actually contains invalid (empty) ES modules (i.e. those which only contained type decls and IMHO should have been elided as with options 2 & 3) and building a userland project with rollup now fails because of those empty modules...

From that all, it seems my only viable option is to disable isolatedModules for building this upstream library project, but then again I'd first like to learn more about the interaction of export type and isolatedModules... (it's pretty confusing) thanks!

@postspectacular
Copy link

Addendum to the above comment: After some deeper enquiry, that build issue I've been encountering might actually have to do with snowpack (which uses rollup internally) and I've also submitted an issue with more details there: https://www.pika.dev/npm/snowpack/discuss/131

Still, I'd highly appreciate to get some more clarity on the correctness/expected behavior of using the different export types WRT isolatedModules in the earlier comment.

@andrewbranch
Copy link
Member

When you use --isolatedModules, you’re saying “ensure that my program can be compiled file-by-file, without any file knowing about another.” So under normal circumstances, when the compiler is running down your index.ts file with all the export *, it resolves information about the target module, and elides the export if there are no values exported from it. But in --isolatedModules, it doesn’t do that, because it’s simulating the circumstances under which a bundler might transpile your app on a file-by-file basis. So, it takes your export * at face value and emits it. This is long-standing behavior and not related to export type at all. And as you determined after tracing the build issue down to snowpack/rollup, re-exporting an empty module shouldn’t actually cause problems (tested with node 14 just now).

Admittedly, I didn’t know setting isolatedModules would actually affect the emit rather than just tell you about problems you’re going to hit when transpiling until looking into the behavior you’re describing. It does seem reasonable to want to check your program against --isolatedModules but also get the smartest emit we can offer. For now, I’d probably suggest that as part of your build, you run a transpiler-safety check via tsc --isolatedModules --noEmit, then actually emit the js with a second run where --isolatedModules is off. (I’m using command line flags here for brevity; of course you can make separate tsconfig.check.json and tsconfig.build.json files if you like.)

@postspectacular
Copy link

Thank you for the detailed explanation @andrewbranch and am sorry for the slight confusion. I was aware of most of this, but this weird build issue somewhat threw me / made me doubt... meanwhile we fixed the snowpack issue already and I will employ the --isolatedModules --noEmit combo for my project in the future. That's a great tip!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants