TypeScript 5+ Fundamentals, v4

Compiling TypeScript with TSC

TypeScript 5+ Fundamentals, v4

Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course

The "Compiling TypeScript with TSC" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Mike explains how the TypeScript CLI works and demonstrates it by using a Hello World TypeScript program. How the TypeScript compiler can target different language levels and generate different module formats is also discussed. Finally, the purpose of declaration files and how they allow developers to benefit from type information when using TypeScript is covered in this segment.


Transcript from the "Compiling TypeScript with TSC" Lesson

>> So the first thing we're gonna do is a Hello World TypeScript program. We're gonna learn how the TypeScript CLI works. There's a small project ready for us to get started with right in this repo that you checked out. And we will get hands on here, we'll look at the output of a TypeScript program, we'll see the readable JavaScript that I promised exists.

And we will see how the compile process results in that readable JavaScript and a declaration file, which represents the types that sort of match onto that JavaScript. You can think of it like the shadow of an object where they have to be in sync and together, it's almost like a TS file.

But when you compile it, the types are separated from the runnable JavaScript. And that's your declaration file and the program that's actually run, we'll look at how that process works. So you can get to this project in the repo that we just checked out by going to packages/welcome-to-ts.

And if we navigate to that here, there are a bunch of packages here which we'll get into a couple others in this course and in later courses. But welcome-to-ts within the packages folder, that's what we're looking for. We're gonna spend a little bit of time here for now.

So I want you to look at the folder structure here, we've got a couple different things that are interesting. Ignore this dist folder for now, the fact that it's grayed out means it's get ignored. So this is not something that's checked in. We've got a couple different files here, three.

We've got our package.json, which you should be familiar with if you've used JavaScript before. This is the package manifest of your program. You can see we have a dev dependency here, which is TypeScript 5.3. It is a beta right now, but I expect the release is gonna be cut any day now.

We've got a couple scripts we can run, which we'll look into later, and then here's the volta stuff we talked about earlier. And then we've got something called a tsconfig and an index.ts. So let's look at this tsconfig. So we're telling the TypeScript compiler a couple things here in this config file.

We're saying, where do we want the output? What language level do we want as output? So this would be ES 2015 language level. This includes the promise constructor, classes, right? Before ES 2015, we didn't have classes. We'll mess with this, and we'll see what happens as we change the language level.

Module resolution node, you don't have to worry about too much here, but just think of this as the conventional setting where things are in node modules. And it's the module module resolution you would expect if you weren't using TypeScript. And then here include, this is where we say what are we compiling, where is the source code?

We're saying, everything in the source folder, compile everything in there. Let's take a look at this index.ts file. So this is a very relatively simple TypeScript program. I have chosen a couple features here that will illustrate the difference between language levels here. So there are a couple things in here that only work in ES 2015.

There's async await, which only landed in ES 2017, and then I've thrown in a static private field, which only works in ES 2022. So what we hope to see here is that all of this kind of boils down to ES 2015. There's a way that it can do that.

But let's look at what this program does. So up here, if we ignore the types aspect of things here, we take in an argument n, and we resolve a promise after n milliseconds. That's effectively what this means. Here, oops, I spotted a typo. We're adding two numbers, not three numbers.

We're adding a and b after waiting half a second. And then we have a class here that has a private static field and a getValue function. This kind of exists mostly to throw in a very modern JavaScript language feature. Down here we're saying, okay, I want to add the numbers, I wanna get this number 3, right, which will come out here.

So we're adding 3 and 4 here and we're waiting for that result to come in, which will happen after this half a second. And once that wait is done, we're gonna console.log the result. All right, here's our dist folder, we've got an index.js file. Now if we look at this, there's a lot going on here.

So we've got something up here that looks like an awaiter. We've got something here that's called classPrivateFieldGet. And now it's sort of starting to look like our original code, but with some of the type information stripped out. So now we're here, we looked at this output, right? It's a bunch of junk up here, junk like, we didn't write this.

We don't understand it. We can run this code. Just pass it straight to node, oops. Unexpected token 'export', mm-hmm, that's interesting. Well, we can't run it like this, right, with this export here. We'd have to make this a CJS module for node to just take it as is.

Well, we have two options. We can make this an MJS file, if you're familiar with what those are. But let's make it a CJS file. MJS is these native JavaScript modules with this export keyword. But let's make it the classic thing that node has been able to run for ages.

So we could go into our tsconfig and we could say, "module": "commonjs". And we can run this again, you have to build it again and then run it again. So there's the build. And here, I just tried to run it, and there's our number 7. So what we've seen here, if we look at this dist folder, look at that, exports.addNumbers = addNumbers.

So this, we can see TypeScript is able to generate a variety of different module formats for us, CJS being one of them. Let's play with that tsconfig a little bit more. I'm gonna just pull it off to the side here so we can see it at the same time, and we can get rid of, I'll just pull this down a little bit.

So let's change our language level. Let's bump this up to ES 2017. Let's build it again and then we'll run it. And what we expect to see, some of this junk will disappear. And it did, that awaiter thing up at the top disappeared, and an await keyword has started to become present in our code.

So now we're seeing, all right, we're targeting a more modern JavaScript language level. Now there's still this classPrivateFieldGet thing because that's not supported in ES 2017. This is kind of a polyfill. It's a way to backport the behavior where we would see, here's the class, right? We can have a static field, we just can't have a private static field.

And you could see what's happening is it's generating this _Foo_bar. That's to sort of make up for this, it's trying to pick a secret property that nobody will guess accidentally. And it's putting the value of 3 in there and then somewhere up here, it's probably getting the value out of that object.

It's sort of a compatibility layer, right, for ES 2017. What if we bump this up to ES 2022, built and run it again? Sorry, I didn't run that last version, but trust me that it would output the number 7. We'll run this one, there's 7. And now look, we've lost almost all of the stuff that was up at the top of our output file here.

And if we look down, here is a static field, a private static field, which is natively supported in ES 2022. Node is happy to run this, Node 18, and you can see that TypeScript is able to sort of down-level our code to some degree and allow us to use very modern JavaScript features, even if we have to run in maybe an older browser.

It's less of a concern now, cuz we're sort of past the Internet Explorer days and we can rely on browsers supporting pretty modern things. But this is an example of TypeScript acting kind of Babel, if you've used that before, which is a way you can sort of adopt very modern language features even if your target runtime doesn't support it yet.

So that's one of the things TypeScript is doing for us. It's also removing all of that type information. So let's go back to our tsconfig and add an option here called declaration. And we'll set that to True. And we can run TSC again, and let's run it. Program still works, a new file has appeared though.

I'm gonna get rid of our TSC for now or put it back here. Let's look at this. So remember I told you there's this thing called a declaration file and I described this as sort of the shadow of the resultant JavaScript code that TypeScript admits. These are the types.

So if we looked at this, sorry, this is our source code. We've got addNumbers, number and number. Our JavaScript file just has addNumbers(a, b), missing the type information. But here is the type information. It's not really a function cuz it doesn't have a body. We would call this a function type declaration, something like that, but it's meant to match the output JavaScript code.

Why do we do this? Well first, we want people who just run regular JavaScript to be able to run our program. So we need to have JavaScript as an output. We also want developers who use TypeScript to benefit from our types. And so we want to include the type information along with the JavaScript so that you almost put these two files together and you're back to index.ts.

You're back to typed code, because you kind of layer these two things on top of each other. You have the code that runs and the types that describe it.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now