Skip to content

Instantly share code, notes, and snippets.

@ersinakinci
Last active February 17, 2020 12:49
Show Gist options
  • Save ersinakinci/832c444811a8fcc019008152727f5d00 to your computer and use it in GitHub Desktop.
Save ersinakinci/832c444811a8fcc019008152727f5d00 to your computer and use it in GitHub Desktop.
tsconfig.json primer

tsconfig.json primer

Documentation progress

Fundamental

Language compatibility

  • compilerOptions.jsx
  • compilerOptions.lib
  • compilerOptions.target

Intermediate

Language compatibility

  • compilerOptions.downlevelIteration
  • compilerOptions.experimentalDecorators
  • compilerOptions.importHelpers
  • compilerOptions.jsxFactory

Handling JavaScript files

  • compilerOptions.allowJs
  • compilerOptions.checkJs

Advanced

Handling JavaScript files

  • compilerOptions.maxNodeModuleJsDepth

Uncategorized

  • compilerOptions.allowSyntheticDefaultImports
  • compilerOptions.allowUnreachableCode
  • compilerOptions.allowUnusedLabels
  • compilerOptions.alwaysStrict
  • compilerOptions.baseUrl
  • compilerOptions.build
  • compilerOptions.charset
  • compilerOptions.checkJs
  • compilerOptions.composite
  • compilerOptions.declaration
  • compilerOptions.declarationDir
  • compilerOptions.declarationMap
  • compilerOptions.diagnostics
  • compilerOptions.disableSizeLimit
  • compilerOptions.emitBOM
  • compilerOptions.emitDeclarationOnly
  • compilerOptions.emitDecoratorMetadata
  • compilerOptions.esModuleInterop
  • compilerOptions.extendedDiagnostics
  • compilerOptions.forceConsistentCasingInFileNames
  • compilerOptions.incremental
  • compilerOptions.inlineSourceMap
  • compilerOptions.inlineSources
  • compilerOptions.init
  • compilerOptions.isolatedModules
  • compilerOptions.keyofStringsOnly
  • compilerOptions.listEmittedFiles
  • compilerOptions.listFiles
  • compilerOptions.locale
  • compilerOptions.mapRoot
  • compilerOptions.module
  • compilerOptions.moduleResolution
  • compilerOptions.newLine
  • compilerOptions.noEmit
  • compilerOptions.noEmitHelpers
  • compilerOptions.noEmitOnError
  • compilerOptions.noErrorTruncation
  • compilerOptions.noFallthroughCasesInSwitch
  • compilerOptions.noImplicitAny
  • compilerOptions.noImplicitReturns
  • compilerOptions.noImplicitThis
  • compilerOptions.noImplicitUseStrict
  • compilerOptions.noLib
  • compilerOptions.noResolve
  • compilerOptions.noStrictGenericChecks
  • compilerOptions.noUnusedLocals
  • compilerOptions.noUnusedParameters
  • compilerOptions.outDir
  • compilerOptions.outFile
  • compilerOptions.paths
  • compilerOptions.preserveConstEnums
  • compilerOptions.preserveSymlinks
  • compilerOptions.preserveWatchOutput
  • compilerOptions.pretty
  • compilerOptions.project
  • compilerOptions.reactNamespace
  • compilerOptions.removeComments
  • compilerOptions.resolveJsonModule
  • compilerOptions.rootDir
  • compilerOptions.rootDirs
  • compilerOptions.showConfig
  • compilerOptions.skipLibCheck
  • compilerOptions.sourceMap
  • compilerOptions.sourceRoot
  • compilerOptions.strict
  • compilerOptions.strictBindCallApply
  • compilerOptions.strictFunctionTypes
  • compilerOptions.strictPropertyInitialization
  • compilerOptions.strictNullChecks
  • compilerOptions.strictExcessPropertyErrors
  • compilerOptions.suppressImplicitAnyIndexErrors
  • compilerOptions.traceResolution
  • compilerOptions.tsBuildInfoFile
  • compilerOptions.types
  • compilerOptions.typeRoots
  • compilerOptions.watch

