2.2 The tsc Command: Compiling a Single File
Alright, let’s get our hands dirty. You’ve written a beautiful piece of TypeScript code in a file called greeter.ts. It’s pristine. It’s elegant. It has type annotations. Your browser, however, couldn’t care less about type annotations. It speaks JavaScript, and frankly, it finds your sophisticated types a bit pretentious. This is where tsc, the TypeScript compiler, comes in. Its job is to be the translator, converting your lofty TypeScript (greeter.ts) into the plain, workmanlike JavaScript (greeter.js) that the browser or Node.js can actually execute.
Think of it as the difference between a detailed architectural blueprint (TypeScript) and the actual, buildable IKEA instructions (JavaScript). One is for designing and catching errors early; the other is for actually running the thing.
The Basic Incantation
The most straightforward way to use tsc is to point it directly at a single file. Open your terminal, navigate to the directory containing your file, and run:
tsc greeter.ts
If all goes well, you’ll be rewarded with… absolute silence. No news is good news in the world of command-line tools. More importantly, you’ll find a new file sitting right next to your original: greeter.js. Go ahead, peek inside. You’ll see your code, but all the type annotations—the : string parts—have been surgically removed. They’ve done their job during compilation and are no longer needed for runtime.
Let’s make this real. Create a greeter.ts file with this classic, slightly-snarky example:
function greet(person: string, date: Date): void {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Brendan", new Date());
Now run tsc greeter.ts. Look at the generated greeter.js:
function greet(person, date) {
console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));
}
greet("Brendan", new Date());
Notice a few things? First, the types are gone. Second, our fancy template literal (those backticks `Hello ${person}`) was converted to a more widely-supported .concat() method. This is called downleveling. By default, tsc targets a older version of JavaScript (ES3) to ensure maximum compatibility. We’ll learn how to change that behavior soon enough.
When Things Go Exactly As Planned (i.e., Wrong)
The true power of tsc isn’t in its ability to generate JavaScript—any transpiler could do that. Its power is in its refusal to generate JavaScript when your types are a mess. Let’s deliberately break our code.
function greet(person: string, date: Date): void {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
// Let's try to greet a number with a string. This is a terrible idea.
greet(42, "Monday");
Now run tsc greeter.ts again. This time, you won’t be met with silence. You’ll get two glorious, helpful error messages:
greeter.ts:6:8 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
6 greet(42, "Monday");
~~
greeter.ts:6:12 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'Date'.
6 greet(42, "Monday");
This is TypeScript earning its keep. It caught two blatant bugs before you even tried to run the code. The compiler is your brilliant, pedantic friend who stops you from walking out of the house with your shirt on inside-out.
Here’s the crucial part: tsc will still output the JavaScript file (greeter.js) even with errors. This seems counterintuitive, but it’s a design choice. The core philosophy is that the compiler’s job is to report issues, not to decide whether your code is fit for production. Sometimes you want to see the output even if there are errors, maybe for a quick test. If you want tsc to be a strict gatekeeper and fail completely on error (which is what you want in most real projects), you need to tell it to.
Making tsc Actually Listen to You
Running tsc greeter.ts is fine for a quick one-off, but it’s a blunt instrument. The compiler uses a set of default rules when you compile this way, which is rarely what you want. To truly control the process, you need a configuration file: tsconfig.json. This is where you tell tsc your preferences on everything from which JavaScript version to target to how strict you want it to be.
We’ll dive into the tsconfig.json black hole in the next section. For now, just know that if you run tsc with no filename arguments and a tsconfig.json is present in the directory, it will use the rules defined in that config file for your entire project. It’s the difference of handing a single order to a chef (tsc file.ts) and giving them your detailed dietary manifesto (tsc).
A Quick Note on Watching
You’re going to get tired of manually running tsc after every single change. So will I. The compiler has a built-in watch mode that does exactly what you think it does:
tsc greeter.ts --watch
# or just
tsc greeter.ts -w
Run that, and tsc will stay active in your terminal, patiently watching your greeter.ts file. Make a change, save the file, and boom—it recompiles instantly. It’s a fantastic way to work when you’re just fiddling with a single file. For full projects, we’ll use the project-level version of watch mode once we have a tsconfig.json set up.