C.W.K.
Stream
Lesson 03 of 06 · published

Your First `tsc`: tsconfig.json, target, module

~14 min · foundations, tsc, tsconfig, first-compile

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"tsc is just a JavaScript program. It reads .ts, it writes .js. Everything else is configuration."

What tsc actually is

tsc is the TypeScript compiler — a Node.js program shipped as part of the typescript npm package. You run it, it reads your .ts files, type-checks them, and writes .js files. That's the whole job description. Everything else — bundlers, dev servers, IDE integration — wraps this same compiler API.

You don't need a bundler to use TypeScript. You don't need Vite, Next.js, or Webpack. They make life nicer, but tsc on its own can compile any TypeScript project. In this lesson we install tsc, point it at a single file, and watch it work.

The three install options

1. Project-local: npm install --save-dev typescript in a project folder. Pinned in package.json, runs via npx tsc. This is what almost every real project does. Every collaborator gets the same compiler version.

2. Global: npm install -g typescript. Available everywhere as plain tsc. Fine for tinkering or one-off scripts. Don't rely on it inside a team project — version drift between machines is a pain.

3. Run-once: npx -p typescript@latest tsc hello.ts. No install. Slower per-invocation but useful for a quick experiment.

tsconfig.json — the only config file you need

Without a tsconfig.json, tsc falls back to defaults that are wrong for most projects (loose mode, ES5 target, CommonJS modules). With one, tsc picks up the entire project layout automatically. Run npx tsc --init and it writes a starter tsconfig.json that's heavily commented and explains every option.

The four fields that matter for your first compile: target (which JavaScript version to emit — almost always 'ES2022' or newer in 2026), module (how to emit imports/exports — usually 'NodeNext' or 'ESNext'), strict (we'll cover this next lesson — leave it true), and outDir (where compiled JS goes — usually 'dist').

The mental model: tsc is a build step that turns one tree of .ts files into a parallel tree of .js files in outDir. The .ts files are your source. The .js files are the artifact. Edit the source, run the build, ship the artifact.

The compile happens in two phases

Phase 1: type-checking. tsc reads every file, builds a global type graph, and flags any inconsistencies — wrong argument types, missing return statements, calls to non-existent methods. If errors are found, tsc reports them (and by default, does NOT emit JS files).

Phase 2: emit. tsc walks each file, strips the type annotations, down-levels any newer JavaScript syntax to the configured target, and writes the .js file to outDir. This phase is mechanical — no opinions, no transformations beyond what's needed.

You can run just phase 1 with tsc --noEmit. This is what CI uses, and what your IDE uses silently as you type.

Pippa's confession

cwkPippa's frontend uses tsc -b (the build mode for project references) plus Vite for the actual dev/build pipeline. But when I want to understand a TypeScript issue — really understand it — I run plain tsc against a single file. No Vite, no bundler, no plugin. Just compiler in, compiler out. It's the cleanest way to isolate "is this a TS problem or a tooling problem?"

Code

Smallest possible TypeScript project·bash
# Fresh project, smallest possible setup.
mkdir hello-ts && cd hello-ts

npm init -y                           # creates a minimal package.json
npm install --save-dev typescript     # adds tsc as a dev dependency
npx tsc --init                        # creates a starter tsconfig.json

# Create one TS file.
cat > hello.ts <<'EOF'
function greet(name: string): string {
  return `Hello, ${name}!`;
}
console.log(greet('Pippa'));
EOF

# Compile.
npx tsc

# See the output.
ls -la hello.js
node hello.js                          # 'Hello, Pippa!'
A starter tsconfig.json with the right defaults for 2026·json
// tsconfig.json — the four fields that matter
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

External links

Exercise

Create a fresh folder. Install TypeScript locally. Run tsc --init. Write a 5-line .ts file with one intentional type error (e.g., pass a number where a string is expected). Run tsc. Read the error message. Now fix the error and run tsc again. What changed in dist/?
Hint
By default tsc does NOT emit JS when there are errors. So before the fix, dist/ should be empty (or unchanged). After the fix, the .js file appears. If you want to emit anyway despite errors, that's --noEmitOnError false — but you usually don't want that.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.