Post

10. Working with JavaScript Libraries

πŸ”— Master TypeScript integration with JavaScript libraries! Learn type definitions, declaration files, React/Node.js typing, and gradual migration strategies. ✨

10. Working with JavaScript Libraries

What we will learn in this post?

  • πŸ‘‰ Using JavaScript Libraries in TypeScript
  • πŸ‘‰ Creating Declaration Files
  • πŸ‘‰ Module Augmentation and Declaration Merging
  • πŸ‘‰ TypeScript with React
  • πŸ‘‰ TypeScript with Node.js
  • πŸ‘‰ Working with Third-Party Type Definitions
  • πŸ‘‰ Gradual Migration from JavaScript

Using JavaScript Libraries in TypeScript Projects πŸŽ‰

Integrating JavaScript libraries with TypeScript is essential for leveraging the vast npm ecosystem while maintaining type safety. Understanding type definitions and declaration files ensures your projects remain robust and maintainable in production environments. TypeScript is a great way to add type safety to your JavaScript projects. Here’s how to use JavaScript libraries smoothly!

Installing Type Definitions πŸ“¦

When you use a JavaScript library, you might need type definitions to help TypeScript understand it. You can install these from the @types packages. For example:

1
npm install --save-dev @types/lodash

This command installs type definitions for the popular library Lodash.

Handling Libraries Without Types ⚠️

Sometimes, a library might not have types. In this case, you can use the any type as a fallback:

1
declare const myLibrary: any;

Creating Minimal Type Definitions ✍️

If you want more control, create your own type definitions. Here’s a simple example:

1
2
3
declare module 'my-library' {
  export function myFunction(param: string): number;
}

This way, you can define just what you need!

@types vs Custom .d.ts: When to Use Each

Aspect@types PackagesCustom .d.ts Files
Setup Time⚑ Instant - just npm installπŸ› οΈ Manual - requires writing definitions
Maintenanceβœ… Community maintainedπŸ”§ You maintain
CoverageπŸ“¦ Full library coverage🎯 Only what you need
Quality⭐ Professional, tested🎨 Depends on your effort
Best ForPopular libraries (lodash, axios, express)Legacy/proprietary/internal libraries
Version SyncπŸ”„ May lag behind library updatesπŸš€ Update anytime
Type AccuracyπŸ“š ComprehensiveπŸŽͺ Can be minimal/focused
graph TD
  A["πŸ€” Need Types for Library?"]:::style1 --> B{"πŸ“¦ @types Available?"}:::style2
  B -- "Yes" --> C["⚑ Install @types Package"]:::style3
  B -- "No" --> D{"πŸ” Library Complexity?"}:::style2
  D -- "Simple" --> E["✍️ Write Minimal .d.ts"]:::style4
  D -- "Complex" --> F["πŸ› οΈ Create Comprehensive .d.ts"]:::style5
  C --> G["βœ… Start Coding!"]:::style3
  E --> G
  F --> G

  classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style4 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

  linkStyle default stroke:#e67e22,stroke-width:3px;

Using JavaScript libraries in TypeScript can be easy and fun! Happy coding! πŸš€

Creating Custom .d.ts Declaration Files πŸŽ‰

Creating custom declaration files empowers you to add type safety to any JavaScript library, even those without official type definitions. This skill is crucial for teams working with legacy code or proprietary libraries.

When using untyped JavaScript libraries in TypeScript, you can create custom declaration files to help TypeScript understand them. Let’s explore how to do this!

Key Concepts

1. Declare Module

Use declare module to define types for a specific library. For example, if you have a library called myLib:

1
2
3
declare module 'myLib' {
  export function myFunction(param: string): number;
}

2. Declare Global

Use declare global to add types to the global scope. This is useful for plugins or global variables:

1
2
3
4
5
declare global {
  interface Window {
    myGlobalVar: string;
  }
}

3. Ambient Declarations

Ambient declarations are used to describe types that exist in the environment but are not defined in your code. For example, for jQuery plugins:

1
2
3
4
5
declare module 'jquery' {
  interface JQuery {
    myPlugin(options?: any): JQuery;
  }
}

Common Patterns

  • jQuery Plugins: Extend jQuery with custom methods.
  • Node.js Modules: Define types for your Node.js modules.

Example for Node.js Module

1
2
3
declare module 'my-node-module' {
  export function myNodeFunction(): Promise<string>;
}

Creating declaration files helps you enjoy the benefits of TypeScript while using JavaScript libraries! Happy coding! πŸš€

Understanding Module Augmentation and Declaration Merging 🌟

Module augmentation and declaration merging are powerful features in TypeScript that let you extend existing type definitions. This is super useful when you want to add new properties or methods to existing interfaces or namespaces without modifying the original code.

