Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our course on developing a modern JavaScript framework.
In our last lesson, we put the finishing touches on our development workflow by integrating Biome into VS Code and setting up pre-commit hooks. This ensures that every piece of code committed to our repository is clean and consistent. Our tooling is now robust and automated.
Today, we shift our focus from code style to code correctness by configuring TypeScript. The tsconfig.json file is the control panel for the TypeScript compiler, and a well-crafted configuration is the bedrock of a reliable library. As a lead developer, you know that a strong type system is not just about preventing bugs; it's about creating clear, self-documenting APIs.
By the end of this lesson, you will be able to configure tsconfig.json for strict library type checking and declaration file generation.
1. The Two Roles of TypeScript in a Modern Project
Before we start adding options, it's crucial to understand a fundamental concept: in a modern project that uses a bundler like Vite, the TypeScript compiler (tsc) serves two distinct purposes:
- Type Checker: It analyzes your code, verifying type correctness without producing any JavaScript files. This is its primary role during development.
- Transpiler: It converts your TypeScript code (
.ts) into JavaScript code (.js).
In our project, Vite will handle the transpilation because it's significantly faster. We will use tsc primarily as a powerful type checker. However, we will also use tsc's transpilation capabilities for one specific, critical task: generating declaration files (.d.ts).
TypeScript: tsconfig demystified!
To solidify this concept, watch this short clip from the video "TypeScript: tsconfig demystified!". It clearly explains the distinction between TypeScript's checking and compiling roles.
Watch from 03:37 to 04:07. The key takeaway is to keep these two roles—checking and compiling—in mind as we configure our tsconfig.json.
2. Enforcing Strict Type Safety
Our first goal is to make our type system as strict as possible. This helps catch a wide range of potential runtime errors during development.
The most important setting for this is strict.
Let's start with the official TypeScript documentation for the strict flag to understand what it provides.
Read the short section on the strict flag. Notice that it's a bundle of several other strictness options, like strictNullChecks and noImplicitAny.
Setting "strict": true is the single most effective step you can take to improve type safety. However, there is one more highly recommended option that is not included in the strict bundle: noUncheckedIndexedAccess.
This option prevents you from accessing array elements or object properties without first verifying they exist, which helps eliminate a common source of undefined errors at runtime.
TypeScript: tsconfig demystified!
This clip from "TypeScript: tsconfig demystified!" provides an excellent, practical code example demonstrating why noUncheckedIndexedAccess is so crucial.
Watch from 09:27 to 12:35. Pay close attention to the example with the empty array. It clearly shows how TypeScript, by default, can make unsafe assumptions and how this flag corrects that behavior.
These two options, strict and noUncheckedIndexedAccess, form the foundation of our type-safe configuration.
3. Configuring for a Consumable Library
For others to use our library in their TypeScript projects, they need to know the "shape" of our code—what functions exist, what arguments they take, and what they return. This is accomplished with declaration files (.d.ts).
Our second goal is to configure tsc to generate these files from our source code.
Matt Pocock's "The TSConfig Cheat Sheet" video has a concise section on the exact settings needed for building a library.
Watch from 04:16 to 05:15. This clip introduces the essential flags: declaration and declarationMap.
Here's a breakdown of the key options for library output:
"declaration": true: This is the main switch. It tellstscto generate a corresponding.d.tsfile for each.tssource file."sourceMap": true: Generates.js.mapfiles that map the compiled JavaScript back to the original TypeScript source. This is essential for debugging."declarationMap": true: Generates.d.ts.mapfiles. This is a powerful feature for library authors. When a user of your library uses "Go to Definition" on one of your functions in their editor, it will take them to your original.tssource file, not the.d.tsdeclaration file, providing a much better developer experience.
4. Assembling Your tsconfig.json
Now, let's combine these concepts into a complete tsconfig.json file for our project. We will use Matt Pocock's cheat sheet as our guide, selecting the options that are right for a modern library built with Vite.
We'll use this comprehensive cheat sheet as a reference to build our configuration. It provides a great starting point with excellent explanations.
Review the 'Quickstart' example and the explanations in the 'Strictness' and 'Building for a Library' sections. This will give you the complete context for the configuration we are about to create.
Based on these best practices, here is the configuration for our project. Open your tsconfig.json file and replace its contents with the following:
{
"compilerOptions": {
/* Base Options */
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
/* Module Resolution */
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
/* Library Output */
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
/* Project Structure */
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Why these options?
- Base Options: We target a modern version of JavaScript (
ES2022) and use modern modules (ESNext), letting Vite handle compatibility. We includeDOMtypings because our framework may be used in browsers. - Strictness: We've enabled the strongest type-checking available.
- Module Resolution:
"moduleResolution": "bundler"is the modern setting that tells TypeScript to mimic how bundlers like Vite resolve modules. It's the correct choice for our stack. - Library Output: We've enabled the generation of declaration files, source maps, and declaration maps, and specified that all output should go to the
distdirectory. - Project Structure:
"rootDir": "./src"tellstscthat our source files live in thesrcfolder, which helps maintain a clean output structure indist. Theincludeandexcludearrays make our project boundaries explicit.
5. Setting Up a Type-Checking Script
With this configuration, we can now add a script to our package.json to run the type checker across our project. This is a command you'll run frequently to ensure your code is valid.
Add the following type-check script to the scripts section of your package.json:
// package.json
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit" // Add this line
},
The --noEmit flag is crucial. It tells tsc to perform all the checks but not to generate any JavaScript files, leaving that job to Vite.
You can test it now by running npm run type-check in your terminal. It should complete without errors.
Conclusion
Excellent work. You've now established a professional-grade TypeScript configuration that ensures your code is both type-safe and ready for distribution as a library.
Key Takeaways:
- A
tsconfig.jsonfor a modern library project should be configured for two primary goals: strict type-checking and declaration file generation. - The combination of
"strict": trueand"noUncheckedIndexedAccess": trueprovides a powerful defense against common bugs. "declaration": trueand"declarationMap": trueare essential for creating a library that provides a great developer experience for its users.- In a Vite project,
tsc --noEmitis used for type-checking, while Vite handles the actual transpilation of code to JavaScript.
Next Lesson Preview:
Our project is now fully configured from a tooling and type-safety perspective. In the next lesson, we will dive into Vite's configuration. We will learn how to configure Vite in library mode to produce both ESM and CommonJS outputs, making our framework compatible with a wide range of JavaScript projects.