Type Declarations and @types
Type Declarations and @types
Type declarations let TypeScript understand JavaScript libraries. When a library has good types, your editor becomes a superpower: autocomplete, documentation, and safe refactors.
Item 48: Prefer Official Types, Then DefinitelyTyped
Some libraries ship their own types. If not, install community types:
pnpm add -D @types/lodashNote
Always prefer official types bundled with a library. Use @types/* when a
package does not ship its own declarations.
Item 49: Know Where Types Belong (dependencies vs devDependencies)
- Apps: types belong in
devDependencies. - Libraries: types may belong in
dependenciesif your users need them at runtime.
{
"name": "my-lib",
"types": "dist/index.d.ts"
}Item 50: Understand Type Resolution (types and typeRoots)
Type resolution is configured in tsconfig.json:
{
"compilerOptions": {
"types": ["node", "jest"],
"typeRoots": ["./types", "./node_modules/@types"]
}
}Misconfiguring these can hide type packages, so use them only when you need to restrict the set of global types.
Item 51: Write Minimal Ambient Declarations First
If a package has no types, start minimal and expand as needed:
// types/legacy-lib.d.ts
declare module "legacy-lib" {
export function parse(input: string): unknown;
}Add more functions as you encounter them in your code.
Item 52: Prefer Module Augmentation
Augment existing libraries instead of adding globals:
// types/express.d.ts
import "express";
declare module "express" {
interface Request {
user?: { id: string; role: string };
}
}This keeps types scoped to the module.
Item 53: Use declare global Only When Needed
If you truly need globals (browser scripts, legacy APIs), use:
// types/global.d.ts
export {};
declare global {
interface Window {
analytics: { track(event: string): void };
}
}Item 54: Understand Default Exports and export =
Some CommonJS libraries use module.exports = ... and need export =:
// types/cjs-lib.d.ts
declare function cjsLib(input: string): number;
export = cjsLib;For ES modules, use export default:
declare const format: (input: string) => string;
export default format;Item 55: Use Overloads to Model API Variants
declare function format(input: string): string;
declare function format(input: number): string;
declare function format(input: string | number): string;Overloads preserve precise types for consumers.
Item 56: Add JSDoc to Improve Editor Hints
/**
* Formats input into a normalized string.
* Throws if input is empty.
*/
export function format(input: string): string;Item 57: Keep Declarations in Sync
Type declarations must match runtime behavior. Use small tests:
// type-tests.ts
import { expectType } from "tsd";
expectType<string>(format("hello"));Warning
Incorrect declarations are worse than no declarations, because they give false confidence.
Common Scenario: Wrapping an Untyped SDK
// types/payment-sdk.d.ts
declare module "payment-sdk" {
export interface Charge {
id: string;
amount: number;
}
export function createCharge(amount: number): Promise<Charge>;
}Key Takeaways
- Prefer official types, then
@types/*. - Keep type packages in
devDependenciesfor apps. - Start with minimal ambient declarations and expand.
- Prefer module augmentation to globals.
- Use overloads and JSDoc for precise, helpful types.
Next: writing and running TypeScript code effectively.