What is Module Augmentation? πŸ€”

Module augmentation allows you to add new types or properties to existing modules. For example, if you want to extend the Express Request object to include a custom property, you can do it like this:

1
2
3
4
5
6
7
8
9
import * as express from 'express';

declare global {
  namespace Express {
    interface Request {
      user?: { id: string; name: string }; // Adding a user property
    }
  }
}

Use Case: Extending Express Request πŸš€

This is particularly handy when you want to attach user information to the request object in an Express app. Now, you can access req.user in your route handlers!

What is Declaration Merging? πŸ› οΈ

Declaration merging allows you to add properties to existing interfaces. For instance, if you have a custom interface for a user, you can merge it with another interface:

1
2
3
4
5
6
7
8
interface User {
  id: string;
  name: string;
}

interface User {
  email: string; // Merging with a new property
}

Use Case: Adding Custom Properties ✨

This is useful when you want to enhance existing types without creating new ones. You can keep your code clean and organized!

Conclusion πŸŽ‰

Module augmentation and declaration merging are great tools for customizing existing types in TypeScript. They help you keep your code flexible and maintainable.

graph LR
  A["πŸ”§ Module Augmentation"]:::style1 --> B["πŸ“¦ Extend Types"]:::style2
  A --> C["βž• Add Properties"]:::style3
  B --> D["πŸš€ Express Request"]:::style4
  C --> E["✨ Custom Interfaces"]:::style5

  classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

  linkStyle default stroke:#e67e22,stroke-width:3px;

Happy coding! 😊

Getting Started with TypeScript in React πŸš€

TypeScript enhances your React experience by adding strong typing. This helps catch errors early and improves code readability. Let’s explore how to type components, props, state, and more!

Typing Components πŸ› οΈ

Functional Components

You can define a functional component using React.FC (Functional Component):

1
2
3
4
5
6
7
8
9
import React from 'react';

interface Props {
  title: string;
}

const MyComponent: React.FC<Props> = ({ title }) => {
  return <h1>{title}</h1>;
};

Class Components

For class components, extend React.Component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';

interface Props {
  title: string;
}

interface State {
  count: number;
}

class MyClassComponent extends React.Component<Props, State> {
  state: State = { count: 0 };

  render() {
    return <h1>{this.props.title} - {this.state.count}</h1>;
  }
}

Typing Props and State πŸ“¦

  • Props Interface: Define the shape of props using interfaces.
  • State Types: Specify state types in class components.

Event Handlers and Refs 🎯

Type event handlers easily:

1
2
3
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
  console.log(event);
};

For refs, use React.RefObject:

1
const myRef = React.useRef<HTMLDivElement>(null);

JSX.Element vs ReactNode 🧩

  • JSX.Element: Represents a React element.
  • ReactNode: Can be anything that can be rendered (strings, numbers, elements, etc.).

Using Hooks with TypeScript πŸ”—

When using hooks, type your state:

1
const [count, setCount] = React.useState<number>(0);

React Component Typing Approaches Comparison

FeatureReact.FCFunction with Typed PropsClass Component
SyntaxReact.FC<Props>(props: Props) => JSX.Elementextends React.Component<Props, State>
Childrenβœ… Implicitβš™οΈ Must declare in Propsβš™οΈ Must declare in Props
Return Typeβœ… Enforced⚠️ Can forgetβœ… Enforced
defaultProps❌ Deprecated in React 18+βœ… Works wellβœ… Works well
Community PreferenceπŸ“‰ DecliningπŸ“ˆ Growing (Recommended)πŸ“Š Legacy/Complex state
Best ForSimple components (older code)Modern functional componentsComplex lifecycle needs
graph TD
  A["🎨 React Component"]:::style1 --> B{"Need Lifecycle?"}:::style2
  B -- "Yes" --> C["πŸ›οΈ Class Component"]:::style3
  B -- "No" --> D["⚑ Functional Component"]:::style4
  D --> E{"Typing Style?"}:::style2
  E -- "Explicit Props" --> F["✨ Function with Props Type"]:::style5
  E -- "React.FC" --> G["πŸ“¦ React.FC<Props>"]:::style6
  
  C --> H["Define Props & State"]:::style3
  F --> I["βœ… Modern Recommended"]:::style5
  G --> J["⚠️ Legacy Pattern"]:::style6

  classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style5 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style6 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

  linkStyle default stroke:#e67e22,stroke-width:3px;

By using TypeScript with React, you can build robust applications with confidence! Happy coding! πŸŽ‰

Setting Up TypeScript for Node.js Projects πŸš€

