5

I have a pretty large npm module written in TypeScript, which exposes a number of types, including several interfaces. Now these interfaces do not all make sense at the root level, which is why I would like to expose them in a way that they can be imported in a somewhat nested way.

Basically, I'm looking for a way to expose interface so that the user of the module can do:

import { Foo } from 'my-module';
import { Bar } from 'my-module/sub-part';

Is this possible in TypeScript, and if so, how? What does not work is this code in the root index.ts file:

export {
  Foo,
  { Bar }
};

Please note that the .ts files are not located at the root directory of the module, and so the .js and .d.ts files aren't either.

How can I solve this?

PS: It's not about default vs named export here, since I want to have a solution for exporting interfaces in multiple nested levels.

Golo Roden
  • 140,679
  • 96
  • 298
  • 425
  • 1
    The usual approach, AFAICT, is to put a `package.json` file in the `sub-part` directory in your package's root and use the `main` and `types` entries in that file (they're gonna be the only ones you need to specify) to refer to the `.d.ts` and `.js` files - which can be anywhere you like. – cartant Oct 28 '19 at 09:07
  • Maybe you could use: `declare module "my-module/sub-part" { export interface Bar { … } }`? – Paleo Oct 28 '19 at 11:02
  • Edited my answer. Let me know, if that helps. – ford04 Oct 28 '19 at 16:46

1 Answers1

1

In the easiest case, you just have separate index.d.ts files inside the my-module and my-module/sub-part folder of the published npm package, so we can import my-module and my-module/sub-part separately. Example project directory:

my-module
|   dist
│   index.ts
│   package.json // types prop e.g. could point to dist/index.d.ts
│
└───sub-part
        index.ts // compiled to dist/sub-part/index.d.ts

TS makes use of its module resolution process to resolve my-module or my-module/sub-part imports (given no global type declaration like @types exists). For example, my-module in import { Foo } from 'my-module' is resolved like this:

// first, try to find file directly
node_modules/my-module.ts
node_modules/my-module.tsx
node_modules/my-module.d.ts

// look in package.json for types property pointing to a file
node_modules/my-module/package.json // <-- lands here, if "types" prop exists

// look in @types
node_modules/@types/my-module.d.ts

// treat my-module as folder and look for an index file
node_modules/my-module/index.ts
node_modules/my-module/index.tsx
node_modules/my-module/index.d.ts // <-- lands here otherwise

For the sake of completness here, my-module and my-module/sub-part could also be defined as separate global module declarations in one containing file, like:

declare module "my-module" {
  const Foo: string
}

declare module "my-module/sub-part" {
  const Bar: number
}

Finally, the client code looks like you already pictured:

import { Foo } from "my-module";
import { Bar } from "my-module/sub-part";

console.log(Foo)

Hope, it helps.

ford04
  • 66,267
  • 20
  • 199
  • 171