We use cookies to enhance your experience on the site
CodeWorlds

TypeScript Compiler (tsc)

Introduction: The InGen laboratory

Welcome to the InGen genetic laboratory! Just as the park's scientists transform ancient DNA sequences into living dinosaurs, the TypeScript compiler transforms TypeScript code into standard JavaScript that can run in a browser or Node.js environment.

Dr. Wu, the park's chief geneticist, often says: "We give life to code from millions of years ago." Similarly, the TypeScript compiler lets us write code in a modern language that is then transformed into a more "primitive" form of JavaScript, understandable by all environments.

What is the TypeScript compiler?

The TypeScript compiler (usually called tsc - TypeScript Compiler) is a tool that analyzes your TypeScript code, checks types, and then generates corresponding JavaScript code. It is a bridge between the world of statically typed TypeScript and dynamically typed JavaScript.

In Jurassic Park terminology, we can say that:

  • TypeScript code is like isolated DNA sequences of a dinosaur
  • The compilation process is like the cloning procedure
  • The generated JavaScript code is the living dinosaur, ready to be released into the park

Basics of using the tsc compiler

Installing the compiler

Before we start genetic experiments, we need the right equipment:

1# Global installation (gives access to the tsc command system-wide)
2npm install -g typescript
3
4# Local installation (only within the project)
5npm install --save-dev typescript

Compiling a single file

Compiling a single TypeScript file is like creating a single dinosaur in the laboratory:

1# Syntax: tsc [filename].ts
2tsc security-system.ts

After running this command, a JavaScript file with the same name will be created:

security-system.js
.

Compiling an entire project

In a real Jurassic Park we don't create individual dinosaurs - we build an entire ecosystem. Similarly, in larger projects we compile many files at once:

1# Compile the entire project according to tsconfig.json
2tsc
3
4# Compile with watch mode - the compiler will watch for changes and recompile automatically
5tsc --watch

The compilation process

The TypeScript compilation process proceeds in several steps, similar to the dinosaur creation process in the laboratory:

  1. Scanning - the compiler analyzes all source files and their dependencies
  2. Parsing - transforms source code into an Abstract Syntax Tree (AST)
  3. Type checking - verifies that types are consistent and issues warnings/errors
  4. Transformations - transforms the AST into appropriate JavaScript structures
  5. Emission - generates JavaScript files, source maps, and type declaration files

This process can be compared to the stages of creating a dinosaur:

  1. Scanning is collecting DNA samples from various sources
  2. Parsing is DNA sequencing
  3. Type checking is verifying that the sequences are correct and compatible
  4. Transformations are genetic modifications to make the DNA work in a modern environment
  5. Emission is placing the finished genetic code in a dinosaur egg

Most important command-line options

The TypeScript compiler offers many command-line options that allow for precise control of the compilation process:

Options for output file types

1# Specifying the output directory
2tsc --outDir ./dist
3
4# Generating type declaration files (.d.ts)
5tsc --declaration
6
7# Generating source maps (.js.map) for easier debugging
8tsc --sourceMap
9
10# Merging multiple output files into one (deprecated for ES modules)
11tsc --outFile ./dist/bundle.js

Options controlling the compilation process

1# Compile without emitting files (type checking only)
2tsc --noEmit
3
4# Compile despite errors
5tsc --noEmitOnError false
6
7# Enable strict mode
8tsc --strict
9
10# Compile in watch mode (automatic recompilation on changes)
11tsc --watch

Options affecting generated code

1# Specifying the target JavaScript version (ES5, ES6, etc.)
2tsc --target ES2015
3
4# Specifying the module system (commonjs, amd, system, esm, etc.)
5tsc --module commonjs
6
7# Preserving directory structure
8tsc --rootDir ./src

Diagnostic options

1# Displaying detailed information about the compilation process
2tsc --verbose
3
4# Listing all files that are part of the compilation
5tsc --listFiles
6
7# Displaying the resulting configuration
8tsc --showConfig

How does TypeScript compile different code constructs?

Let's see how the compiler transforms specific TypeScript constructs to JavaScript:

1. Interfaces and types - disappear during compilation

1// TypeScript code (security-types.ts)
2interface Employee {
3  id: number;
4  name: string;
5  role: string;
6  accessLevel: number;
7}
8
9type AccessLevel = 1 | 2 | 3 | 4 | 5;
10
11function checkAccess(employee: Employee, requiredLevel: AccessLevel): boolean {
12  return employee.accessLevel >= requiredLevel;
13}
1// Compiled JavaScript (security-types.js)
2function checkAccess(employee, requiredLevel) {
3  return employee.accessLevel >= requiredLevel;
4}

Interfaces and types are used only during type checking and are completely removed from JavaScript code. It's like dinosaur DNA - important during creation, but invisible in the final creature.