What is it

  • Configuration file for tsc, the TypeScript compiler.
  • Indirectly used by tslint and therefore editor plugins that rely on TSLint (i.e., your IntelliSense editor errors may be due to an incorrectly configured tsconfig.json).

Location

  • By default, tsc searches for a file named tsconfig.json in the current working directory.
    • If it can't find it there, it traverses up the directory hierarchy until it finds one.
    • If it doesn't find one, it falls back to a default configuration.
  • Multiple tsconfig.json files in sibling/nested folders of a project shouldn't cause problems, tsc will just follow the algorithm above.
  • Editors may, however, get confused by multiple tsconfig.json files, or at least interact with them in non-intuitive ways. For example, when opening the root directory of a monorepo, VS Code will only read the top-level tsconfig.json file regardless of where the closest config file is located relative to the location of your currently open file in the directory hierarchy.

Common workflows

  • .tsx? files → tsc.js files
    • By default, tsc compiles whole projects rather than individual files. Running tsc on the command line will compile every .ts file (and optionally .tsx files) in the current directory and every subdirectory. Compiled files will be emitted to wherever has been configured in tsconfig.json.
  • .tsx? files → webpacktsc (via ts-loader) → .js files
    • For Node >= 8.10 targets, can use target: 'ES2016' in compilerOptions
    • For browser targets, can use target and lib options while manually providing polyfills if necessary (see below), or can chain ts-loader with babel-loader and let Babel handle polyfills
    • Note: tsc never introduces polyfills but can introduce compatibility helpers for supproting certain syntaxes (see below)
  • .tsx? files → webpack -> tsc (via ts-loader) → babel (via babel-loader) → .js files
    • Probably not necessary for Node targets
    • For browser targets, can use target: 'ES2018' and let Babel handle polyfills and down-compiling
  • .tsx? files → webpackbabel (via babel-loader) → .js files
    • Uses Babel rather than tsc for compilation.
    • Pros: simpler toolchain (one less loader and compiler), supposedly faster especially in development when rapidly iterating because it skips type checking
    • Cons: skips type checking, doesn't support const enum (inconvenient) or type metadata (breaks major TypeScript ecosystem packages like TypeORM)
  • .tsx? files → tslint (via CLI or editor) → tsc --noEmit → errors (no files emitted)

Language compatibility vs. library support vs. syntax support

When configuring which language your TypeScript code compiles into, it's important to understand how compatibility is modeled.

Language compatibility

"Language compatibility" is a broad concept indicating whether some code conforms to a particular version of a given language. For example, if we say that your code is compatible with ES5, we might mean that it doesn't use ES2015+ features such as promises. We might alternatively mean that your source code uses ES2015+ features but is compiled into code that can run in environments that only support ES5 by using polyfills or changing the syntax. A more precise way to talk about compatibility is to talk in terms of library and syntax support.

Library support

This concept refers to whether the compiler allows certain non-syntactical features of a language. For example, in order to use promises, we need support for the ES2015.Promises API, which is normally provided by the execution environment as a library. Features that need library support can be thought of as those that must have a polyfill to be backward compatibile because they rely on new, implementation-specific functions.

You must specify what language libraries need to be supported when using TypeScript in order to avoid compiler errors, but since tsc doesn't inject polyfills it can't down-compile source code that relies on them. If you want to use a feature that requires library support you can take one of three approaches:

  1. Pick a language version that's completely supported by your target execution environment (e.g., ES5 is a safe bet for browsers) and use compilerOptions.target by itself to set the target language. This configuration effectively tells the compiler both that you're writing in that language and that you want it to compile into the same language. The compiler will raise an error if you use an unsupported feature, therefore you must limit yourself to whichever features are supported by the target language.
  2. Pick a language version that's completely supported by your target execution environment, use compilerOptions.target to set the target language, and also use compilerOptions.lib to indicate the language libraries that will be available at runtime regardless of the target language. Compilation will succeed, but if you use features supported by compilerOptions.lib but not supported by compilerOptions.target, you must introduce polyfills manually to make sure that the compiled code runs correctly.
  3. Pick a language version that supports all of the features you want irrespective of whether your target environment supports them (e.g., ES2018 is widely unsupported but has the very useful object spread operator) and use compilerOptions.target by itself to set the target language. Your compiled code won't run correctly by itself, but you can use a tool like Babel to further down-compile your emitted code and inject the necessary polyfills rather than doing it manually.

Syntax support

This concept refers to being able to use purely syntactical features of a language. For example, in order to use classes, we would need compatibility with the ES2015 language since that's the standard that introduced that features. However, because classes are purely syntactical, the compiler can down-compile them without introducing a full-blown polyfill.

You can safely use features that only require syntax support by setting your target language with compilerOptions.target without any further work. Regardless of your targeted language version because tsc will always down-compile when necessary.

TypeScript down-compiles syntax features by injecting language compatibility helper functions into your compiled code, which aren't exactly full-blown polyfills but still add some complexity. This behavior is rarely desirable because the injected code is duplicated anywhere that the compiler had to down-compile. You can therefore use the compilerOptions.importHelpers option to cause tsc to link to the NPM package tslib using injected import's (or require's, if your target is commonjs). You can either execute the compiled code as-is if your environment supports tslib, or you can bundle the dependency using Webpack, which will still result in a smaller bundle size.

Examples

(Reminder: ES5 → ES6/ES2015 → ES2016 → ES2017 → ES2018 → ESNext. ES6 is the old name for ES2015, ESNext is a moving target that represents the bleeding edge of the ECMAScript standard.)

target: 'ES5'

  • Your code will be compiled into ES5
  • ES5 library features ARE supported by the compiler, WON'T raise errors and WON'T be down-compiled
  • ES5 syntax features ARE supported by the compiler, WON'T raise errors and WON'T be down-compiled
  • ES2015+ library features AREN'T supported by the compiler, WILL raise errors and WON'T be down-compiled
  • ES2015+ syntax features ARE supported by the compiler, WON'T raise errors and WILL be down-compiled

target: 'ES2015'

  • Your code will be compiled into ES2015
  • ES2015 library features ARE supported by the compiler, WON'T raise errors and WON'T be down-compiled
  • ES2015 syntax features ARE supported by the compiler, WON'T raise errors and WON'T be down-compiled
  • ES2016+ library features AREN'T supported by the compiler, WILL raise errors and WON'T be down-compiled
  • ES2016+ syntax features ARE supported by the compiler, WON'T raise errors and WILL be down-compiled

target: 'ES5', lib: 'ES2015'

  • Your code will be compiled into ES5
  • ES2015 library features ARE supported by the compiler, WON'T raise errors and WON'T be down-compiled
    • Because ES2015 features won't be down-compiled and will be included as-is, it's your responsibility to introduce the appropriate polyfills to ensure that your compiled code runs in your target environment if necessary
  • ES2015 syntax features ARE supported by the compiler, WON'T raise errors and WILL be down-compiled
  • ES2016+ library features AREN'T supported by the compiler, WILL raise errors and WON'T be down-compiled
  • ES2016+ syntax features ARE supported by the compiler, WON'T raise errors and WILL be down-compiled

One-off language feature compatibility

TypeScript also supports a few language features that don't fit neatly within the scope of compilerOptions.target or compilerOptions.lib through one-off configs.

Decorators support

compilerOptions.experimentalDecorators enables support for decorators (i.e., @autobind).

Decorators are used widely throughout the JavaScript ecosystem. Despite this fact, they aren't yet part of any official ECMAScript standard (as of writing, they are at stage 2 or "draft", see https://github.com/tc39/proposal-decorators). Apps using decorators typically rely on Babel (see https://babeljs.io/docs/en/babel-plugin-proposal-decorators).

Decorator support isn't controlled by compilerOptions.target even though it can be thought of as a purely syntactical feature because it isn't actually part of any language. Yet not including support would be highly impractical given how commonly they're used (again, thanks to Babel). Therefore, tsc presumably supports decorators using the compilerOptions.experimentalDecorators config as a pragmatic compromise.

Full ES3/ES5 support for iterators/for...of loops

compilerOptions.downlevelIteration enables full support for the ES2015+ iteration protocol (i.e., for...of loops) when targeting ES5 and lower.

for...of by itself is a syntax feature that can be rewritten using index-based loops, hence it's already supported using compilerOptions.target. However, ES2015 is smarter about how for...of iterates depending on the iterable type. For example, strings in ES2015 are iterated over their code points rather than their characters. compilerOptions.downlevelIteration allows for using this more intelligent behavior in ES5 and below. Without it, targeting ES5 or earlier versions will use the standard "dumb" behavior, causing for...of loops to incorrectly iterate over things like strings.

Since this feature only affects ES5 and below, and because the correct, spec-compliant ES5 behavior is actually the undesirable one, it's not controlled by compilerOptions.target. Also, this behavior isn't implemented as part of an ES2015 library, so it's not controlled by compilerOptions.lib. See https://mariusschulz.com/blog/typescript-2-3-downlevel-iteration-for-es3-es5 for more details.

JSX compatibility

TypeScript code mixed with JSX is saved in .tsx files and can by handled by tsc by using the compilerOptions.jsx setting.

In general, you'll want to set the config to preserve, which causes tsc to pass through any JSX into the compiled files, which are emitted as .jsx files. These won't run on their own, but Babel will compile the .jsx files into .js, which will then be included as part of your bundle if using Webpack. You'll want to set the config to react or react-native if you're not using Babel to convert the JSX into React API (e.g., React.createElement) calls and compile the code into .js files. See the helpful table here: https://www.typescriptlang.org/docs/handbook/jsx.html#basic-usage

A notable edge case is if you're using server-side rendering, in which case you may not be using Babel and would want to set compilerOptions.jsx to React so that the compiled code can run in your Node environment.

Finally, if you'd like tsc to transform your JSX into API calls without using Babel but you're not using React or React Native (e.g., you're using Preact), compilerOptions.jsxFactory allows you to control the function that's used for the transformation. This defaults to React.createElement, but you could set it to h for Preact, for example. Two things to note when setting this option:

  • If you set this option, you must also counterintuitively set compilerOptions.jsx to 'react' for it to have any effect. Think of compilerOptions.jsx = 'react' as strictly meaning "transform my JSX into API calls, by default React but potentially something else" rather than literally "transform my JSX into React API calls."
  • It's also possible to set this option on a per-file basis using the /** @jsx */ pragma at the top of individual files (e.g., /** @jsx h */ for Preact). This approach can be helpful in situations where you're mixing multiple JSX-compatible libraries since compilerOptions.jsxFactory specifies the same function for all JSX transformations across all files.

See https://mariusschulz.com/blog/typescript-2-8-per-file-jsx-factories for more details.

Handling JavaScript files

The TypeScript compiler by default only handles .ts, .tsx and .d.ts (i.e., TypeScript) files. However, since projects often have a mixture of TypeScript and JavaScript, tsconfig.json has several options for processing .js and .jsx files, as well. Some of these options can help replace the need for Babel, leading to a simplified toolchain, or they can be enabled as part of an incremental migration strategy from JavaScript to TypeScript.

Processing JavaScript

compilerOptions.allowJs tells the compiler to process .js and .jsx files. The compiler will apply whatever transformations (e.g., syntax compatibility helpers, JSX to React API calls) that it would normally apply to .ts and .tsx files. If these transformations are enough to run your code on your target execution environment, you may be able to dispense with Babel altogether if you're using Babel just for your JavaScript files. You can also take the approach of enabling this option while you gradually rewrite JavaScript in TypeScript, helping ease the migration.

Another benefit of enabling compilerOptions.allowJs is that downstream utilities that rely on tsc will process your JavaScript files. This behavior is especially useful for getting tslint to lint your JavaScript files. If you're using eslint for your JavaScript files, you could potentially get rid of it and just use tslint for everything, depending on your needs and your configuration.

Type checking JavaScript

"Type checking JavaScript" may sound like an oxymoron at first blush--after all, isn't that language's lack of types the reason why TypeScript exists?

Nevertheless, tsconfig's compilerOptions.checkJs option enables partial type safety in vanilla JavaScript. When set to true, TypeScript will use a variety of methods to infer type information, most of all from JSDoc annotations. This setting can be helpful in all situations involving JavaScript files, but it really shines for code bases that have a complete set of JSDoc comments with documentation for parameter types, etc.

JavaScript type checking is disabled by default. You can selectively enable type checking while leaving compilerOptions.checkJs unset or false by adding // @ts-check to whichever .js and .jsx files you want checked. Conversely, if you've enabled compilerOptions.checkJs, you can add the // @ts-nocheck to selectively disable type checking when there are too many false positives. (NB: // @ts-check and // @ts-nocheck must be at the top of the file and applies to the whole file. There currently isn't a way to enable or disable checking on particular lines. Also, // @ts-nocheck only works on JavaScript files and has no effect on TypeScript files.)

See https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files for details on how compilerOptions.checkJs works.

Reference

General language compatibility

compilerOptions.target

  • Controls which language your code gets compiled into AND syntax support
  • DOESN'T introduce polyfills
  • DOES introduce language compatibility helpers when down-compiling certain syntactical features (e.g., classes, async/await)
  • Recommendation
    • If you're going to further compile your emitted code with Babel, set to whichever ECMAScript version supports all the features that you're using (e.g., ES2018) regardless of compatibility with your runtime environment
    • If you're going to run the emitted code without further compilation, set to whichever ECMAScript version is supported by your runtime environment (e.g., ES2016 for Node >=8.10, ES2015 or ES5 for web browsers)

"I want to compile code that's written in X version of JavaScript but I may also use certain syntaxes that are only available in later versions."

compilerOptions.lib

  • Controls library support (DOESN'T control which language your code gets compiled into NOR syntax support)
  • DOESN'T introduce polyfills
  • DOESN'T introduce language compatibility helpers (that's the responsibility of compilerOptions.target, see above)
  • Can be combined with compilerOptions.target to allow the compiler to compile code with advanced language features without errors, even if the target language version doesn't support those features
  • Recommendation
    • If you're going to further compile your emitted code with Babel, don't set this option at all (the default is to set to whatever version is set by compilerOptions.target). Just make sure to configure Babel to inject any necessary polyfills.
    • If you're going to run the emitted code without further compilation, set to whichever ECMAScript version or library subset supports the features you use, then manually include those polyfills in your source code.

"I want to compile code that's written in X version of JavaScript and I don't care whether the target version supports all the features that I'm using because I will make sure that those features are supported at runtime (e.g., with polyfills that I introduce manually or through Babel)."

compilerOptions.importHelpers

  • Controls whether language compatibility helpers introduced when down-compiling syntax features are inlined in the emitted code or whether they're imported at runtime
  • Recommendation
    • If you're going to bundle your code using a tool like Webpack or if you're going to run in an environment where you can import/require packages (e.g., Node), set this to true since inlining will lead to duplicated helper code wherever the compiler had to down-compile. This case is probably the most common one.
    • If you're going to run your code without bundling and in an environment that doesn't have access to tslib, then set this to false.

"I want to ensure that any injected helper code isn't duplicated by linking to an external library, tslib, at runtime or when I bundle my code."

or

"I want to ensure that any injected helper code doesn't ever depend on an external library."

One-off language feature compatibility

compilerOptions.experimentalDecorators

  • Controls whether decorator syntax (e.g., @autobind) is supported
  • Recommendation
    • Set to true since decorators are used very frequently in JS.
    • Note that as of writing (April 23rd, 2019, there are differences between the current ECMAScript draft standard for decorators and TypeScript's implementation. For most use cases, however, the specs are compatible.

compilerOptions.downlevelIteration

  • Controls whether code compiled with an ES3/ES5 target uses the ES2015 and later iterable protocol/for...of loop behavior (e.g., when iterating over strings in a for...of loop, does the code iterate over code points rather than characters?)
  • Recommendation
    • Set to true since the ES3/ES5 behavior is generally considered broken.
    • Only set to false if you need to exactly match the original ES3/ES5 behavior.
    • May not make any difference even if you set this to false if you run your compiled code in a recent environment that implements the ES2015 iterable protocol since the for...of syntax is identical.

compilerOptions.jsx

  • Controls whether JSX code is compiled into React/React Native API calls or whether it's preserved as raw JSX; also, controls the extension of emitted files (.tsx.js vs. .tsx.jsx)
  • Recommendation
    • If you're using Babel, set to preserve to have Babel handle conversion into React API calls and .js. This is probably the most common case.
    • If you're not using Babel (e.g., many server-side rendering setups), use react or react-native.

"I want to make sure any JSX code is passed through by tsc for later processing by Babel or another tool."

or

"I want tsc to take care of transforming JSX code into API calls, by default for the React API using React.createElement, but potentially using a different function call (e.g., h for Preact) if I also set compilerOptions.jsxFactory."

compilerOptions.jsxFactory

  • Controls what function call tsc uses to replace JSX code with. Defaults to React.createElement but can be replaced with anything (e.g., h for Preact).
  • Only has an effect if you set compilerOptions.jsx to 'react'.
  • Recommendation
    • Only set if you're writing JSX for a library other than React/React Native and you want tsc to handle your JSX transforms (e.g., you're not using Babel to transform JSX into executable code).
    • You can also specify a different function call on a per-file basis using the /** @jsx */ pragma. This approach can be helpful in projects that mix different JSX-consuming libraries (e.g., if you're using React and Preact) since setting compilerOptions.jsxFactory will apply the same function call to all transformations across all files.
    • See https://mariusschulz.com/blog/typescript-2-8-per-file-jsx-factories for more details.

"I want tsc rather than Babel or another tool to handle my JSX transformations and I'm not using React or React Native."

Handling JavaScript files

compilerOptions.allowJs

  • Controls whether .js and .jsx files are processed by tsc.
  • Any transformations that would normally be applied to .ts or .tsx files (e.g., syntax compatibility helpers, JSX-to-React API calls) are applied.
  • Recommendation
    • Set to true by default, especially if you'd like to standardize your toolchain on just using tsc and/or tslint (i.e., removing Babel and/or eslint).
    • Consider setting to false if you have complicated Babel or eslint configurations and compiling your JavaScript files with tsc may raise errors that contradict your settings elsewhere.

compilerOptions.checkJs

  • Controls whether tsc attempts limited type checking on JavaScript files by inferring types, especially from JSDoc annotations.
  • See https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files for more details on how tsc's JavaScript type checking works.
  • Recommendation
    • Set to true by default, especially if you have complete JSDoc type annotations, and add // @ts-nocheck to the top of problematic JavaScript files where type checking seems to result in false positives.
    • Alternatively, set to false or leave unset and add // @ts-check to JavaScript files that you want type checked as part of an iterative migration strategy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment