A Modern Framework for Maintainable Code
An interactive story exploring a modern approach to type-safe JavaScript without transpilation.
The goal isn't to avoid TypeScript, but to ship standard, transpilation-free JavaScript. We use JSDoc and TSC as a powerful static analysis layer, not a compiler.
For applications, which will have a build step anyway, the benefits are less pronounced. For libraries, I strongly urge you to use JSDoc instead. — Rich Harris, Creator of Svelte
A pragmatic, two-tiered system for type definitions that balances co-location with maintainability.
For types specific to a single function's implementation and not reused elsewhere, use inline JSDoc.
For complex objects and shared data structures, define them in a dedicated -types.d.ts
file. This creates a canonical, single source of truth.
Let's explore some powerful JSDoc patterns that unlock the full potential of TypeScript's type inference engine.
Some TypeScript features, like interface
or enum
, are syntax-only and cannot be used in JSDoc. This is a feature, not a bug! It encourages defining complex types in .d.ts
files, cleanly separating type definitions from runtime logic.
Use the @returns {x is Type}
syntax to create type guard functions that narrow types within conditional blocks.
Use @returns {asserts x is Type}
to create functions that throw an error if a type assumption is wrong, narrowing the type for all subsequent code.
Apply /** @type {const} */
to an object or array literal to treat it as deeply readonly, inferring the most specific types possible.
Use /** @satisfies {Type} */
to validate that an expression conforms to a type without changing the expression's own, more specific type.
Let's look at the essential configuration files needed to enable this workflow for a new library or application.
package.json
Define scripts for cleaning, building declarations, and running multiple checks in parallel.
tsconfig.json
This file activates TypeScript's checking engine for your JavaScript project. Here are the key options:
"strict": true
enables all of TypeScript's strict type-checking options. This is essential for catching errors."checkJs": true
tells the compiler to process and report errors in .js
files."noEmit": true
ensures that running tsc
only performs checks and does not create any output files."moduleResolution": "node16"
ensures TypeScript resolves modules the same way older versions of Node.js do.declaration.tsconfig.json
A separate config for publishing. It inherits from the main config and emits .d.ts
files. (For libraries only)
.gitignore
Ignore generated declaration files, but make an exception for hand-authored type definitions (like *-types.d.ts
).
For a harmonious workflow, it's crucial to assign clear roles to our tools. TypeScript handles type correctness, while linters manage style and documentation quality.
Balancing the roles of tsc
and ESLint for a harmonious workflow.
tsc
.
By combining a clear philosophy, strategic tooling, and advanced JSDoc patterns, we can create JavaScript ecosystems that are robust, maintainable, and a joy to work in—all without leaving the world of standard JavaScript.
Thank You.