Getting Started with TypeScript

To set up TypeScript in your Node.js project, follow these simple steps:

  1. Initialize your project:
    1
    
    npm init -y
    
  2. Install TypeScript:
    1
    
    npm install typescript --save-dev
    
  3. Create a tsconfig.json file: Run this command to generate a basic configuration:
    1
    
    npx tsc --init
    

Installing Node Types 🌐

To use Node.js APIs with TypeScript, install the type definitions:

1
npm install @types/node --save-dev

Configuring Modules πŸ“¦

You can choose between CommonJS or ES Modules in your tsconfig.json:

  • For CommonJS:
    1
    
    "module": "commonjs"
    
  • For ES Modules:
    1
    
    "module": "esnext"
    

CommonJS vs ES Modules in TypeScript

AspectCommonJSES Modules
Syntaxrequire() / module.exportsimport / export
File Extension.js (or .cjs).mjs (or .js with "type": "module")
Node.js Supportβœ… Native since beginningβœ… Native since Node.js 12+
Dynamic Importsβœ… Synchronous⚑ Asynchronous
Tree Shaking❌ Limitedβœ… Excellent
TypeScript Config"module": "commonjs""module": "esnext" or "es2020"
Best ForLegacy Node.js appsModern apps, better bundling
Top-Level Await❌ Not supportedβœ… Supported

Using ts-node for Development πŸ› οΈ

For a smoother development experience, use ts-node:

1
npm install ts-node --save-dev

Run your TypeScript files directly:

1
npx ts-node src/index.ts

Typing Node.js APIs πŸ“

You can easily type Node.js APIs like fs, http, and process. Here’s an example using fs:

1
2
3
4
5
6
import * as fs from 'fs';

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

Express.js Typing Example

To use Express with TypeScript, install the types:

1
npm install express @types/express --save

Here’s a simple Express server:

1
2
3
4
5
6
7
8
9
10
11
import express, { Request, Response } from 'express';

const app = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript with Express!');
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

Best Practices for Using @types Packages

Using @types packages can enhance your TypeScript experience. Here are some friendly tips to help you navigate them!

Understanding Type Definition Versions

  • Stay Updated: Always check for the latest version of @types packages. Use npm outdated to see if updates are available.
  • Version Compatibility: Ensure the version of the @types package matches the library version you are using.

Dealing with Type Definition Conflicts

  • Check for Duplicates: If you encounter conflicts, check if multiple @types packages are installed.
  • Use --save-dev: Install type definitions as dev dependencies to avoid conflicts in production.

Using skipLibCheck Option

  • What is it?: The skipLibCheck option in tsconfig.json can speed up compilation by skipping type checks on declaration files.
  • When to Use: Use it when you trust the types in your libraries and want faster builds.
1
2
3
4
5
{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

Reporting Issues & Contributing to DefinitelyTyped

  • Report Issues: If you find a bug, report it on the DefinitelyTyped GitHub.
  • Contribute: Want to help? Fork the repo, make your changes, and submit a pull request!

By following these tips, you can make the most of @types packages and enjoy a smoother TypeScript experience! 😊

Migrating JavaScript Projects to TypeScript πŸš€

Migrating your JavaScript projects to TypeScript can be a smooth journey if you take it step by step. Here are some friendly strategies to help you along the way!

graph LR
  A["πŸ“‚ JavaScript Project"]:::style1 --> B["βš™οΈ Add tsconfig.json"]:::style2
  B --> C["πŸ”§ Enable allowJs"]:::style3
  C --> D["πŸ“ Rename .js to .ts"]:::style4
  D --> E["✍️ Add Type Annotations"]:::style5
  E --> F["πŸ§ͺ Fix Type Errors"]:::style6
  F --> G["βœ… Fully Typed Project!"]:::style7
  
  D -.->|"Gradual"| H["πŸ”„ Mix .js and .ts"]:::style3
  H -.-> E

  classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style6 fill:#e74c3c,stroke:#c0392b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
  classDef style7 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

  linkStyle default stroke:#e67e22,stroke-width:3px;
  linkStyle 6,7 stroke:#95a5a6,stroke-width:2px,stroke-dasharray: 5 5;

1. Rename Your Files πŸ—‚οΈ

Start by renaming your .js files to .ts. This simple change tells TypeScript to start checking your code.

2. Use allowJs and checkJs Options βš™οΈ

In your tsconfig.json, enable allowJs to let TypeScript handle JavaScript files. Use checkJs to check for errors in your JavaScript files. This way, you can gradually introduce TypeScript without breaking everything at once.

1
2
3
4
5
6
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true
  }
}

3. Add Type Annotations Gradually ✍️

