DEV Community

Adam Smolenski
Adam Smolenski

Posted on

TypeScript. She's crafty, just my Type....Script. Breaking down tsconfig

Ok maybe the Beastie Boys are not the best introduction to TypeScript, but bad jokes make me happy. Errors do not, so I have decided to dive into the settings of tsconfig.json, that could make or break your day. I will be honest, at first I was resistant to TypeScript. I liked Ruby and Python originally but felt way more free in Javascript. But TypeScript saves time in the end. A lot of your testing is done for you in the compiler, the errors make right before you pour your next cup of coffee are highlighted before you refill (no offense CoffeeScript). It really is a handy tool. Also, it seems to be one of the directions we are headed in, so when you start a TypeScript project you need this thing called a tsconfig.json file. This will enable you to manipulate what angers the TS gods, and what you can get away with. With this, let's go through the compiler options... and there are a few, we will separate them into Basic Options, Strict options, Additional Checks, Module Resolution Options, Source Map Options, Experimental and advanced... Why? Because that's how they separate them as you type tsc --init. Clever I know. Here's what generates when you type that:

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /`* Report errors for fallthrough cases in switch statement. */
    // "noUncheckedIndexedAccess": true,      /* Include 'undefined' in index signature results */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}
Enter fullscreen mode Exit fullscreen mode

Basic Options

  • "incremental": boolean
    Already starting with a TS joke, so Incremental is a boolean and it's how we are starting this post... incrementally. For large projects this is a nice thing to have. It will refer to a buildinfo and only compile things that have to be compiled or changed. Essentially it's watching and not running all the compilations over again. So it's efficient use of memory... why wouldn't you want to do this? If it's a small project, this is annoying on first start up because it's kind of slow, there have also been reported bugs with its use (not truly compiling everything), that have been mostly been sorted out. Good to be aware of though.


  • "target" : 'ES3' | 'ES5' | 'ES6' | 'ES2015' | 'ES2016'| 'ES2017' | 'ES2018' | 'ES2019' | 'ES2020' | 'ESNEXT'
    This is probably one of the most widely used compiler options, you want to tell typescript in what version of ECMAscript to compile to. ES3 is the default, probably ill-advised if you're starting a new project. This makes sure you are following the rules of that version as well as compiling in a manner that it will play nicely with other non-typescript files of that generation.

  • "module" : 'none | 'commonjs' | 'amd' | 'system' | 'umd' | 'es2015' | 'es2020' | 'ESNext' If you weren't aware before modules are a new thing to Javascript... go figure you can organize your code better. Each generation of JS has a different method of importing and exporting modules, and this nifty little guy is what tells the compiler how to do it. So you can write modern TS and have it work with older systems by just setting this to their level of specificity. The battle of require vs import is strong.
  • "lib": string[] Ok there are a lot of options here, so I'm going to be lazier than before. Here is where you can specify what Javascript libraries you want. Say you're not working in a browser, you don't need to have "dom" definitions. This will minimize bloat of your application if that is essential to your process. Similar to target, but this is telling your typescript what it wants to know and what it doesn't from certain javascript APIs.
  • "allowJs": boolean Why are we allowing Js? We're working in TS right? Well sometimes you are working with older libraries and don't have the time to convert them, by setting this to true you can still import javascript files into your typescript project. Otherwise watching over your typescript you'll get an error.
  • "checkJs": boolean Working with its friend allowJs, this will throw errors if there is something wrong with compiling the javascript. It type checks functions that are part of your library. If you only want certain .js files compiled you can also put a // @ts-check at the top of them.
  • "jsx": 'preserve' | 'react-native' | 'react' So here's where you get into some fun things when you are doing frontend work. How do you want to compile your JSX? This can change your .tsx file into react where it will compile to a React.createElement and go through the motions there, the preserve mainly does not change the JSX just parses through the types. So here you can either skip babel or continue with babel transpiling.
  • "declaration": boolean If set to true this will generate a .d.ts for exported components so others can integrate those modules.
  • "declarationMap: boolean This will allow text editors to go back and find the declarations to it's original .ts sourcefile. Actually very helpful when troubleshooting or just trying to figure where the types are declared. While learning TypeScript I have used other libraries implementations of this to figure out how I was ruining their work :).
  • "sourceMap": boolean Maps what the heck you did wrong in the compiled js and points you to where it is in your typescript files. Handy when those errors pop up so you can follow it all the way back to its source.
  • "outFile": string In case you are generating one javascript file this is where you would name the destination file in your repo.
  • "outDir": string Similar to the above but preserves file structure and just compiles all the javascript into a mirrored directory.
  • "rootDir": string Root directory of input files... helps your compiler traverse the files and make sure everything is in the right place.
  • "composite": boolean When set to true this will change some default settings, it will set the rootDir to where the tsconfig.json is, you will also have to make sure the patterns in your files line up or you will get some unhappy build errors.
  • "tsBuildInfoFile": string This is where your build file will be referenced, so if you have incremental set to true, this is where it will check for information on what has been created and what it needs to update.
  • "removeComments": boolean Well, just like the variable says, when compiling this will remove any comments you have left in your code.
  • "noEmit": boolean This will not compile output files, normally used if you are using Babel or swc to convert typescript and just have TS locally for a text editor checker.
  • "importHelpers": boolean So the ts library has some functions that make it's compiled code slightly more readable. So if you have our next variable of downlevelIteration on but helpers off, the syntax for-of, spread and a lot of the newer JS tools will get real funky. There was a reason that these weren't in the original JS, it took some time to write them out and make them work, and after looking at what it compiles to, I can't blame them... for hilarities sake the example below.
export function shiftLuckyNumber(arr: number[]) {
  const arr2 = [13, ...arr];
}
Enter fullscreen mode Exit fullscreen mode



becomes


var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
export function shiftLuckyNumber(arr) {
    var arr2 = __spreadArray([13], __read(arr));
}
Enter fullscreen mode Exit fullscreen mode



The helpers are just that top bit being imported from the tslib

  • "downLevelIteration": boolean
    Similar to what I said above, but this provides support for those operations when targeting ECMAscript versions that don't necessarily have these options built in. So it allows the silliness you see above.

  • "isolatedModules": boolean
    So depending if you are using other transpilers. Some only can operate on single files and not understand some dependencies from other files. Setting this to true will give you a warning if when using those transpilers will break. Also if this is set to true, everything TS is reading has to be treated as a module, since that is how it is going to read it. So no global script files.


Strict

Now on to a section where nuns and rulers come to mind... So many rules to follow, and here is where I have been finding most of my errors, and making things more lax depending on what libraries I'm integrating. Setting things to the any type just doesn't feel right.

  • "strict": boolean Well... yeah, this enables all strict type-checking. So everything that follows until our additional checks section would be enabled. You can piecemeal the rest of the options to better suit your needs, especially if you're transporting a JS project into TS
  • "noImplicitAny": boolean So this one is probably the simplest to get around. You have to give it some sort of declaration. You have to provide to typescript some sort of expectations otherwise you will get yelled at
  • "noImplicitThis": boolean Similar to the above but this... hehehe. has to do with class's and constructors when you are declaring this, you have to say what it is.
  • "strictNullChecks": boolean This is actually something I really appreciate, go figure. TS will raise an error if it thinks there is a possibility you may get null or undefined. If that is behavior you want, you can tell the function that hey maybe there's nothing here. It safeguards against a lot of assumptions which leads to more logic and safety. I find this most useful in react when implementing API returns, sometimes you may not have any information, and this will let you know right away.
  • "strictBindCallApply": boolean This option checks function arguments to make sure the parameters given line up with the expected values. So if you are passing a function a string and declare it to be a string, you can't give it a number instead. When regular JS would just try to make everything play happily.
  • "strictPropertyInitialization": boolean When creating classes this will ensure that your constructor will contain the variables that the class expect to be there by default. Just double checks that you make things in the proper way and all variables that are expected to be there live in the right place.
  • "noImplicitThis": boolean You have to tell TS what this is. It can't be implied to be any. Just double checks that if you call this somewhere else that it is following the right syntax whether you are in ES6 or before.
  • "alwaysStrict": boolean Just like the nuns... Basically it adds the "use strict" to all it's emitted files.

Additional Checks

Well these things are slightly more lenient, and helpful, less yelly unless you want that.

  • "noUnusedLocals" boolean Makes sure that you use variables that have been declared.
  • "noUnusedParameters": true Makes sure when you pass in arguments to a function that you are indeed using them.
  • "noImplicitReturns": boolean Ok... so also another one of my favorites. Sometimes when working in Ruby where returns just happen on the last executed line of code, this check makes sure you are returning something and checks that it the type you expect.
  • "noFallthroughCasesInSwitch": boolean Ensures that you have a break or a return when using switch statements, also makes sure you have a default fallback so everything gets evaluated.
  • "noUncheckedIndexedAccess": boolean Enabling this will add undefined to type not specifically declared in the interface but attempted to be accessed through the object.

Module Resolutions Options

  • "moduleResolution": 'node' | 'classic' TS even admits you probably won't ever use classic, but this is a setting that will tell the compiler how to figure out where imports are. Node and classic have different ways of checking for dependencies. Remember modules are relatively new, so node ensures that if it can't find a module specifically it knows to go into node_modules. When importing relative modules, they have similar strategies but it is typically the npm installed or yarn added requirements that may cause it to be confused.
  • "baseUrl": string Where to start looking for non absolute modules, so base relative path. Must use if you are going to use custom paths
  • "paths": {} This can map out dependencies if they differ from the baseUrl, also can provide some shortcuts in case your file tree gets really deep.
  • "rootDirs": string[] This tells the compiler that there can be multiple base directories on compiling, if you are generating css it may have a different relative path, so this ensures that TS will check both places before it gets cranky.
  • "typeRoots": string[] This is where the compiler will look for your d.ts files. Normally TS will look for @types files, but declaring this will overwrite that and have TS check that directory ignoring the @types.
  • "types": string[] Declaring certain libraries in here will only allow what you type in to be referenced globally. In which case you would have to locally import the types you need for certain modules.
  • "allowSyntheticDefaultImports": boolean If false you TS will not generate a default export, you will have to go to the file and specifically declare one.
  • "esModuleInterop": boolean This checks that you are requiring or importing modules appropriately based on their generation. So importing * as bob from 'bob' with work similarly to import importing bob from 'bob, and const bob = require("bob"). Also adds some stability for libraries that specifically require .default.
  • "preserveSymlinks": boolean This will change the behavior of referencing linked files. If it's true it will go to the location of the relative file instead of the location where your link resolves to.
  • "allowUmdGlobalAccess": boolean Exports from UniversalModuleDefinition files are allowed globally. So your bundler can allow access to that module everywhere instead of explicitly having to require it. Libraries like jQuery will be there when you're up and running but you're not necessarily importing it.

Source Map Options

  • "sourceRoot": string Instead of the relative path, you can specify the direct location for typescript files.
  • "mapRoot": string Tell the debugger where to find your map files, all so you can go back to where your error began.
  • "inlineSourceMap": boolean Embeds your mapping into the compiled js file instead of creating a separate file for it.
  • "inlineSources": boolean Setting this with the above configuration will also generate the ts source code at the bottom of the .js file

Experimental options

  • "experimentalDecorators": boolean Decorators are not implemented universally yet which is why they are experimental. It's how you can effect the output of a function with simply putting @ImADecorator() above the function or class. Something from python originally, but a lot of newer libraries are using this. Recently using ORMs with Graphql mikro-orm and typeorm really make life easier with the decorators.
  • "emitDecoratorMetadata": boolean Gives the metadata of what is going on with the decorator, works with the library reflect-metadata. It will give the information from the surrounding method.

Advanced Options

  • "skipLibCheck": boolean This bypasses checking your declaration files for following certain rules. You may want to do this to speed up compilation time. In reality if one type file depends on another it won't ensure the imports are of the specified type. Not skipping Lib check would also make you aware if you are doubly importing somewhere.
  • "forceConsistentCasingInFileNames": boolean If you're reading this, I'm sure you have bounced between languages and frameworks and possibly OSs'. They all have unique naming schemes, snake case, camel case, pascal case. If this is set to false you can find a file ./thisone.ts to be the equivalent to ./ThisOne.ts. Some areas of work are more sensitive than others so turn this off with caution.

Oh my that was a lot. Typescript has a lot of variables to make your life easier or more difficult. In the end it's your friend. As browsers across devices change, TS allows you to write in one standard and export it to different generations that may be friendlier to browsers you never really use.. Not looking at you Internet Explorer... Anyway, it is a challenge sometimes but TS seems like it is here to stay and is also here to help.

Top comments (1)

Collapse
 
quyethn2 profile image
quyethn2

Great <3 thanks for sharing :D