1. The compiler and the tooling
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Code: the files examples/l01_* and errors/l01_*, the project l01-emit, the course’s tsconfig.json, and the C# and Java sides in compare_fail/l01_types.cs and compare_fail/L01Types.java.
Types that disappear
Section titled “Types that disappear”In C# and Java, the compiler and the runtime agree on the types. csc refuses a program that doesn’t type-check, and the types it checked are still there in the IL, where the CLR verifies casts and typeof(T) returns a real type. TypeScript works differently: it is JavaScript with type annotations, the compiler checks them, and nothing of them reaches the program that runs.
| C# | Java | TypeScript | |
|---|---|---|---|
| Compiler | csc, through dotnet build |
javac |
tsc |
| What it produces | IL, with the types | bytecode, with most of the types | JavaScript, without the types, or nothing at all |
| A type error | no assembly | no class file | a message; the JavaScript is written anyway, and Node.js runs the .ts file anyway |
| Types at run time | all of them, generics included | classes, not generic arguments | none |
| A cast to the wrong type | InvalidCastException |
ClassCastException |
nothing: as checks nothing (lesson 3) |
So a TypeScript project has two separate steps where a .NET project has one: checking the types, which tsc does, and running the code, which a JavaScript engine does after the types have been removed, either by tsc itself or, since Node.js 23.6, by Node.js.
flowchart LR ts["main.ts"] --> check["tsc --noEmit<br/>checks the types, writes nothing"] check --> report["errors, or nothing"] ts --> strip["node main.ts<br/>replaces the types with spaces"] strip --> run["the program runs,<br/>type errors or not"] ts --> emit["tsc<br/>checks, then writes JavaScript"] emit --> js["dist/main.js and main.d.ts"] --> node["node dist/main.js"]Nothing forces the first path before the others. That is the most important difference with C#, and this lesson shows it on real output before anything else.
TypeScript 5, 6 and 7
Section titled “TypeScript 5, 6 and 7”The compiler changed more in the last year than in the ten before:
| Version | Released | What changed |
|---|---|---|
| 5.9 | August 2025 | the last release of the 5.x line, the compiler written in TypeScript |
| 6.0 | March 2026 | the last compiler written in TypeScript: new defaults (strict on, module esnext, types empty) and deprecations of old options |
| 7.0 | July 2026 | the compiler ported to Go, a native executable, with the defaults of 6.0 and the deprecated options removed |
TypeScript 7 is built to report the same errors as TypeScript 6.0 on the same code, and its announcement measures full builds 8 to 12 times faster. On my machine, checking the application project of GA’s component library took 14.7 seconds with TypeScript 5.9.3 and 1.9 seconds with 7.0.2. What 7.0 doesn’t have yet is a programmatic API: tools that load the compiler as a library, such as typescript-eslint or the language tools of Astro, Vue and Svelte, still need TypeScript 6, which the team publishes as @typescript/typescript6 with a tsc6 command, to install next to 7. This course uses TypeScript 7.0.2, the latest release on 14 September 2026.
Installing the compiler
Section titled “Installing the compiler”TypeScript is an npm package, installed in each project as a development dependency, like a local .NET tool pinned in a manifest. There is no global installation to keep in sync: the version in package-lock.json is the version everyone gets.
New-Item -ItemType Directory typescript-course | Set-Locationnpm init -ynpm install --save-dev --save-exact typescript@7.0.2 @types/node@24.13.4npx tsc --versionGet-ChildItem node_modules\@typescript -NameVersion 7.0.2typescript-win32-x64mkdir typescript-course && cd typescript-coursenpm init -ynpm install --save-dev --save-exact typescript@7.0.2 @types/node@24.13.4npx tsc --versionls node_modules/@typescriptVersion 7.0.2typescript-linux-x64mkdir typescript-course && cd typescript-coursenpm init -ynpm install --save-dev --save-exact typescript@7.0.2 @types/node@24.13.4npx tsc --versionls node_modules/@typescriptVersion 7.0.2typescript-darwin-arm64The last line differs, and it is the new part. The typescript package lists one package per platform as optional dependencies, and npm installs only the one that matches the machine: the Go executable for Windows x64, Linux x64 or macOS on Apple silicon. The course’s CI prints that line on its three runners. @types/node holds the type declarations of Node.js’s API, process, node:fs and the rest, versioned like Node.js 24; --save-exact writes 7.0.2 instead of ^7.0.2 in package.json (JavaScript lesson 1 explains the difference).
npx tsc runs the compiler from node_modules/.bin; so does "typecheck": "tsc" in the scripts of package.json, which is how the course’s package.json runs it.
tsconfig.json
Section titled “tsconfig.json”A tsconfig.json is to tsc what a .csproj is to dotnet build: the files of the project and the options of the compiler. npx tsc --init writes a commented one:
> npx tsc --init
Created a new tsconfig.json
You can learn more at https://aka.ms/tsconfig{ // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { // File Layout // "rootDir": "./src", // "outDir": "./dist",
// Environment Settings // See also https://aka.ms/tsconfig/module "module": "nodenext", "target": "esnext", "types": [], // For nodejs: // "lib": ["esnext"], // "types": ["node"], // and npm install -D @types/node
// Other Outputs "sourceMap": true, "declaration": true, "declarationMap": true,
// Stricter Typechecking Options "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true,
// Style Options // "noImplicitReturns": true, // "noImplicitOverride": true, // "noUnusedLocals": true, // "noUnusedParameters": true, // "noFallthroughCasesInSwitch": true, // "noPropertyAccessFromIndexSignature": true,
// Recommended Options "strict": true, "jsx": "react-jsx", "verbatimModuleSyntax": true, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, }}It is a starting point for a library published to npm, with declaration files and source maps. The course runs its files with Node.js and only asks tsc to check them, so its tsconfig.json follows the settings that Node.js recommends for type stripping:
{ // The course's compiler options: Node.js 24 runs the .ts files itself, tsc only checks them "compilerOptions": { "noEmit": true, "target": "esnext", "lib": ["esnext"], "types": ["node"], "module": "nodenext", "rewriteRelativeImportExtensions": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "strict": true, "skipLibCheck": true }, "include": ["examples", "solutions"]}| Option | What it does | Closest thing in .NET |
|---|---|---|
noEmit |
check only, write no JavaScript | a build that stops after the analyzers |
target |
the JavaScript version of the output; esnext keeps the syntax as written |
— |
lib |
the built-in APIs that exist, here ECMAScript only, without the browser’s DOM | the target framework’s base class library |
types |
the global declarations to load from @types packages; empty by default since 6.0 |
package references that add global usings |
module nodenext |
resolve and check imports exactly as Node.js does, "type" of package.json included |
— |
rewriteRelativeImportExtensions |
allow import './money.ts', and write ./money.js in emitted code |
— |
erasableSyntaxOnly |
refuse the syntax that Node.js can’t strip (below) | — |
verbatimModuleSyntax |
require import type for types, so that removing types never changes an import |
— |
strict |
a family of checks, strictNullChecks among them; on by default since 6.0 |
<Nullable>enable</Nullable>, and more |
skipLibCheck |
don’t check the .d.ts files of packages |
— |
strict is written out even though it is the default: code copied to a project that still sets "strict": false should fail loudly rather than quietly lose its checks. Lesson 2 opens the family.
A type error stops nothing
Section titled “A type error stops nothing”An order from a CSV file, where every price arrives as text:
interface Line { product: string; price: number; quantity: number;}
function withTax(line: Line): number { return line.price + line.price * 0.15;}
// The prices come from a CSV file, where everything is textconst lines: Line[] = [ { product: 'capo', price: '9.99', quantity: 2 }, { product: 'strings', price: 12.5, quantity: 1 },];for (const line of lines) { console.log(line.product, withTax(line) * line.quantity);}tsc sees the problem:
> npx tsc -p out/tsconfig.l01_order.json --prettyerrors/l01_order.ts:14:22 - error TS2322: Type 'string' is not assignable to type 'number'.
14 { product: 'capo', price: '9.99', quantity: 2 }, ~~~~~
errors/l01_order.ts:4:3 - The expected type comes from property 'price' which is declared here on type 'Line' 4 price: number; ~~~~~
Found 1 error in errors/l01_order.ts:14The same line in C#, where Line is a record with a decimal price, and in Java:
> dotnet run l01_types.cscompare_fail/l01_types.cs(4,22): error CS1503: Argument 2: cannot convert from 'string' to 'decimal'
The build failed. Fix the build errors and run again.> javac L01Types.javaL01Types.java:6: error: incompatible types: String cannot be converted to double var lines = new Line[] { new Line("capo", "9.99", 2), new Line("strings", 12.5, 1) }; ^Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output1 errorSo far the three languages agree. Now run the file:
> node errors/l01_order.tscapo NaNstrings 14.375Node.js ran it, and the program printed NaN for the capo without an exception. line.price holds the string '9.99', so line.price + line.price * 0.15 concatenates '9.99' and 1.4985 into '9.991.4985', and multiplying that string by the quantity gives NaN (JavaScript lesson 2 explains the two meanings of +). The type annotation price: number was a promise to tsc, and Node.js never reads it.
In a .NET solution, a type error means there is no program to run. In a TypeScript project, it means there is a message, and the project decides what the message stops: a typecheck script in CI, a pre-commit hook, the editor. A project that builds with a tool that only strips types, as Vite does, can ship code that tsc rejects, and the last section of this lesson finds such code in GA.
tsc writes JavaScript even when it reports errors
Section titled “tsc writes JavaScript even when it reports errors”Without noEmit, tsc also writes JavaScript. The project l01-emit extends the course’s options and writes to dist:
export type Currency = 'CAD' | 'EUR';
export interface Money { readonly cents: number; readonly currency: Currency;}
export function add(a: Money, b: Money): Money { if (a.currency !== b.currency) throw new Error(`${a.currency} + ${b.currency}`); return { cents: a.cents + b.cents, currency: a.currency };}
export function formatMoney(money: Money): string { return `${(money.cents / 100).toFixed(2)} ${money.currency}`;}import { add, formatMoney, type Money } from './money.ts';
const capo: Money = { cents: 999, currency: 'CAD' };const strings = { cents: 1250, currency: 'CAD' } satisfies Money;const shipping: Money = { cents: '500', currency: 'CAD' }; // a type error: tsc reports it, and writes the files anyway
console.log(formatMoney(add(add(capo, strings), shipping)));> npx tscsrc/main.ts(6,27): error TS2322: Type 'string' is not assignable to type 'number'.> ls distdist/main.d.tsdist/main.jsdist/money.d.tsdist/money.jstsc exited with code 2, which means “errors, and the files were written anyway”, and dist/main.js runs:
import { add, formatMoney } from './money.js';const capo = { cents: 999, currency: 'CAD' };const strings = { cents: 1250, currency: 'CAD' };const shipping = { cents: '500', currency: 'CAD' }; // a type error: tsc reports it, and writes the files anywayconsole.log(formatMoney(add(add(capo, strings), shipping)));> node dist/main.js22495.00 CADThe types are gone, the import lost type Money and now names ./money.js, and the shipping cost '500' was concatenated to the sum: 2,249 cents followed by the text 500 makes 2249500 cents. The option noEmitOnError makes tsc write nothing when it finds an error (exercise 3). dist/money.d.ts keeps what the JavaScript lost, the types of what the module exports, for the projects that import it; it plays the role of the metadata of a .NET assembly:
export type Currency = 'CAD' | 'EUR';export interface Money { readonly cents: number; readonly currency: Currency;}export declare function add(a: Money, b: Money): Money;export declare function formatMoney(money: Money): string;Reading a diagnostic
Section titled “Reading a diagnostic”A tsc message gives the file, the line and column, the severity, a code and a text, and then, with --pretty, the line itself with ~~~ under the problem. The indented part is related information: The expected type comes from property 'price' points to the declaration that set the expectation, where C# would only say what it couldn’t convert.
--pretty is the default in a terminal, and --pretty false prints one line per error, in the file(line,col): error CODE: text format of MSBuild and csc, which editors and CI annotations read:
> npx tsc --pretty falsesrc/main.ts(6,27): error TS2322: Type 'string' is not assignable to type 'number'.The codes are grouped by number, loosely, in the compiler’s message catalog: the 1000s are mostly syntax, the 2000s type checking, the 5000s options and configuration, the 6000s the texts of the command line, the 7000s implicit any, and the 18000s newer checks. TS2322, “Type … is not assignable to type …”, is the one you will meet most.
One configuration error surprises everyone who types tsc file.ts, as you would type javac File.java:
> npx tsc errors/l01_order.tserror TS5112: tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.Since TypeScript 6.0, a file name on the command line in a folder that has a tsconfig.json is an error, because the file would have been checked with the default options instead of the project’s. npx tsc checks the project; npx tsc --ignoreConfig file.ts checks one file with the defaults. The course’s check.sh checks each error snippet alone by giving it a two-line tsconfig.json that extends the course’s, which is the -p out/tsconfig.l01_order.json above.
Running TypeScript with Node.js
Section titled “Running TypeScript with Node.js”Node.js 24 runs a .ts file directly. It doesn’t compile it: it replaces the type annotations with spaces, a feature called type stripping, enabled by default since Node.js 23.6 and marked stable in 24.12. The function that does it is public, as module.stripTypeScriptTypes:
import { stripTypeScriptTypes } from 'node:module';
// What Node.js runs: the same text, with the types replaced by spacesconst source = `interface Line { price: number; quantity: number;}export function total(lines: readonly Line[]): number { let sum: number = 0; for (const line of lines) sum += line.price * line.quantity; return sum;}const empty = [] as Line[];`;console.log(stripTypeScriptTypes(source));export function total(lines ) { let sum = 0; for (const line of lines) sum += line.price * line.quantity; return sum;}const empty = [] ;(node:PID) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time(Use `node --trace-warnings ...` to show where the warning was created)The interface became blank lines and each annotation became as many spaces as it had characters, so every remaining character keeps its line and column. That is why an error in a .ts file points to the right place without a source map. The warning comes from the function, still marked release candidate in the documentation; running a .ts file prints none.
Node.js reads a .ts file as it reads a .js file: the nearest package.json decides between ES module and CommonJS, and .mts and .cts force the choice, like .mjs and .cjs. Node.js ignores tsconfig.json entirely and checks no types.
What Node.js refuses
Section titled “What Node.js refuses”Stripping only works on syntax that disappears without leaving a hole. A few TypeScript features generate JavaScript instead, and Node.js refuses them:
class Price { constructor(private readonly cents: number) {} // a parameter property declares and assigns a field toString() { return (this.cents / 100).toFixed(2); }}
namespace Tax { export const rate = 0.15;}
const price = <Price>new Price(999); // the old cast syntaxconsole.log(`${price}`, Tax.rate);> node errors/l01_not_erasable.tsnode:internal/modules/run_main:107 triggerUncaughtException( ^
errors/l01_not_erasable.ts:3class Price { constructor(private readonly cents: number) {} // a parameter property declares and assigns a field ^^^^^^^^^^^^^ toString() {
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode{ code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'}
Node.js v24.21.0Node.js stops at the first one. With erasableSyntaxOnly, tsc reports all three before anything runs:
> npx tsc -p out/tsconfig.l01_not_erasable.json --prettyerrors/l01_not_erasable.ts:3:15 - error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
3 constructor(private readonly cents: number) {} // a parameter property declares and assigns a field ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
errors/l01_not_erasable.ts:9:11 - error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
9 namespace Tax { ~~~
errors/l01_not_erasable.ts:13:15 - error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
13 const price = <Price>new Price(999); // the old cast syntax ~~~~~~~
Found 3 errors in the same file, starting at: errors/l01_not_erasable.ts:3| Refused | What it generates | What to write instead |
|---|---|---|
a parameter property, constructor(private readonly cents: number) |
a field and an assignment | the field, and the assignment in the constructor |
a namespace that contains values |
an object filled by a function | a module, one per file |
an enum |
an object with a reverse mapping | an object with as const, and a union type (exercise 2) |
<Price>value, the old assertion syntax |
nothing, and Node.js refuses the syntax | value as Price |
A namespace that only contains types is accepted, since it disappears. C# developers reach for enum first, and it is the one to unlearn:
enum Currency { CAD, EUR,}
console.log(Currency.EUR, Currency[1]);> node errors/l01_enum.tsnode:internal/modules/run_main:107 triggerUncaughtException( ^
errors/l01_enum.ts:2 // errors/l01_enum.ts > enum Currency { CAD, EUR, > }
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode{ code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'}
Node.js v24.21.0Two tools run it anyway. Node.js’s flag --experimental-transform-types generates the JavaScript, with a warning, and tsx, a package built on esbuild, runs any TypeScript file without checking it either:
> node --experimental-transform-types errors/l01_enum.ts(node:PID) ExperimentalWarning: Transform Types is an experimental feature and might change at any time(Use `node --trace-warnings ...` to show where the warning was created)1 EUR> npx tsx errors/l01_enum.ts1 EURBoth print 1 EUR: Currency.EUR is 1, and Currency[1] is the reverse mapping back to the name. The course keeps to erasable syntax, which runs with plain node, needs no extra package, and reads the same before and after stripping.
Imports: the extension, and type
Section titled “Imports: the extension, and type”Node.js needs the real file name in an import, .ts included (JavaScript lesson 1 showed the same rule for .js). An import written the way many TypeScript projects write them:
import { formatMoney, Money } from '../examples/l01_money';
const price: Money = { cents: 999, currency: 'CAD' };console.log(formatMoney(price));> npx tsc -p out/tsconfig.l01_imports.json --prettyerrors/l01_imports.ts:2:36 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../examples/l01_money.js'?
2 import { formatMoney, Money } from '../examples/l01_money'; ~~~~~~~~~~~~~~~~~~~~~~~
Found 1 error in errors/l01_imports.ts:2> node errors/l01_imports.tsnode:internal/modules/esm/resolve:272 throw new ERR_MODULE_NOT_FOUND( ^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'examples/l01_money' imported from errors/l01_imports.ts{ code: 'ERR_MODULE_NOT_FOUND', url: 'examples/l01_money'}
Node.js v24.21.0With module set to nodenext, tsc reports the error Node.js will throw, and suggests .js, the extension that a project compiled to JavaScript would import; for Node.js’s type stripping, the right extension is .ts. With the extension, a second error appears:
import { formatMoney, Money } from '../examples/l01_money.ts';
const price: Money = { cents: 999, currency: 'CAD' };console.log(formatMoney(price));> npx tsc -p out/tsconfig.l01_type_import.json --prettyerrors/l01_type_import.ts:2:23 - error TS1484: 'Money' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
2 import { formatMoney, Money } from '../examples/l01_money.ts'; ~~~~~
Found 1 error in errors/l01_type_import.ts:2> node errors/l01_type_import.tserrors/l01_type_import.ts:2import { formatMoney, Money } from '../examples/l01_money.ts'; ^^^^^SyntaxError: The requested module '../examples/l01_money.ts' does not provide an export named 'Money'
Node.js v24.21.0Stripping works file by file, so Node.js can’t know that Money is a type in the other file: it keeps the name in the import, and the module that exports it has no such value. import { formatMoney, type Money } marks the name as a type, and removes it with the other annotations. verbatimModuleSyntax makes tsc require that marker. The corrected file, examples/l01_imports.ts:
import { formatMoney, type Money } from './l01_money.ts';
const price: Money = { cents: 999, currency: 'CAD' };console.log(formatMoney(price));> node examples/l01_imports.ts9.99 CADIn real projects
Section titled “In real projects”GuitarAlchemist/ga has two React front ends in TypeScript, at commit a826864. Their package.json files ask for typescript ^5.5.3, and their build script is vite build (ga-client/package.json, line 13). Vite strips the types with esbuild and, as its documentation says, doesn’t check them; the check is a separate build:with-types script, tsc -b && vite build. I cloned the repository into a scratch folder, installed the dependencies and ran tsc -b with TypeScript 5.9.3:
Apps/ga-clientreports 346 errors, 165 of themTS6133for unused variables. Among the rest,ChatInterface.tsxusesVIRTUALIZATION_THRESHOLDandVirtualizedMessageListat lines 69, 219, 254 and 255, and nothing defines or imports them:TS2304: Cannot find name 'VIRTUALIZATION_THRESHOLD'. The names have been missing since the file’s first commit. Line 219 is in the component’s JSX, so rendering the page/ai-copilotshould throw aReferenceError(to verify in a browser).ReactComponents/ga-react-componentsreports 186 errors. Its lock file no longer matches itspackage.json(pnpm install --frozen-lockfilerefuses it: 21 dependencies were added without updating the lock file), so I installed without it, and some of those errors may depend on the versions I got.
Its application project alone, tsc -p tsconfig.app.json, reports 180 errors in 14.7 seconds with TypeScript 5.9.3, and 197 in 1.9 seconds with TypeScript 7.0.2. Most of the difference is the new default of types: a script that uses process, fs and __dirname gets TS2591: Cannot find name 'process'. Do you need to install type definitions for node?, and "types": ["node"] in that project’s options brings them back, as the 6.0 release notes predict.
This site has a tsconfig.json that extends Astro’s strict preset, and no typescript dependency at all: Astro strips types with Vite, and only the editor reads the file. Checked with npx -p typescript@7.0.2 tsc --noEmit at commit 8ba378e, it reports three errors, and none of them breaks the site: astro:content is a module that exists only after astro sync generates its declarations, the JavaScript course’s error snippet about private fields is picked up by the **/* pattern, and the sidebar that astro.config.mjs imports from src/streeling-sidebar.json doesn’t match Starlight’s type for sidebar items, which lesson 3 explains. The TypeScript 7 announcement says that Astro’s own tooling still needs TypeScript 6.
Key takeaways
Section titled “Key takeaways”- TypeScript’s types exist only for
tsc: Node.js, Vite and esbuild remove them without reading them, and nothing checks them at run time. - A type error doesn’t stop a program from running, nor
tscfrom writing JavaScript. Maketsc --noEmita step of your build and CI, or setnoEmitOnError. - TypeScript 7 is the same type checker as 6.0, ported to Go and several times faster; tools that need the compiler’s API stay on 6.0 for now.
- Install TypeScript per project with npm;
tsconfig.jsonis the project file, andtsc file.tsnext to one is an error since 6.0. - Node.js 24 strips types from
.tsfiles; it refusesenum, parameter properties, namespaces with values and<T>assertions.erasableSyntaxOnlyandverbatimModuleSyntaxmaketscrefuse them first. - For Node.js, import
./file.tswith its extension, and import types withimport type.
Exercises
Section titled “Exercises”- Fix
errors/l01_order.tsso thattscaccepts it and it prints the right totals, with the rows of the CSV as arrays of strings. Where should the conversion happen?
Solution
At the border, once: the rest of the program then works with a Line whose price really is a number. solutions/l01_ex1_order.ts:
interface Line { product: string; price: number; quantity: number;}
function withTax(line: Line): number { return line.price + line.price * 0.15;}
// The CSV gives text: convert it once, at the border, and refuse what isn't a pricefunction parsePrice(text: string): number { if (!/^\d+(\.\d{1,2})?$/.test(text)) throw new TypeError(`not a price: ${JSON.stringify(text)}`); return Number(text);}
const rows = [ ['capo', '9.99', '2'], ['strings', '12.5', '1'],] as const;const lines: Line[] = rows.map(([product, price, quantity]) => ({ product, price: parsePrice(price), quantity: Number.parseInt(quantity, 10),}));for (const line of lines) { console.log(line.product, (withTax(line) * line.quantity).toFixed(2));}capo 22.98strings 14.38as const makes each row a tuple of three strings, so the destructuring in map gets product, price and quantity as strings, and tsc would reject price in a field typed number until it goes through parsePrice. JavaScript lesson 2 explains why the regular expression comes before Number. tsc checks that the conversion happens; it can’t check that it is right.
- Rewrite
errors/l01_not_erasable.tswith erasable syntax only, and add aCurrencythat would have been anenum, so thatnoderuns it without a flag.
Solution
solutions/l01_ex2_erasable.ts:
// An enum becomes an object marked as const, and a union type of its valuesconst Currency = { CAD: 'CAD', EUR: 'EUR' } as const;type Currency = (typeof Currency)[keyof typeof Currency];
// A parameter property becomes a field and an assignmentclass Price { readonly #cents: number; readonly currency: Currency; constructor(cents: number, currency: Currency) { this.#cents = cents; this.currency = currency; } toString() { return `${(this.#cents / 100).toFixed(2)} ${this.currency}`; }}
// A namespace becomes a module, or here a frozen objectconst Tax = Object.freeze({ rate: 0.15 });
const price = new Price(999, Currency.EUR); // no assertion needed: new Price already has the type Priceconsole.log(`${price}`, Tax.rate, Object.values(Currency));9.99 EUR 0.15 [ 'CAD', 'EUR' ]The name Currency is used twice, once as a value and once as a type, which TypeScript allows because values and types live in separate spaces. (typeof Currency)[keyof typeof Currency] reads “the type of any property of the object”, here 'CAD' | 'EUR'; lesson 4 explains keyof and indexed types. Unlike the enum, the object has no reverse mapping and its values are the strings themselves, which is also what a JSON payload contains. The private field #cents replaces private: it is enforced by the engine, as JavaScript lesson 4 shows, where private is checked by tsc only.
- Copy
l01-emit’s configuration with"noEmitOnError": true, deletedist, and runtsc. What does it write, and what exit code does it return?
Solution
solutions/l01-ex3/tsconfig.json extends l01-emit’s configuration:
{ // Exercise 3: l01-emit's project, with noEmitOnError "extends": "../../l01-emit/tsconfig.json", "compilerOptions": { "noEmitOnError": true, "rootDir": "../../l01-emit/src", "outDir": "dist" }, "include": ["../../l01-emit/src"]}> npx tsc --pretty false../../l01-emit/src/main.ts(6,27): error TS2322: Type 'string' is not assignable to type 'number'.> node -e "console.log('dist exists:', require('node:fs').existsSync('dist'))"dist exists: falseThe same error, no dist folder, and exit code 1 instead of 2: 1 means that errors prevented the output, 2 that the output was written despite errors. That is the behavior of dotnet build, and the setting to use when tsc produces what you deploy. extends inherits the options of another file; relative paths in a configuration are resolved from the file that contains them, which is why rootDir and include are repeated here.