Lesson 22 of 25
Configuring tsconfig.json and Strictness
What the strict flag actually turns on, the individual checks that make it up, and the other tsconfig options that affect real projects most: target, module, and skipLibCheck.
tsconfig.json was introduced briefly, early in this track, as "the file
that configures the compiler." This lesson is about the options that
actually matter most in a real project — above all, what strict mode is
really doing, since it's the single setting responsible for most of the
safety this whole track has been teaching.
strict is a bundle, not one switch
"strict": true doesn't turn on one check called "strict mode" — it's
shorthand for enabling a whole family of individually-named flags at once.
The two most consequential are worth knowing by name:
- noImplicitAny — an un-annotated parameter or variable the compiler
can't infer a type for becomes an error, instead of silently falling back
to
any. This is what makesfunction greet(name)(no annotation) actually catch missing types, rather than quietly opting that parameter out of checking. - strictNullChecks —
nullandundefinedbecome genuinely distinct from every other type, rather than being silently assignable everywhere. Without it, a function typed to returnstringcould actually returnnullat runtime with no compile error at all, which defeats a large part of the point of typing it in the first place.
The rest of the family, briefly:
- strictFunctionTypes — checks function parameter types more precisely (contravariantly, if you want the formal term) when comparing function types.
- strictBindCallApply — checks the arguments passed to
.call(),.apply(), and.bind()against the function's actual signature. - strictPropertyInitialization — a class property without a definite
assignment (in the constructor or at declaration) is an error, catching a
field that's typed as always present but might actually start as
undefined. - noImplicitThis — an untyped
thisinside a function is an error rather than silentlyany. - alwaysStrict — emits
"use strict"and parses in strict mode, same as plain JavaScript's own strict mode. - useUnknownInCatchVariable — a caught error (
catch (error) {}) is typedunknownrather thanany, which is whyinstanceof Errornarrowing, covered earlier in this track, was necessary in the first place.
{
"compilerOptions": {
"strict": true
}
}Turning individual flags off after enabling strict is possible
("strictNullChecks": false alongside "strict": true), but doing this
piecemeal in a real project quietly reintroduces exactly the bugs strict
exists to prevent — it's rarely the right call outside a specific, deliberate
migration.
target: what JavaScript version comes out
target controls how modern the emitted JavaScript is allowed to be —
newer syntax gets rewritten into older equivalents below the chosen target:
{
"compilerOptions": {
"target": "es2020"
}
}A low target (es5) maximizes compatibility with very old runtimes at the
cost of larger, less efficient output (classes rewritten as functions,
for instance); a modern target (es2020 or later) keeps most contemporary
syntax as-is, assuming a reasonably current runtime. For most projects
targeting current browsers or Node.js, a recent target is the right default.
module and moduleResolution
module controls what module syntax is emitted (commonjs's require, or
ES modules' import/export), and moduleResolution controls how the
compiler looks up what a given import path actually resolves to. Modern
projects using a bundler typically use:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}This tells TypeScript to leave import/export syntax untouched (the
bundler handles the actual module format) and to resolve imports the way
modern bundlers do, rather than following Node.js's older CommonJS
resolution rules.
skipLibCheck: a pragmatic trade-off
skipLibCheck skips type-checking inside .d.ts declaration files —
overwhelmingly, ones from node_modules, not your own source:
{
"compilerOptions": {
"skipLibCheck": true
}
}Without it, tsc type-checks every declaration file pulled in by every
dependency, including ones you have no control over and can't fix if
they're wrong — which is also simply slow on a project with a realistic
number of dependencies. skipLibCheck trades a small amount of theoretical
safety (an actually broken third-party .d.ts might go unnoticed) for
meaningfully faster builds, and is enabled in most real-world projects for
exactly that reason.
A sensible modern baseline
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}esModuleInterop and forceConsistentCasingInFileNames are two further
options worth this default's inclusion: the first smooths over
interoperability between CommonJS and ES module import styles; the second
catches a file imported with inconsistent casing (./Utils vs ./utils),
which works on case-insensitive filesystems (macOS, Windows) but silently
breaks on case-sensitive ones (Linux, and most CI and production servers).
Try it yourself
This playground already runs with strict-equivalent checking. Notice the difference an un-annotated parameter would make.
What to remember
strictbundles several individually-named checks;noImplicitAnyandstrictNullChecksare the two doing the most work, and this whole track assumes both are on.targetcontrols the JavaScript version emitted;module/moduleResolutioncontrol the module syntax and how imports resolve.skipLibCheckskips checking dependency.d.tsfiles, trading a small amount of safety for meaningfully faster builds — standard in most real projects.- Disabling individual strict-family flags after turning
stricton is possible, but usually reintroduces exactly the bugs it exists to catch.
Check yourself
4 questions · pass 3/4 to unlock as const and satisfies
1.What is the practical effect of setting
"strict": truein tsconfig.json?2.What does noImplicitAny specifically catch?
3.What does strictNullChecks change about how null and undefined behave?
4.What does skipLibCheck do, and why do many real projects enable it?
4 left to answer