Chapter 36: TypeScript in JavaScript Projects (JSDoc)
TypeScript in JavaScript Projects — via JSDoc (very detailed, realistic 2025–2026 perspective)
Imagine the following situation — very common in 2025–2026:
- You have a medium/large JavaScript codebase (not TypeScript)
- You want better autocompletion, hover documentation, refactoring safety, type errors before runtime
- But you cannot / do not want to rename all files to .ts, run build steps, change the CI pipeline, deal with transpilation issues, convince the team to learn TypeScript syntax, etc.
Solution that many teams actually choose today → Use TypeScript’s type checker only as a “smart linter” on plain JavaScript files → Write type information using JSDoc comments → Let VS Code + tsc –noEmit –watch show you red squiggles and suggestions
This approach is officially called “TypeScript in JavaScript with JSDoc” (or “JSDoc-powered TypeScript checking”)
1. How much value can you really get without converting to .ts files?
In 2025–2026 — surprisingly a lot:
| Feature | Works with JSDoc + .js files? | Quality / DX in 2026 |
|---|---|---|
| Autocompletion inside functions | Yes | Very good |
| Hover type information | Yes | Excellent |
| Parameter & return type checking | Yes | Very good |
| Refactor → rename symbol | Yes | Good–very good |
| Find all references | Yes | Good |
| “Extract to function” with type preservation | Yes | Good |
| Catch property-does-not-exist errors | Yes | Good |
| Strict null/undefined checking | Partial (requires extra flags) | Medium–good |
| Generics & conditional types | Yes (limited but useful) | Medium |
| Full strict mode + noImplicitAny | No (almost impossible) | — |
Bottom line in 2026: JSDoc + TypeScript gives you ~60–80% of the value of full TypeScript with ~10–20% of the migration pain
2. Minimal setup (what actually works best in 2026)
package.json
|
0 1 2 3 4 5 6 7 8 9 10 11 |
{ "scripts": { "typecheck": "tsc --noEmit", "typecheck:watch": "tsc --noEmit --watch" } } |
tsconfig.json — the sweet spot for JSDoc + .js files
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "allowJs": true, "checkJs": true, // ← this is the magic line "noEmit": true, "skipLibCheck": true, "strict": false, // usually keep false "noImplicitAny": false, // important — many .js files rely on any "strictNullChecks": true, // you can often turn this on "esModuleInterop": true, "jsx": "preserve" // if you have .jsx files }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } |
3. Most useful JSDoc patterns (real examples people actually write)
Pattern 1: Basic function typing
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * @param {string} name * @param {number} [age] - optional * @returns {string} */ function greet(name, age) { return `Hello ${name}${age ? ` (${age})` : ''}`; } |
Pattern 2: Object shape
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * @typedef {Object} User * @property {number} id * @property {string} name * @property {string} [email] * @property {boolean} isActive */ /** * @param {User} user * @returns {string} */ function formatUser(user) { return `${user.name} (${user.id})`; } |
Pattern 3: Array of objects
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * @typedef {Object} Product * @property {string} sku * @property {number} price * @property {string[]} tags */ /** * @param {Product[]} products * @returns {number} */ function calculateTotal(products) { return products.reduce((sum, p) => sum + p.price, 0); } |
Pattern 4: Import types from other files (very powerful)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// types/user.js /** * @typedef {Object} User * @property {number} id * @property {string} name */ // utils.js /** * @typedef {import("./types/user").User} User * * @param {User} user */ function logUser(user) { console.log(user.name); } |
Pattern 5: Generic-like behavior (limited but useful)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * @template T * @param {T[]} items * @returns {T | undefined} */ function first(items) { return items[0]; } |
Pattern 6: Promise / async
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * @param {number} id * @returns {Promise<User>} */ async function fetchUser(id) { const res = await fetch(`/api/users/${id}`); return res.json(); } |
4. Quick reference — most useful JSDoc tags in 2026
| Tag | What it does | Most common use-case |
|---|---|---|
| @param {Type} name | Parameter type | Almost every function |
| @returns {Type} | Return type | Functions that return values |
| @typedef | Define reusable object shape | DTOs, API responses, config objects |
| @type | Inline type for variable | When you cannot use @typedef |
| @template T | Generic type parameter | Utility functions |
| @import | Import type from another file | Cross-file type reuse |
| @satisfies | TS 4.9+ — narrow without widening | Config objects, theme tokens |
| @see | Link to documentation | Team knowledge sharing |
5. Realistic migration path using only JSDoc
- Add tsconfig + checkJs: true
- Run tsc –noEmit → see hundreds of errors
- Start adding @param, @returns, @typedef to high-traffic utility functions
- Fix obvious errors (wrong number of arguments, property does not exist)
- Gradually add types to more files
- When a file feels “ready” → rename to .ts and convert syntax (optional)
- Eventually turn on more strict flags
Many teams stop at step 4–5 — they never rename to .ts and stay in “JSDoc TypeScript” mode for years.
6. Pros & Cons — honest 2026 perspective
Pros
- Zero runtime changes
- No build step changes
- Team can adopt gradually (one developer can start)
- Works with plain .js, .jsx, .cjs
- Very good VS Code experience
- Low risk — can remove everything easily
Cons
- Verbose (lots of comments)
- No enums, no as const inference, no real generics
- No satisfies on variables (only on expressions)
- Refactoring across files sometimes weaker
- Cannot use full strict mode (noImplicitAny usually stays off)
- Some third-party libs still need @types/*
7. Your first realistic step today
- Add the tsconfig above
- Run npx tsc –noEmit → see what complains
- Pick one small utility function in your project
- Add @param and @returns
- Watch VS Code give you better hints immediately
- Run tsc –noEmit again → see fewer errors
Any part confusing or most relevant for your current codebase?
Want to:
- See a full small project before/after JSDoc
- Compare JSDoc vs full .ts migration effort
- Learn the most powerful JSDoc + generics tricks
- Handle JSX + JSDoc in React projects
- Discuss when to finally switch to real .ts files
Just tell me — we continue from exactly where you need it 😄
