Mastering tsconfig.json: Advanced Configuration Techniques for TypeScript

Gili Yaniv
3 min readNov 21, 2024

In our introductory guide to tsconfig.json, we covered the basics of configuring a TypeScript project. While those foundational concepts are essential, advanced configurations can unlock even more powerful workflows, especially for larger or more complex projects.

This article dives into advanced tsconfig.json techniques, building on the basics to help developers streamline development and manage challenging scenarios effectively.

Advanced Techniques and Features

1. Using Project References for Multi-Project Builds

For large-scale applications or monorepos, you can break your codebase into smaller, interdependent projects. TypeScript’s project references feature makes this possible.

Example: Setting Up Project References

  1. Create separate tsconfig.json files for each project or module.
  2. In the “parent” configuration file, reference other projects.

packages/module-a/tsconfig.json:

{
"compilerOptions": {
"composite": true,
"outDir": "../../dist/module-a"
},
"include": ["src/**/*"]
}

packages/module-b/tsconfig.json:

{
"compilerOptions": {
"composite": true,
"outDir": "../../dist/module-b"
},
"references": [
{ "path": "../module-a" }
],
"include": ["src/**/*"]
}

tsconfig.json (root project):

{
"files": [],
"references": [
{ "path": "./packages/module-a" },
{ "path": "./packages/module-b" }
]
}

Run tsc --build to compile all referenced projects efficiently.

Benefits

  • Dependencies are built in the correct order.
  • Incremental builds reduce compile time.
  • Ensures inter-project type safety.

2. Configuring Aliases with paths

While path mapping is a basic feature, you can leverage it for more advanced use cases, such as supporting multiple environments or conditional module resolution.

Example: Conditional Module Resolution

Set up aliases for environment-specific modules:

"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@config": [
"configs/development",
"configs/production"
]
}
}

Use the environment variable to resolve paths dynamically during build or runtime with a bundler (e.g., Webpack).

3. Fine-Tuning Build Outputs

You can customize how and where TypeScript generates outputs using options like declarationMap and rootDirs.

Generate Declaration Maps for Better Debugging

"compilerOptions": {
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"sourceMap": true
}

This setup generates .d.ts.map files alongside .d.ts, enabling better navigation in IDEs when using your library.

Use rootDirs for Virtual Source Merging

For projects with multiple source directories that compile into a single output:

"compilerOptions": {
"rootDirs": ["src", "generated"],
"outDir": "dist"
}

This allows seamless referencing of files across directories as if they were in the same virtual root.

4. Custom Module Resolution

TypeScript allows you to define how modules are resolved using moduleResolution. Use node for Node.js-style resolution or classic for legacy module resolution.

Example: Customizing Module Resolution Strategy

"compilerOptions": {
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@shared/*": ["src/shared/*"],
"@legacy/*": ["lib/legacy/*"]
}
}

5. Controlling Emit Behavior

Sometimes, you may want to compile TypeScript files without generating JavaScript outputs. This is useful for tasks like type-checking only.

Enable noEmit to Skip JavaScript Output

"compilerOptions": {
"noEmit": true,
"strict": true
}

Alternatively, use emitDeclarationOnly to output .d.ts files without JavaScript:

"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "./types"
}

6. Improving Performance for Large Projects

For large codebases, performance optimizations can save significant time.

Enable Incremental Compilation

"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.cache/tsbuildinfo"
}

Skip Files Using skipLibCheck

"compilerOptions": {
"skipLibCheck": true
}

This skips type-checking for .d.ts files, speeding up compilation without affecting runtime behavior.

7. TypeScript with ESLint and tsconfig.json

To ensure ESLint correctly interprets your TypeScript configuration, reference tsconfig.json in your ESLint settings:

{
"parserOptions": {
"project": "./tsconfig.json"
}
}

This enables rules like @typescript-eslint/no-unused-vars to function correctly, ensuring consistency across tools.

8. Using Compiler Hooks

If you need to run custom scripts during compilation, TypeScript supports hooks through plugins. Add a custom plugin in your tsconfig.json:

"compilerOptions": {
"plugins": [
{ "name": "typescript-plugin-styled-components" }
]
}

Plugins can extend or modify how TypeScript processes specific code patterns.

Wrapping Up

Advanced tsconfig.json configurations allow developers to handle complex scenarios, optimize workflows, and build scalable projects efficiently. By combining features like project references, path aliases, and incremental builds, you can tailor your TypeScript setup to meet the needs of even the most demanding applications.

Follow me on Twitter, Medium, and Linkedin to read more!

--

--

No responses yet