What the heck is a tsconfig.json file?
I am an experienced full stack engineer currently working in Rakuten India as an SSE2 with a strong background in the MERN stack and over 5 years of experience building products from scratch. I have also led small teams and am well-versed in TypeScript. Additionally, I bring a unique perspective to my work having previously been an ex-founder. I am passionate about creating high-quality and efficient solutions and am always eager to take on new challenges.
Writing code in TypeScript is beneficial, but as a senior developer, understanding how to configure your project to meet your needs is crucial. One place where you can fine-tune project configuration and inform the TypeScript compiler how to compile the code is through a tsconfig.json file. While a default tsconfig.json file is provided when setting up a project (tsc --init), it may not always suit your requirements. In this article, I'll discuss some of the most commonly used options in a TypeScript configuration file.
target
This property specifies the ECMAScript target version for TypeScript compilation. The value can be any valid ECMAScript version like "ES5", "ES6", "ES2015", "ES2016", etc. It determines the level of compatibility with JavaScript engines and which features and syntax TypeScript will allow. We should choose this based on the packages that we are going to use in our project
strict
This is one of the most important options in a tsconfig.json file. This decides how strict your project configuration is and this makes a lot of difference in the quality of your project code. When set to true, it enables all strict type-checking options: noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, etc. Enabling strict mode helps catch more type-related errors at compile-time and promotes writing safer and more predictable code. Let us discuss about each type-checking option and the error it throws if made true
noImplicitAnyThe
noImplicitAnycompiler option in TypeScript is a strict type-checking option that helps enforce stronger typing discipline by flagging variables and parameters with an implicitanytype. Let us discuss with an example/** add function without noImplicitAny in compiler options */ function add(a, b) { return a + b; } const result = add(3, "5"); // No compilation error if noImplicitAny is disabled console.log(result); /** add function with noImplicitAny in compiler options */ function add(a: number, b: number): number { return a + b; } const result = add(3, "5"); // Compilation error: Argument of type 'string' is not assignable to parameter of type 'number' console.log(result);In the above example, when the function is written without noImplicitAny option disabled, it implicitly considers arguments a and b as type any that leads to not throwing any compilation errors. In the 2nd case, when noImplicitAny is enabled, the compiler asks you to explicitly declare types of arguments which donot allow any other types apart from numbers.
noImplicitThis
WhennoImplicitThisis enabled, TypeScript will issue a compilation error whenever it detects that the type ofthisis implicitly of typeany. This often occurs whenthisis used in a function or method without a clear type annotation or without being explicitly bound to a particular context.By default, TypeScript assumes that the type of
thiswithin a function or method isanyif no type information is provided. This can lead to errors, especially in object-oriented code where the context ofthisis crucial for correct behavior. Consider the following exampleclass MyClass { private data: number = 10; fetchData() { return this.data; } } const myObject = new MyClass(); const fetchDataFunc = myObject.fetchData; console.log(fetchDataFunc()); // undefined or runtime error because 'this' is not boundIn this example, when
fetchDataFuncis called, the context ofthisis lost because it's invoked as a standalone function rather than as a method of an instance ofMyClass. As a result,this.datawithinfetchDatawould likely result inundefined, leading to unexpected behavior or runtime errors.By enabling
noImplicitThisin thetsconfig.jsonfile TypeScript will raise a compilation error for the usage ofthisinfetchData, indicating that the type ofthisis implicitly treated asany. Developers must then either provide explicit type annotations or ensure thatthisis correctly bound within the function.To resolve the compilation error in the previous example, developers could either bind
fetchDatato an instance ofMyClassor use arrow function syntax, which automatically captures the lexicalthis
class MyClass {
private data: number = 10;
fetchData = () => {
return this.data;
}
}
const myObject = new MyClass();
const fetchDataFunc = myObject.fetchData;
console.log(fetchDataFunc()); // 10
strictNullChecksWhen
strictNullChecksis enabled,Variables declared without an explicit type or initialized to
undefinedare considered to have a type ofundefinedVariables declared with a union type that includes
nullorundefinedcannot be accessed without first checking for null or undefined.TypeScript raises compilation errors when null or undefined values are assigned to variables that do not allow them.
Here is an example
// Example 1: Variable Initialization let x: number; x = 10; // OK x = undefined; // Error: Type 'undefined' is not assignable to type 'number' // Example 2: Union Types let y: string | null; y = "hello"; // OK y = null; // OK console.log(y.length); // Error: Object is possibly 'null' // Example 3: Function Parameters function greet(name: string) { return "Hello, " + name; } greet("Alice"); // OK greet(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string' // Example 4: Optional Parameters function printMessage(message?: string) { console.log(message.toUpperCase()); // Error: Object is possibly 'undefined' } printMessage(); // OK printMessage("Hello TypeScript"); // OK printMessage(undefined); // OK
strictFunctionTypesWhen
strictFunctionTypesis enabled, TypeScript performs stricter checks on function type compatibility, which includes checking parameter types contravariantly (input types are contravariant) and return types covariantly (output types are covariant). This means:interface Animal { name: string; } interface Dog extends Animal { breed: string; } // Function that takes an Animal parameter function printAnimal(animal: Animal): void { console.log(animal.name); } // Function that takes a Dog parameter function printDog(dog: Dog): void { console.log(dog.name); } // Assigning 'printDog' to a variable of type 'Animal => void' const printFunc: (animal: Animal) => void = printDog; // Error: Argument of type '(dog: Dog) => void' is not assignable to parameter of type '(animal: Animal) => void'.In this example, even though
Dogis a subtype ofAnimal, TypeScript raises an error becausestrictFunctionTypesenforces stricter type checking on function assignments.printDogcannot be assigned to a variable of type(animal: Animal) => voidbecause its parameter type (Dog) is not assignable to the parameter type (Animal) of the target function signature.// Function that returns a subtype function getDog(): Dog { return { name: 'Buddy', breed: 'Labrador' }; } // Function that returns a supertype function getAnimal(): Animal { return { name: 'Buddy' }; } // Assigning 'getDog' to a variable of type '() => Animal' const getFunc: () => Animal = getDog; // OKIn this example,
strictFunctionTypesallows assigninggetDogto a variable of type() => Animalbecause the return type (Dog) ofgetDogis assignable to the return type (Animal) of the target function signature.
By enablingstrictFunctionTypes, TypeScript ensures stricter type compatibility between function types, which helps catch potential type errors early in the development process, leading to more robust and reliable code. However, it may require adjustments in code where looser function type compatibility was previously tolerated.
While there are many other properties, I am not writing about all of them. The above mentioned are the most commonly used properties.
outDir:
Defines the directory where TypeScript compiler outputs compiled JavaScript files.
It can be an absolute or relative path.
All compiled JavaScript files will be placed in this directory while maintaining the same relative directory structure as the source TypeScript files.
module:
Specifies the module system used in the generated JavaScript code.
Common values include
"CommonJS","AMD","UMD","System","ES6", and"ESNext".The choice of module system affects how TypeScript generates module import/export statements in the output JavaScript files.
esModuleInterop:
Without
esModuleInterop, when importing a CommonJS module with a default export using the syntaximport x from 'module', TypeScript generates code that expects the module to export its default value asmodule.exports.default.With
esModuleInteropenabled, TypeScript generates code that directly accesses the default export, allowing you to use the default import syntax without errors.Without
esModuleInterop, when exporting a value as default in a CommonJS module usingmodule.exports = value, TypeScript generates code that exports the default value asexports.default.With
esModuleInteropenabled, TypeScript generates code that exports the default value directly, allowing seamless consumption of default exports by ES modules.
There are many other properties that you can explore on the official typescript documentation but these are the most commonly used options that everybody should understand to become a good typescript developer.
Iam leaving the tsconfig.json file setup in my recent project so that it could be a good starting point.
{
"compilerOptions": {
"target":"ESNext",
"module":"esnext",
"allowJs":false,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis":true,
"strictFunctionTypes":true,
"strictBindCallApply": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"strictPropertyInitialization":true,
"esModuleInterop": true
}
}


