Interoperability¶
TypeScript’s type system is optional and gradual.
This means you can mix JS and TS freely—as long as you understand how TypeScript interprets JavaScript modules and how to provide missing types.
Let’s break it down.
Importing JavaScript into TypeScript¶
TypeScript can import .js files directly, as long as allowJs is enabled in tsconfig.json:
{
"compilerOptions": {
"allowJs": true
}
}
Example: Importing a JS module¶
utils.js
export function add(a, b) {
return a + b
}
index.ts
import { add } from "./utils.js"
add(1, 2) // works
How TypeScript types JS imports¶
- If
checkJsis off, TS treats JS asany - If
checkJsis on, TS infers types from the JS code - If JSDoc is present, TS uses those types
This gives you multiple migration paths.
Typing Untyped Libraries¶
Some npm packages don’t ship with TypeScript types.
When you import them, TypeScript will treat them as any:
import foo from "foo" // foo: any
This removes type safety.
There are three ways to fix this:
1. Install community types (@types/...)¶
Many libraries have type definitions on DefinitelyTyped.
npm install --save-dev @types/lodash
After installing:
import _ from "lodash" // fully typed
This is the easiest and most common solution.
2. Write your own .d.ts file¶
If no types exist, you can create them.
Create a file:
types/foo/index.d.ts
Inside:
declare module "foo" {
export function greet(name: string): string
}
Now TypeScript knows how to type the library.
Usage¶
import { greet } from "foo"
greet("Alice") // typed!
3. Use declare module for quick fixes¶
If you just want to silence errors temporarily:
declare module "foo"
This tells TypeScript:
“This module exists, but I don’t know its types.”
Everything becomes any.
Use this only as a temporary measure.
Writing Declaration Files (.d.ts)¶
Declaration files describe the shape of code without implementing it.
They contain:
declarestatements- interfaces
- function signatures
- module declarations
- type aliases
They do not contain runtime code.
Example: Declaring a function¶
// math.d.ts
declare function add(a: number, b: number): number
Example: Declaring a module¶
// my-lib.d.ts
declare module "my-lib" {
export function parse(input: string): number
}
Example: Declaring a class¶
declare class Person {
constructor(name: string)
greet(): string
}
Example: Declaring a global variable¶
declare const VERSION: string
Where to Put Declaration Files¶
You have two options:
1. Inside a types/ folder¶
project/
src/
types/
foo/
index.d.ts
Add to tsconfig.json:
{
"compilerOptions": {
"typeRoots": ["./types", "./node_modules/@types"]
}
}
2. Inline next to the JS file¶
utils.js
utils.d.ts
TypeScript automatically picks it up.
JSDoc vs .d.ts¶
| Approach | Best For |
|---|---|
| JSDoc | Gradual migration, typing JS files directly |
.d.ts files |
Typing external libraries, providing ambient types |
| TypeScript files | Full migration, maximum safety |
You can mix all three in the same project.
Putting It All Together¶
Here’s a realistic scenario:
You have a JS library:
logger.js
exports.log = function (msg) {
console.log("LOG:", msg)
}
TypeScript complains:
Could not find a declaration file for module './logger'
Fix: Add a declaration file¶
logger.d.ts
export function log(msg: string): void
Now in TypeScript:
import { log } from "./logger.js"
log("Hello") // fully typed
This is exactly how you integrate legacy JS into modern TS codebases.
Summary¶
In this lesson, you learned how TypeScript interoperates with JavaScript:
1. Importing JS into TS¶
- Works automatically with
allowJs - TypeScript infers types or uses JSDoc
2. Typing untyped libraries¶
- Install
@types/... - Write your own
.d.tsfiles - Use
declare moduleas a temporary fallback
3. Writing declaration files¶
- Describe modules, functions, classes, globals
- No runtime code
- Essential for integrating JS libraries
Interoperability is one of TypeScript’s greatest strengths—it lets you adopt TS at your own pace while still working with the entire JavaScript ecosystem.