You don’t need to annotate everything at once! Start with the most important parts of your code. Adding types incrementally helps you learn and adapt without feeling overwhelmed.

4. Use // @ts-ignore or // @ts-expect-error for Temporary Suppressions 🚧

If you encounter errors that you want to ignore temporarily, use // @ts-ignore or // @ts-expect-error. This allows you to keep moving forward while you plan a proper fix later.

1
2
// @ts-ignore
const value = someUndefinedFunction();
🎯 Hands-On Assignment: Build a Type-Safe Library Integration System πŸš€

πŸ“ Your Mission

Create a comprehensive TypeScript project that demonstrates integration with multiple JavaScript libraries, custom type definitions, and migration strategies. Build a real-world application that showcases proper typing patterns for React components, Node.js modules, and third-party libraries.

🎯 Requirements

  1. Library Integration: Install and properly type at least 3 JavaScript libraries (e.g., lodash, axios, moment) using @types packages.
  2. Custom Declaration Files: Create .d.ts files for at least 2 untyped libraries with proper module declarations and ambient types.
  3. Module Augmentation: Extend Express Request object or similar library interface with custom properties using module augmentation.
  4. React Integration: Build typed React components with proper Props, State, event handlers, and hooks typing.
  5. Node.js Backend: Create a TypeScript Node.js server with typed Express routes, middleware, and API responses.
  6. Migration Strategy: Include a JavaScript file and demonstrate gradual migration using allowJs, checkJs, and incremental type annotations.
  7. Error Handling: Implement comprehensive error handling with typed error responses.

πŸ’‘ Implementation Hints

  1. Use npm install --save-dev @types/library-name for type definitions
  2. Create custom types in types/ directory with proper module declarations
  3. Use declare global for global augmentations and declare module for library extensions
  4. Leverage React.FC for functional components and generic typing for hooks
  5. Configure tsconfig.json with proper moduleResolution and type roots
  6. Use ts-node for running TypeScript files directly during development

πŸš€ Example Structure

style="background: #2c3e50; color: #ecf0f1; padding: 20px; border-radius: 8px; overflow-x: auto; margin: 15px 0;">// Custom declaration file: types/my-untyped-lib.d.ts declare module 'my-untyped-lib' { export interface Config { apiKey: string; timeout: number; } export function initialize(config: Config): Promise; export function getData(endpoint: string): Promise; } // Module augmentation: types/express.d.ts import 'express'; declare global { namespace Express { interface Request { user?: { id: string; email: string; role: 'admin' | 'user'; }; } } } // Typed React component import React, { useState } from 'react'; interface UserCardProps { name: string; email: string; onEdit?: (id: string) => void; } const UserCard: React.FC = ({ name, email, onEdit }) => { const [isEditing, setIsEditing] = useState(false); const handleClick = (event: React.MouseEvent) => { setIsEditing(!isEditing); }; return (

{name}

{email}

<button onClick={handleClick}>Edit</button>
); }; </code></pre>

πŸ† Bonus Challenges

  • Generic Utilities: Create reusable generic utility types for API responses
  • Advanced Patterns: Implement discriminated unions for different response types
  • Testing: Add typed tests using Jest with @types/jest
  • Documentation: Generate API documentation from TypeScript types using TypeDoc
  • Linting: Configure ESLint with TypeScript-specific rules

πŸ“š Learning Goals

  • Master type definitions installation and usage 🎯
  • Create custom .d.ts declaration files for any library πŸ“
  • Apply module augmentation and declaration merging patterns ✨
  • Build type-safe React and Node.js applications πŸš€
  • Implement gradual migration strategies from JavaScript πŸ”„
  • Configure TypeScript for optimal developer experience πŸ› οΈ

πŸ’‘ Pro Tip: Major companies like Microsoft, Google, and Airbnb use these exact patterns for integrating TypeScript with their existing JavaScript codebases. Master these skills to work on enterprise-scale applications!

Share Your Solution! πŸ’¬

Built your type-safe integration system? Post your repository link in the comments below! Show us your TypeScript integration expertise! πŸš€βœ¨

</div> </details> ## Conclusion Mastering TypeScript integration with JavaScript libraries is essential for modern web development, enabling you to leverage the vast npm ecosystem while maintaining type safety and code quality. By understanding type definitions, creating custom declaration files, and applying proper migration strategies, you'll build robust applications that scale with confidence. Start integrating TypeScript into your projects today and experience the benefits of static typing in the dynamic JavaScript world! πŸš€ By following these strategies, you can make your migration to TypeScript a friendly and enjoyable experience! Happy coding! πŸŽ‰
This post is licensed under CC BY 4.0 by the author.