Search code examples
typescript

How can I re-export a package's full set of types?


Package foo has many useful TypeScript interfaces that I want exposed transitively through package bar to consumers (represented as package baz), but I cannot find an elegant way to re-export that full set of types (namespace?) for downstream use. foo is a frequently changing dependency which I do not strictly maintain. It's important that its interfaces automatically reflect through bar, as bar is the sole provider of foo to baz.

Consider the following package dependency chain:

foo -> bar -> baz

Now consider some basic implementations:

// pkg foo
export interface Fooz { x: number }
// pkg bar
import * as foo from 'foo';

// Objective: get all interfaces/types re-exported through this package
export type FooMod = import('foo');
// ^ This would be ideal, but not supported
// Module 'foo' does not refer to a type, but is used as a type here. Did you mean 'typeof import('foo')'?ts(1340)
// pkg baz
import * as bar from 'bar';

// This is my end customer's package. How can they get to interfaces from `foo`, hosted by `bar`? 
// e.g. observe the return type:
const doStuffWithBar = (): bar.FooMod.Fooz => {
  // 
}

Things I've tried so far:

  1. export FooMod = typeof foo;. typeof foo creates a new type, and is lossy. It may bring over types from all entities physically present in the module, but types/interfaces from the foo module are all dropped. This is not adequate.
  2. export FooMod = import('foo'). This produces a TS error, mentioned inline in comments.

Without a solution, I have to force my customers to actually install the foo module to access its top-level types. I could also inline exports in bar for all types in foo, but that's not sustainable, and given the size and change rate of foo, the worst outcome.

Is such a solution feasible?


Solution

  • Use export * (as X)? from 'package' in Bar. If you want to import the types directly in Baz, skip the as <X>, otherwise you can import the namespace in Baz like the example below:

    Foo

    // index.ts
    export interface Test {
      a: string
    }
    

    Namespace import in Baz

    Bar

    // index.ts
    export type * as Foo from 'foo';
    

    Baz

    import { Foo } from 'bar';
    
    let a: Foo.Test;
    

    Direct type imports in Baz

    Bar

    // index.ts
    export type * from 'foo';
    

    Baz

    import { Test } from 'bar';
    

    Edit: As noted by OP's comment, even with export types the non-type exports in Foo will also show up when importing in Baz. But they can neither be used in Baz, as typescript will give the error: <variable> cannot be used as a value because it was exported using 'export type'. and it doesn't even look like they're really exported from Bar, only in name in the declrataion file.