Why I Prefer JSDoc Over TypeScript
The Case for JSDoc: Documentation-First Development
After years of working with both TypeScript and JavaScript, I’ve come to a conclusion that might surprise some developers: I strongly prefer JSDoc over TypeScript for most projects. This isn’t because I don’t value type safety—quite the opposite. It’s because I believe there’s a better way to achieve type safety while maintaining the simplicity and standards-compliance of vanilla JavaScript.
The Philosophy: Code Should Be About Behavior, Not Metadata
As Jared White eloquently puts it in his article “The Nuances of JavaScript Typing using JSDoc”, code should be strictly about behavior. What it’s called, what it does. The metadata surrounding the code—this is a string, that is an integer—belongs in documentation, not intertwined with your business logic.
Think about it: you should be documenting your code anyway. Every function, class, and complex data structure deserves at least a sentence or two explaining its purpose. JSDoc lets you kill two birds with one stone—document your code AND provide type information.
The Technical Benefits
1. No Build Step Required
Your .js files are real, standards-compliant JavaScript that runs anywhere without compilation. No webpack plugins, no babel transforms, no build pipeline complexity. This is huge for:
- Library authors who want to ship actual JavaScript
- Legacy codebases that can’t easily adopt a full TypeScript build process
- Prototyping and experimentation where build overhead slows you down
- Educational content where you want to focus on JavaScript fundamentals
2. Gradual Adoption
You can add JSDoc types incrementally:
// Start simple
function add(a, b) {
return a + b;
}
// Add types when needed
/**
* Adds two numbers together
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) {
return a + b;
}
No need to convert your entire codebase at once or deal with mixed .js and .ts files.
3. Same Type Checking Power
With // @ts-check at the top of your files, you get identical type checking to TypeScript:
// @ts-check
/** @type {string} */
let userName;
userName = "Alice"; // ✓ Works fine
userName = 42; // ✗ Type error!
Your IDE (VSCode, WebStorm, etc.) will show the same red squiggles and autocomplete suggestions.
Real-World Examples
Complex Object Types
/**
* User configuration object
* @typedef {Object} UserConfig
* @property {string} name - User's display name
* @property {number} age - User's age
* @property {string[]} permissions - Array of permission strings
* @property {boolean} [isActive] - Whether user is active (optional)
*/
/**
* Creates a new user with the given configuration
* @param {UserConfig} config - User configuration
* @returns {Promise<User>} Promise resolving to created user
*/
async function createUser(config) {
// Implementation here
}
Generic-Like Behavior
/**
* Generic array utility function
* @template T
* @param {T[]} array - Input array
* @param {(item: T) => boolean} predicate - Filter function
* @returns {T[]} Filtered array
*/
function filterArray(array, predicate) {
return array.filter(predicate);
}
Class Documentation
/**
* Manages user authentication and sessions
*/
class AuthManager {
/**
* @param {string} apiEndpoint - API base URL
* @param {Object} options - Configuration options
* @param {number} [options.timeout=5000] - Request timeout in ms
* @param {boolean} [options.retryOnFailure=true] - Whether to retry failed requests
*/
constructor(apiEndpoint, options = {}) {
this.apiEndpoint = apiEndpoint;
this.timeout = options.timeout || 5000;
this.retryOnFailure = options.retryOnFailure !== false;
}
/**
* Authenticates a user with email and password
* @param {string} email - User's email address
* @param {string} password - User's password
* @returns {Promise<{token: string, user: UserConfig}>} Authentication result
* @throws {AuthError} When authentication fails
*/
async login(email, password) {
// Implementation
}
}
Setup and Configuration
Getting JSDoc type checking working is surprisingly straightforward:
1. Basic Configuration
Add a jsconfig.json to your project root:
{
"compilerOptions": {
"checkJs": true,
"strictNullChecks": false,
"target": "es2022"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
2. Generate Declaration Files (Optional)
If you’re building a library, add a tsconfig.json for generating .d.ts files:
{
"include": ["src/**/*"],
"compilerOptions": {
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "types",
"declarationMap": true
}
}
Then add to your package.json:
{
"scripts": {
"build:types": "npx tsc"
}
}
3. IDE Integration
Most modern editors work out of the box:
- VSCode: Full support with IntelliSense
- WebStorm: Excellent JSDoc integration
- Vim/Neovim: Works with LSP plugins
- Zed: Growing support for JSDoc hints
Addressing Common Concerns
“But TypeScript Catches More Errors!”
This is largely a myth. With "strict": true and proper JSDoc annotations, you get the same level of type safety. The TypeScript compiler (tsc) is doing the checking in both cases.
“JSDoc Syntax is Verbose!”
Yes, it’s more verbose than TypeScript’s inline syntax. But this “verbosity” is actually documentation. Compare:
// TypeScript - concise but no docs
function processUser(user: User, options: ProcessingOptions): Promise<ProcessedUser> {
// What does this function do? 🤷♂️
}
/**
* Processes user data by applying validation, normalization, and enrichment
* @param {User} user - User object to process
* @param {ProcessingOptions} options - Processing configuration
* @returns {Promise<ProcessedUser>} Promise resolving to processed user data
*/
function processUser(user, options) {
// Clear what this does! 👍
}
“What About Complex Generics?”
JSDoc handles most generic scenarios with @template:
/**
* @template T, U
* @param {T[]} items
* @param {(item: T) => U} mapper
* @returns {U[]}
*/
function mapArray(items, mapper) {
return items.map(mapper);
}
For extremely complex generic scenarios, you might need TypeScript. But these cases are rarer than you think.
Industry Momentum and Standards
The web runs on ECMAScript, not TypeScript. When you choose JSDoc + JavaScript, you’re:
- Betting on web standards rather than a single company’s technology
- Reducing your build complexity and toolchain dependencies
- Making your code more accessible to developers who don’t know TypeScript
- Future-proofing against potential changes in the TypeScript ecosystem
As Jared White argues, “JavaScript + JSDoc + tsc should be the industry default.” The combination gives you all the benefits of TypeScript’s type checking while keeping your code as vanilla JavaScript.
When TypeScript Still Makes Sense
I’m not completely anti-TypeScript. There are scenarios where it’s still the right choice:
- Large teams where strict interfaces prevent integration issues
- Frameworks that require it (some React/Angular setups)
- Complex domain models with deep inheritance hierarchies
- When your team is already TypeScript-native and productivity would suffer from switching
Getting Started Today
Want to try JSDoc in your next project? Here’s your action plan:
- Add
// @ts-checkto the top of your JavaScript files - Install TypeScript as a dev dependency:
npm install -D typescript - Start documenting your functions with JSDoc comments
- Add type annotations gradually using
@param,@returns,@typedef - Configure your editor to show type hints and errors
- Set up a build script for type checking in CI
Conclusion
JSDoc represents a philosophy of development where documentation and type safety go hand in hand. Instead of scattering type information throughout your code, you centralize it in meaningful, readable comments that serve multiple purposes.
You get the safety of TypeScript, the simplicity of JavaScript, and the bonus of well-documented code. Your future self (and your teammates) will thank you for choosing clarity and standards over the latest trendy syntax.
The web is built on JavaScript. Why not embrace it fully while still getting all the development benefits you need?
Have you tried JSDoc in your projects? What’s been your experience with documentation-driven development? I’d love to hear your thoughts and see how you’re using JSDoc in real-world applications.