Skip to content

export type vs export { type } with augmentations #202

@skirtles-code

Description

@skirtles-code

Background

I recently helped migrate Pinia to tsdown, vuejs/pinia#3092. One problem we encountered was with type augmentations.

Previously Pinia was using rollup, rollup-plugin-typescript2 and @microsoft/api-extractor to bundle the types. This yielded types of this form:

export declare type StoreActions = /* type details not important */;

After switching to tsdown this same type is bundled slightly differently:

type StoreActions = /* type details not important */;

export { type StoreActions };

These might appear equivalent, but they aren't. They behave differently if the type is used with a type augmentation:

// Type augmentation in app code
declare module 'pinia' {
  // StoreActions is available here
  // without needing to import it
}

With the original inline export, StoreActions could be accessed inside the augmentation without importing it. With the deferred export used by tsdown this doesn't work, it needs to be explicitly imported.

Is this a TypeScript bug?

I thought this might be a bug in TypeScript, so I opened this issue:

I don't understand the explanation, but apparently this is by design.

How do other tools handle it?

I did some investigation into other build tools and I wrote it up here:

I won't repeat everything I wrote there, but in short, tsdown seems to be consistent with tsup, but differs from tools like @microsoft/api-extractor and dts-bundle-generator.

I don't think any of those approaches would be considered 'right' or 'wrong', they're just different. But it is potentially a barrier to migrating to rolldown/tsdown if the types aren't backwards compatible with those generated by other build tools.

Vue core includes a 'fix' for this

Vue core recently migrated from tsup to tsdown (on the minor branch), but historically it used other build tools and so it uses a custom plugin to rewrite the types and keep them backwards compatible. You can see that here (this is for rollup, but it's essentially the same for rolldown):

This rewrites the type exports to make them all inline, rather than batching them at the end.

What next?

Maybe there could be a config option or an official plugin similar to the one used by Vue core?

I haven't been able to find any discussion/explanation of this topic, not just for rolldown but more generally. Even if nothing needs to be done I thought it would be useful to open this issue so we have somewhere to discuss and document the rationale behind any decisions that are made.

CC @posva @edison1105

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions