tsconfig.json demystified (Part III)
By: Alex Tzinov
Tags: JavaScript, Web Development, Typescript, Front End Development, Nodejs

We dove into modules in Part 1 and explored the various module systems — AMD, CommonJS, the new ES6 system, the CommonJS + AMD hybrid called UMD. One “system” still used in the browser that must also be included in this exploration is the old fashion, pre-module way: attaching things on global by importing <script> tags that execute JS code that populates the global scope. A JS file that only has const fancyString = 'fancy' will create a global variable called fancyString that is accessible across the whole app. This kind of JS file isn’t a module (has no exports/imports). It is simply a script that gets invoked in the global namespace.

What happens when we include the <script> tag for the JQuery CDN?

We get a new $ property on our global/window scope representing JQuery. No modules, no imports, no encapsulation. It’s “exported” for usage throughout our app by virtue of being on the global scope that everything has access to.

the allowUmdGlobalAccess flag allows you to use variables that exist on the global scope instead of explicitly using them by importing them.

“An example use case for this flag would be a web project where you know the particular library (like jQuery or Lodash) will always be available at runtime, but you can’t access it with an import.”

baseUrl:

Params <path>

TL;DR Set the base path from which to import files across your project using absolute paths as opposed to every import being relative to the file it’s being used in

When To Use: When you want to shorten your import paths and not have to calculate levels of directories in projects with deep directory trees.

Instead of needing to calculate the number of relative paths you need to traverse up every time you want to import some commonly used module:

‘../utils’ from some component,

‘./utils’ from a container,

‘../../../../../../utils’ from some super nested component

you can set a baseUrl equal to the root of your source (ie: app/src ) and then import everything relative to that ( import ‘utils/’ ). Important: for this to work, you must use non-absolute paths (any path starting with ./ is considered an absolute path and thus will not resolve relative to the baseUrl

moduleResolution:

Params (node | classic)

TL;DR Which resolution strategy to use when looking for the location of imported files

When To Use: When you have a reason to deviate from what’s now the standard behavior of using the node resolution system. Safe to leave as default.

The difference between these two module resolution systems (ie: different sequence of directories to walk up when searching for a modules location) is heavily documented here.

A super effective tool in investigating this behavior yourself is to use the --traceResolution option when running tsc as it will show you an exact trace of the directories/files that TypeScript checks when resolving the location of an imported module.

paths:

Params (true | false)

TL;DR A map of often accessed directories and corresponding aliases to simplify import paths

When To Use: When you have frequently accessed sub-directories under baseUrl (eg: utils) that you want to create an alias for to shorten import paths.

This is closely related to baseUrl . If you have a bunch of frequently used services in a directory structure such as

app/src/utils/tools/services/

you might find yourself writing imports like this:

import {fetchUsers} from ‘utils/tools/services/users’ ,

import {fetchItems} from 'utils/tools/services/items' ,

import {sanitizeRequest} from 'utils/tools/services/utils' ,

Paths lets you set custom prefixes so that you can tell TypeScript where to look in order to resolve imports matching your prefix`

// sample paths setting in a tsconfig.json

paths : { "services/*" : ["utils/tools/services/*"] }

allowing you to rewrite the above imports as

import {fetchUsers} from ‘services/users’ ,

import {fetchItems} from 'services/items' ,

import {sanitizeRequest} from 'services/utils' ,

preserveSymlinks:

Params (true | false)

TL;DR Replicate the behavior of NodeJS when dealing with Symlinks

When To Use: When you want to preserve symlinks.

This is best documented on NodeJS’s documentation which even the official TS docs refer to as well. See more explicit examples there.

rootDirs:

Params list of <paths>

TL;DR Specifies a list of folders (“roots”) whose contents are expected to be merged at runtime so that TypeScript can allow relative imports across these “virtual” directories as if they were merged together in a single directory. When To Use: When you expect files that at compile time are in separate directories to end up in the same directory at run-time, and you need to tell the TypeScript compiler about this so it can allow you to import across these directory boundaries.

This option is best explained with a straightforward example. Let’s say we have a src/utilities/math/equations directory that contains some custom math equations that we are actively developing and iterating on. The equations in src/utilities/math/equations happen to rely on some generated equations that live in src/generated/math/equations . We don’t want the generated code getting dumped in with our custom code, so we keep them in separately distinct directories.

src/utilities/math/equations/myCustomEquation.ts

src/generated/math/equations/genEq.ts

Now, let’s say that as part of our build process (Webpack/Babel/Rollup/etc), we plan on actually merging all files under both /equations folders into a single directory called /allEquations so that during runtime they all live in the same directory. This allows imports to be localized ( ./ ) and rootDirs allows you to specify this relationship. Now myCustomEquation.ts can import ./genEq.ts as if the two files lived in the same directory. More examples of how this can be used to implemented efficient and robust internationalization can be found here.

typeRoots:

Params (true | false)

TL;DR Sets the exact directories from which type declarations will be imported from

When To Use: When you want to be explicit about where tsc will pull type declarations from.

“If typeRoots is specified, only packages under typeRoots will be included.” This is different than the default behavior of including all types within node_modules that are under @types/. You might set this if you have .d.ts files in other parts of your project that you wish to import ( node_modules/custom_lib/types/ , src/generated_types , etc)

types:

Params (true | false)

TL;DR Sets the exact set of types that will be used

When To Use: When you want to be explicit about the types that your project includes.