How do I handle optional peer dependencies when publishing a TypeScript package?

Your situation is a case that currently TypeScript does not support well.

Let's first summarize your situation:

  1. foo and bar is your optional dependency, meaning you expect your consumer will use one of them along with your library.
  2. You are only using the type information from those libraries, meaning you don't have any code dependency and don't want to add them as dependencies in your package.json
  3. your customPackage function is public.

Because of point 3, you need to include the type in your library typings, meaning you need to add foo and bar as dependencies. This contradicts with point 1 and 2.

If the typings of foo and bar comes from DefinitelyTyped (i.e. from package @types/foo and @types/bar), then you can add them as your dependencies in package.json. That will solve the problem.

If the typings of foo and bar are distributed with the libraries themselves, you have to either include the libraries as dependencies (which you don't want), or create a replicate of the types ExternalFoo and ExternalBar yourself.

This means you will cut yourself off from depending on foo and bar.

Another way is looking at your library closely and sees if there is any harm in including the foo and bar as dependencies. Depending on the nature of your library, it might be not as bad as you think.

Personally, I typically will go for declaring the types myself. JavaScript is a dynamic langauge to begin with.


Since Typescript 3.8 you can use the following syntax:

import type { ExternalFoo } from "foo";

So if you're just using that library for type information, you probably don't have to list it as a dependency or optionalDependency anymore. You might prefer to leave it as a peerDependency so that if your users will have those dependencies, they'll be on versions compatible with your library. Naturally, adding as devDependency is useful too.

The import will live on the generated d.ts files only, but not on .js transpiled code. One problem, though, is that if users don't have that library installed, that type becomes any and this might mess up your own typing a bit. For example, if foo is not installed your function becomes

customPackage = (source: any | ExternalBar) =>
// equivalent to customPackage = (source: any) =>

And for this particular type annotation, it sucks because even if I have bar installed, it won't be used in that type checking. So there is a way to not depend on that library anymore, but it doesn't solve the hardship of writing type annotations that won't break down if that type is not there.

Edit: please take a look at this answer on how to deal with missing types.

Reference