Chapter 3

TypeScript In The Development Pipeline

Discover TypeScript's integration with JavaScript, CLI setup, and seamless transpilation for web applications. Enhance your development workflow today.

3

We've explored the relationship between JavaScript and TypeScript, and also how TypeScript improves your life as a developer. But let's go a bit deeper. In this chapter we'll get the TypeScript CLI up and running, and see how it fits into the development pipeline.

As an example, we'll be looking at using TypeScript to build a web application. But TypeScript can also be used anywhere JavaScript can - in a Node, Electron, React Native or any other app.

The Problem with TypeScript in the Browser

Consider this TypeScript file example.ts that contains a run function that logs a message to the console:

const run = (message: string) => {
  console.log(message);
};

run("Hello!");

Alongside the example.ts file, we have a basic index.html file that references the example.ts file in a script tag.

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />

    <title>My App</title>
  </head>

  <body>
    <script src="example.ts"></script>
  </body>
</html>

However, when we try to open the index.html file in a browser, you'll see an error in the console:

Unexpected token ':'

There aren't any red lines in the TypeScript file, so why is this happening?

Runtimes Can't Run TypeScript

The problem is that browsers (and other runtimes like Node.js) can't understand TypeScript on their own. They only understand JavaScript.

In the case of the run function, the : string after message in the function declaration is not valid JavaScript:

const run = (message: string) => {
  // `: string` is not valid JavaScript!

  console.log(message);
};

Removing : string gives us something that looks a bit more like JavaScript, but now TypeScript displays an error underneath message:

const run = (message) => {};
Parameter 'message' implicitly has an 'any' type.7006
Parameter 'message' implicitly has an 'any' type.

Hovering over the red squiggly line in VS Code, we can see that TypeScript's error message is telling us that message implicitly has an any type.

We'll get into what that particular error means later, but for now the point is that our example.ts file contains syntax that the browser can't understand, but the TypeScript CLI isn't happy when we remove it.

So, in order to get the browser to understand our TypeScript code, we need to turn it into JavaScript.

Transpiling TypeScript

The process of turning TypeScript into JavaScript (called 'transpilation') can be handled by the TypeScript CLI tsc, which is installed when you install TypeScript. But before we can use tsc, we need to set up our TypeScript project.

Open a terminal, and navigate to the parent directory of example.ts and index.html.

To double check that you have TypeScript installed properly, run tsc --version in your terminal. If you see a version number, you're good to go. Otherwise, install TypeScript globally with PNPM by running:

pnpm add -g typescript

With our terminal open to the correct directory and TypeScript installed, we can initialize our TypeScript project.

Initializing a TypeScript Project

In order for TypeScript to know how to transpile our code, we need to create a tsconfig.json file in the root of our project.

Running the following command will generate the tsconfig.json file:

tsc --init

Inside of the newly created tsconfig.json file, you will find a number of useful starter configuration options as well as many other commented out options.

For now, we'll just stick with the defaults:

// excerpt from tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

With our tsconfig.json file in place, we can begin transpilation.

Running tsc

Running the tsc command in the terminal without any arguments will take advantage of the defaults in tsconfig.json and transpile all TypeScript files in the project to JavaScript.

tsc

In this case, this means that our TypeScript code in example.ts file will become JavaScript code inside of example.js.

Inside of example.js, we can see that the TypeScript syntax has been transpiled into JavaScript:

// Inside of the example.js file

"use strict";

const run = (message) => {
  console.log(message);
};

run("Hello!");

Now that we have our JavaScript file, we can update the index.html file to reference example.js instead of example.ts:

// inside of index.html

<script src="example.js"></script>

Opening the index.html file in the browser will now show the expected "Hello!" output in the console without any errors!

Does TypeScript Change My JavaScript?

Looking at our JavaScript file, we can see how little has changed from the TypeScript code.

"use strict";

const run = (message) => {
  console.log(message);
};

