I think your only hope is if the optional dependency declares an ambient type (or modifies a built-in one), that you don’t have to import. Even if you can do it, you can’t import anything from it (at least at compile time), which means you’ll need to recreate any of its types you want to use (or work “blind” with any
).
But if those limitations still work for you, you could try to use declaration merging on some ambient type.
For instance, if you know that the optional dependency has an ambient type like
node_modules/@types/optionalDependency/index.d.ts
:
interface OptionalDependencyGlobal {
someKnownProperty: unknown;
/*…*/
}
Then you could write your own (empty) ambient type with the same name:
libs/types/checkOptionalDependency.d.ts
:
interface OptionalDependencyGlobal {}
Then you could use conditional types based on
'someKnownProperty' extends keyof OptionalDependencyGlobal
? /* dependency present */
: /* dependency absent */
Because you declared your own OptionalDependencyGlobal
, you won’t get an error referencing it when the dependency is missing, and because you left it empty, keyof OptionalDependencyGlobal
will be never
, and 'someKnownProperty' extends never
is false. Thanks to declaration merging, if the dependency does exist, then keyof OptionalDependencyGlobal
will be 'someKnownProperty' | /* other properties */
, and 'someKnownProperty'
does extend that.
Note that you can pull the same kind of trick if the optional dependency uses its own declaration merging to modify a built-in type (or a type from a shared dependency). Consider:
node_modules/typescripts/lib/lib.dom.d.ts
(built-in):
interface Window extends /*…*/ {
// …
}
node_modules/@types/optionalDependency/index.d.ts
:
interface Window {
someKnownProperty: true;
}
Now you can use 'someKnownProperty' extends keyof Window
just as we used it with OptionalDependencyGlobal
above.
Either way, though, this is a brittle, hacky workaround, and if your optional dependency doesn’t do something like this, I don’t think you can detect it. Even when it works, it doesn’t let you import anything at compile time. I’d only consider this if I controlled both libraries, I think.
But I’m pretty confident there’s no direct mechanism for “optional import” that allows you to directly try an import and do something else if it’s missing; if the file specified in import
is missing, you just get a compilation error. You could use require
at runtime for optional imports (inside try
/catch
), but that won’t help at compile time and won’t help with typing.