2. Type annotations - also removed

1// TypeScript code (employee.ts)
2function createEmployee(name: string, role: string, accessLevel: number): Employee {
3  return {
4    id: generateId(),
5    name,
6    role,
7    accessLevel
8  };
9}
10
11let newEmployee: Employee;
1// Compiled JavaScript (employee.js)
2function createEmployee(name, role, accessLevel) {
3  return {
4    id: generateId(),
5    name,
6    role,
7    accessLevel
8  };
9}
10
11let newEmployee;

Type annotations disappear, but the code structure remains the same. It's like a genetic map used to create a dinosaur, but not transferred to the final creature.

3. Advanced TypeScript features that require transformation

1// TypeScript code (tracking-system.ts)
2// Using optional chaining and nullish coalescing operator
3function getLocationName(dinosaur?: Dinosaur) {
4  return dinosaur?.currentLocation?.name ?? "Unknown Location";
5}
6
7// Using rest parameters and destructuring
8function analyzeDinosaurs(...dinosaurs: Dinosaur[]) {
9  const [mostDangerous, ...others] = dinosaurs.sort((a, b) => b.dangerLevel - a.dangerLevel);
10  return { mostDangerous, others };
11}

Compilation may look different depending on the target ES version. For ES5 it might look like this:

1// Compiled JavaScript (tracking-system.js) for ES5
2function getLocationName(dinosaur) {
3  var _a, _b;
4  return (_b = (_a = dinosaur === null || dinosaur === void 0 ? void 0 : dinosaur.currentLocation) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "Unknown Location";
5}
6
7function analyzeDinosaurs() {
8  var dinosaurs = [];
9  for (var _i = 0; _i < arguments.length; _i++) {
10    dinosaurs[_i] = arguments[_i];
11  }
12  var sortedDinosaurs = dinosaurs.sort(function (a, b) { return b.dangerLevel - a.dangerLevel; });
13  var mostDangerous = sortedDinosaurs[0], others = sortedDinosaurs.slice(1);
14  return { mostDangerous: mostDangerous, others: others };
15}

Here the compiler had to do much more work to transform modern JavaScript features into ES5-compatible code. It's like adapting "primitive" genetic code to work in a modern environment.

4. TypeScript classes

1// TypeScript code (dinosaur.ts)
2class Dinosaur {
3  private _healthStatus: number = 100;
4
5  constructor(
6    public readonly species: string,
7    public age: number,
8    private readonly dangerLevel: number
9  ) {}
10
11  get health(): number {
12    return this._healthStatus;
13  }
14
15  feed(amount: number): void {
16    this._healthStatus = Math.min(100, this._healthStatus + amount);
17    console.log(`${this.species} has been fed. New health: ${this._healthStatus}%`);
18  }
19}
1// Compiled JavaScript (dinosaur.js) for ES2015
2class Dinosaur {
3  constructor(species, age, dangerLevel) {
4    this.species = species;
5    this.age = age;
6    this.dangerLevel = dangerLevel;
7    this._healthStatus = 100;
8  }
9
10  get health() {
11    return this._healthStatus;
12  }
13
14  feed(amount) {
15    this._healthStatus = Math.min(100, this._healthStatus + amount);
16    console.log(`${this.species} has been fed. New health: ${this._healthStatus}%`);
17  }
18}

For ES5, classes will be transformed into constructor functions and prototypes.

Type declaration files (.d.ts)

In the InGen laboratory, detailed documentation is kept for all created species. In the TypeScript world, the equivalent of such documentation is type declaration files (

.d.ts
).

These files contain only type information, without implementation. They are used by IDEs and the compiler to provide code hints and type checking.

1// dinosaur.d.ts - type declaration file for the Dinosaur class
2declare class Dinosaur {
3  readonly species: string;
4  age: number;
5  private readonly dangerLevel: number;
6  private _healthStatus: number;
7
8  constructor(species: string, age: number, dangerLevel: number);
9
10  get health(): number;
11  feed(amount: number): void;
12}

To generate type declaration files for your project, you can use the

--declaration
option:

1tsc --declaration

Source maps

Debugging dinosaurs can be complicated - we don't always know which DNA sequence corresponds to a given behavior. Similarly, debugging compiled JavaScript code can be difficult without source maps.

Source maps are

.
js.map` files that allow the browser or Node.js environment to map compiled JavaScript code back to the original TypeScript code. This allows you to debug TypeScript code directly, even when JavaScript is running.

To generate source maps, use the

--sourceMap
option:

1tsc --sourceMap

Projects (tsconfig.json)

In a large park full of different dinosaur species, we need detailed plans and specifications. Similarly, in larger TypeScript projects we use the

tsconfig.json
file to configure the compiler.

This file allows you to set all compiler options in one place, which is much more convenient than passing them every time on the command line.

1# Creating a basic tsconfig.json file
2tsc --init
3
4# Compiling the project according to the configuration
5tsc

Suppressing errors and gradual migration

Dr. Wu sometimes has to ignore certain genetic incompatibilities so that a dinosaur can survive. Similarly, when migrating an existing JavaScript project to TypeScript, we may need to temporarily ignore some type errors.

Ignoring errors in specific lines

1// @ts-ignore
2element.style.background = 'red'; // TypeScript would report an error, but we ignore it
3
4// @ts-expect-error
5console.log(dinosaur.location); // We document that we expect an error here

Temporary use of the
any
type

1function legacyFunction(data: any) {
2  // We temporarily use 'any', but plan to fix this later
3  return data.process();
4}

Gradually enabling type checking

We can also gradually enable type checking throughout the project, starting with less strict settings:

1// tsconfig.json with less strict settings
2{
3  "compilerOptions": {
4    "allowJs": true,         // Allows compiling .js files
5    "checkJs": false,        // Doesn't check types in .js files
6    "noImplicitAny": false,  // Allows implicit 'any' type
7    "strictNullChecks": false // Doesn't require checking null/undefined
8  }
9}

Over time, as the project becomes more "tamed", we can gradually increase the strictness of type checking.

Common compiler problems and their solutions

1. TypeScript version conflict

Problem: A global TypeScript installation may conflict with the project's local version.

Solution: Use the locally installed compiler via npx:

1npx tsc

2. Incorrect paths

Problem: The compiler can't find files or compiles the wrong files.

Solution: Check the

include
,
exclude
,
rootDir
and
outDir
settings in tsconfig.json:

1{
2  "compilerOptions": {
3    "rootDir": "./src",
4    "outDir": "./dist"
5  },
6  "include": ["src/**/*"],
7  "exclude": ["node_modules", "**/*.spec.ts"]
8}

3. Type incompatibilities with libraries

Problem: TypeScript reports errors about types in external libraries.

Solution: Install the appropriate type definition files or create your own:

1npm install --save-dev @types/library

Or create your own definition file:

1// types/missing-library.d.ts
2declare module 'missing-library' {
3  export function someFunction(): void;
4}

4. Errors in complex generic types

Problem: TypeScript has problems with inferred types in complex generic structures.

Solution: Provide explicit type annotations at strategic locations:

1// Instead of letting TypeScript infer the type:
2const result = complexFunction(data);
3
4// Provide an explicit type annotation:
5const result: ExpectedReturnType = complexFunction(data);

Advanced usage: compiler API

For the most advanced scenarios, TypeScript provides a compilation API that allows deep integration with developer tools. It's like access to advanced laboratory tools for particularly demanding genetic experiments.

1import * as ts from 'typescript';
2
3// We create a compiler host
4const host = ts.createCompilerHost({});
5
6// We create a TypeScript program
7const program = ts.createProgram(['file1.ts', 'file2.ts'], {
8  target: ts.ScriptTarget.ES2015,
9  module: ts.ModuleKind.CommonJS
10}, host);
11
12// We compile the program
13const emitResult = program.emit();
14
15// We get the diagnostics (errors and warnings)
16const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
17
18// We display the diagnostics
19diagnostics.forEach(diagnostic => {
20  if (diagnostic.file) {
21    const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
22    const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '
23');
24    console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
25  } else {
26    console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, '
27'));
28  }
29});

This functionality is used by tools such as webpack, Rollup, or ESBuild to integrate TypeScript compilation into their own build processes.

Summary

The TypeScript compiler, like the InGen genetic laboratory, transforms advanced, safe code (TypeScript) into a form that can be "brought to life" in a browser or Node.js environment (JavaScript).

Key points:

  1. The TypeScript compiler (

    tsc
    ) transforms TypeScript code into JavaScript, removing type annotations and transforming advanced features.

  2. The compilation process includes: scanning, parsing, type checking, transformations and emission.

  3. There are many compiler options that can be passed on the command line or configured in

    tsconfig.json
    .

  4. Every TypeScript feature has a way of compiling to JavaScript, with the complexity of this compilation depending on the target JavaScript version.

  5. .
    d.ts
    files and source maps
    .
    js.map
    help with documentation and debugging.

  6. When migrating existing projects, TypeScript allows gradual introduction of types.

  7. For advanced scenarios, there is a TypeScript compilation API.

Just as the InGen laboratory needs precise protocols and tools to create dinosaurs, programmers need a well-configured TypeScript compiler to create safe and efficient JavaScript applications.

In the next lesson we'll dive into the basic types available in TypeScript - it will be like studying different dinosaur species in our park!

Go to CodeWorlds