run("Hello!");

It's removed the : string from the run function, and added "use strict"; to the top of the file. But other than that, the code is identical.

This is a key guiding principle for TypeScript - it gives you a thin layer of syntax on top of JavaScript, but it doesn't change the way your code works. It doesn't add runtime validation. It doesn't attempt to optimise your code's performance.

It just adds types (to give you better DX), and then removes them when it's time to turn your code into JavaScript.

There are some exceptions to this guiding principle, such as enums, namespaces and class parameter properties - but we'll cover those later.

A Note on Version Control

We've successfully transpiled our TypeScript code to JavaScript, but we've also added a new file to our project. Adding a .gitignore file to the root of the project and including *.js will prevent the JavaScript files from being added to version control.

This is important, because it communicates to other developers using the repo that the *.ts files are the source of truth for the JavaScript.

Running TypeScript in Watch Mode

You might have noticed something. If you make some changes to your TypeScript file, you'll need to run tsc again in order to see the changes in the browser.

This will get old fast. You might forget it, and wonder why your changes aren't yet in the browser. Fortunately, the TypeScript CLI has a --watch flag that will automatically recompile your TypeScript files on save:

tsc --watch

To see it in action, open VS Code with the example.ts and example.js files side by side.

If you change the message being passed to the run function in example.ts to something else, you'll see the example.js file update automatically.

Errors In The TypeScript CLI

If tsc encounters an error, it will display the error in the terminal and the file with the error will be marked with a red squiggly line in VS Code.

For example, try changing the message: string to message: number in the run function inside of example.ts:

const run = (message: number) => {
  console.log(message);
};

run("Hello world!");
Argument of type 'string' is not assignable to parameter of type 'number'.2345
Argument of type 'string' is not assignable to parameter of type 'number'.

Inside the terminal, tsc will display an error message:

// inside the terminal

Argument of type 'string' is not assignable to parameter of type 'number'.

run("Hello world!");

Found 1 error. Watching for file changes.

Reversing the change back to message: string will remove the error and the example.js file will again update automatically.

Running tsc in watch mode is extremely useful for automatically compiling TypeScript files and catching errors as you write code.

It can be especially useful on large projects because it checks the entire project. This is different to your IDE, which only shows the errors of the file that's currently open.

TypeScript With Modern Frameworks

The setup we have so far is pretty simple. A TypeScript file, a tsc –watch command, and a JavaScript file. But in order to build a frontend app, we're going to need to do a lot more. We'll need to handle CSS, minification, bundling, and a lot more. TypeScript can't help us with all of that.

Fortunately, there are many frontend frameworks that can help.

Vite is one example of a frontend tooling suite that not only transpiles .ts files to .js files, but also provides a dev server with Hot Module Replacement. Working with an HMR setup allows you to see changes in your code reflected in the browser without having to manually reload the page.

But there's a drawback. While Vite and other tools handle the actual transpilation of TypeScript to JavaScript, they don't provide type checking out of the box. This means that you could introduce errors into your code and Vite would continue running the dev server without telling you. It would even allow you to push errors into production, because it doesn't know any better.

So, we still need the TypeScript CLI in order to catch errors. But if Vite is transpiling our code, we don't need TypeScript to do it too.

TypeScript as a Linter

Fortunately, we can configure TypeScript's CLI to allow for type checking without interfering with our other tools.

Inside the tsconfig.json file, there's an option called noEmit that tells tsc whether or not to emit JavaScript files.

By setting noEmit to true, no JavaScript files will be created when you run tsc. This makes TypeScript act more like a linter than a transpiler. This makes a tsc step a great addition to a CI/CD system, since it can prevent merging a pull request with TypeScript errors.

Later in the book, we'll look closer at more advanced TypeScript configurations for application development.

Want to become a TypeScript wizard?

Unlock Pro Essentials
TypeScript Pro Essentials
PreviousIDE Superpowers
NextEssential Types and Annotations