When publishing a TypeScript package to npm that provides a function that accepts input from either one peer dependency or another, how do I define the optional peer dependencies?
import { ExternalFoo } from 'foo';
import { ExternalBar } from 'bar';
export const customPackage = (source: ExternalFoo | ExternalBar) => {
/* ... */
}
How do I prevent people using my package from getting errors when one of the two options is missing?
Unmet Dependencies When Testing When you use peer dependencies, npm will not automatically install those dependencies (see comments above in respect to npm version 7).
A dependency is a library that a project needs to function effectively. DevDependencies are the packages a developer needs during development. A peer dependency specifies that our package is compatible with a particular version of an npm package.
Automatically installs project's peerDependencies (as devDependencies).
Fortunately prepare is called after dependencies installation and only when you are developing so it won't install the peer dependencies when a user install your module. Then add the prepare script in your module's package.json and call install-peers-cli in it:
Peer dependencies are a specific kind of dependencies really useful for reusable modules: Ask user to install a dependency your module needs to work without specifying a version in particular The problem with peer dependencies is npm and yarn don't install them at all.
Our package exposes declarations from each of those, so any user of our browserify-typescript-extension package needs to have these dependencies as well. For that reason, we used "dependencies" and not "devDependencies", because otherwise our consumers would have needed to manually install those packages.
Peer Dependencies are used to specify that our package is compatible with a specific version of an npm package. Good examples are Angular and React. To add a Peer Dependency you actually need to manually modify your package.json file. For example, for Angular component library projects, I recommend adding angular/core as a peer dependency.
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
Combining all the current answers, here's the best solution I've found for current versions of TypeScript (as of late 2021):
// @ts-ignore -- optional interface, will gracefully degrade to `any` if `foo` isn't installed
import type { Foo } from "foo";
import type { Bar } from "bar";
// Equates to `Bar` when `foo` isn't installed, `Foo | Bar` when it is
type Argument = any extends Foo ? Bar : (Foo | Bar);
export function customPackage(source: Argument): void {
...
}
You can try it out yourself. If foo
is installed, the exported method will take Foo
or Bar
arguments, and if it isn't, it will only accept Bar
(not any
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With