target, lib & module
Learn what target, lib, module, and moduleResolution change in tsconfig.json, why bundlers need isolatedModules, and which settings fit a Vite or Node project.
- 01Pick a target and a libSay which JavaScript tsc writes for a target, and why lib describes built-ins without adding them to a browser.
- 02Set up importsChoose module and moduleResolution for a bundler or for Node, and fix an import path TypeScript rejects.
- 03Keep bundlers safeExplain why tools that compile one file at a time need isolatedModules and export type.
Three settings that describe where your code runs
A shop page shows the newest item in the cart. The code is plain JavaScript that every current browser runs. Yet in this project, TypeScript marks it, on purpose here:
const cart = ["tea", "cake", "jam"];const newest = cart.at(-1);console.log(`Newest item: ${newest}`);Line 1 makes a cart of three items. Line 2 reads the last one with .at(-1), a method that counts from the end of an array. Line 3 prints Newest item: jam when it runs. TypeScript marks line 2:
Property 'at' does not exist on type 'string[]'means "as far as I know, arrays have noatmethod".Try changing the 'lib' compiler option to 'es2022' or laternames the fix. The code is fine; the project's settings say.at()doesn't exist.
This example is checked with the setting "lib": ["es2021", "dom"], which says "the code runs where JavaScript stops at the 2021 edition". .at() came in 2022. That's what this lesson is about: the settings in tsconfig.json that describe where your code will run. Here are the ones that matter, in a typical browser project:
{ "compilerOptions": { "target": "es2022", "lib": ["es2022", "dom", "dom.iterable"], "module": "esnext", "moduleResolution": "bundler", "strict": true }, "include": ["src"]}- Line 3,
target, picks the edition of JavaScript tsc writes. Older editions mean tsc rewrites more of your code. - Line 4,
lib, lists the built-in things TypeScript knows about: methods like.at(), and the browser'sdocument. - Lines 5 and 6,
moduleandmoduleResolution, say howimportandexportare written, and how TypeScript finds the file animportnames.
tsconfig.json, three questions about where your code runs. target picks the JavaScript tsc writes, lib lists the built-in things TypeScript believes exist, and module decides how imports are written and found.You're cooking dinner at a friend's place. Before you start, you ask about their kitchen. Their oven can't grill, so you change the recipe to bake instead. You read their pantry list so you only plan dishes with what they have. And you remember that a name on the list is only a note: if the jar is empty, the dish still fails.
- In real life: You ask what kind of kitchen your friend has
- In TypeScript:
tsconfig.jsonsays where your code will run - In real life: Their old oven has no grill, so you rewrite the step to use the oven
- In TypeScript:
targetmakes tsc rewrite syntax an older browser doesn't know - In real life: You check their pantry list before you plan a dish
- In TypeScript:
libtells TypeScript which built-in methods exist - In real life: Writing "saffron" on the pantry list doesn't put saffron on the shelf
- In TypeScript: Adding to
libdoesn't add a method to an old browser
Output settings describe where your code runs. target picks which JavaScript tsc writes. lib lists the built-in things TypeScript believes exist. module and moduleResolution say how imports are written and found. None of them adds a missing feature to a browser.
target: which JavaScript tsc writes
JavaScript gets a new edition every year: ES2015, ES2016, and so on (ES is short for ECMAScript, the language's official name). Each edition adds syntax, like ?. in ES2020. target names the oldest edition your users' browsers understand. When your code uses newer syntax, tsc rewrites it into older syntax that does the same thing. This is called downleveling (the How new syntax is downleveled lesson shows how). Here is a small class that picks a page theme:
type Settings = { theme?: { color: string } }; class ThemePicker { fallback = "light"; pick(settings: Settings) { return settings.theme?.color ?? this.fallback; }} const picker = new ThemePicker();console.log(`Theme: ${picker.pick({})}`);- Line 1 says
Settingsmay have athemewith acolor. - Line 4 is a class field:
fallbackis set on every newThemePicker, written inside the class body (Class basics covers fields). - Line 6 reads the color with
?., which stops at a missing theme, and falls back with??(both are in Nullish coalescing & optional chaining). - Line 11 picks a theme for empty settings and prints
Theme: light.
Now pick a target and read what tsc writes for each one:
type Settings = { theme?: { color: string } }; class ThemePicker { fallback = "light"; pick(settings: Settings) { return settings.theme?.color ?? this.fallback; }} const picker = new ThemePicker();console.log(`Theme: ${picker.pick({})}`);"es5"var ThemePicker = /** @class */ (function () {
function ThemePicker() {
this.fallback = "light";
}
ThemePicker.prototype.pick = function (settings) {
var _a, _b;
return (_b = (_a = settings.theme) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : this.fallback;
};
return ThemePicker;
}());
var picker = new ThemePicker();
console.log("Theme: ".concat(picker.pick({})));the class, the field, ?., ??, const, and the template literalTheme: lightES5 is from 2009, before classes, const, and template literals. tsc rewrites all of them: the class becomes a function with ThemePicker.prototype.pick, const becomes var, and the template literal becomes .concat(). ?. and ?? become a long check against null and undefined. It still prints Theme: light.
Start at es5 and go up. The output shrinks as the target gets newer, because there's less to rewrite. At es2022 it's your code without its types. Every version prints Theme: light: downleveling changes how the code is written, not what it does. The table below lists when the syntax you use most arrived. Read each row as: the edition, what it added, and what tsc does when your target is older.
| Edition | What it added | With an older target |
|---|---|---|
| ES2015 | const, let, classes, arrow functions, and template literals. | Below ES2015, const becomes var, a class becomes a function, and a template literal becomes .concat(). |
| ES2017 | async functions and await. | Below ES2017, an async function becomes a helper called __awaiter that runs a generator. |
| ES2020 | ?. (optional chaining) and ?? (nullish coalescing). | Below ES2020, both become a long check against null and undefined. |
| ES2022 | Class fields, written inside the class body, and #private fields. | Below ES2022, a field moves into the constructor, and a #private field becomes a WeakMap. |
Here is the catch. target rewrites syntax: the symbols and keywords of the language. It never adds methods. Here is what tsc writes for the overview's code with "target": "es2017":
const cart = ["tea", "cake", "jam"];
const newest = cart.at(-1);
console.log(`Newest item: ${newest}`);It's the same three lines. cart.at(-1) on line 2 is a method call, which is ordinary syntax, so nothing needs rewriting. In a browser without .at(), like one from 2020, line 2 stops the code:
TypeError: cart.at is not a functionA missing method needs a polyfill, a piece of code that adds it while the page runs (the Transpilers & polyfills lesson covers both). TypeScript never adds polyfills.
Two more facts about target. If you leave it out, TypeScript 5.9 uses es5, which rewrites almost everything; TypeScript 6.0 uses the newest edition instead and deprecates es5, so write the target you want. And if a bundler like Vite builds your page, it ignores this setting and uses its own: build.target in Vite's settings. In such a project, target still matters for checking, as the next section shows.
lib: which built-in types TypeScript knows
TypeScript doesn't look inside a browser to see which methods exist. It reads lib files: files that describe, in types, everything JavaScript and the browser have built in. Each edition of JavaScript has one, and so does the browser's page. Here is the part of lib.es2022.array.d.ts that describes .at() (the comment is shortened):
interface Array<T> { /** * Returns the item located at the specified index. */ at(index: number): T | undefined;}Line 1 adds to the Array type. Line 5 says an array has a method at that takes a number and gives back an item or undefined. There's no code here that finds the item; it's only a description. The lib setting picks which of these files TypeScript reads:
"es2022"describes JavaScript up to the 2022 edition:Array,Math,Promise,.at(), and so on."dom"describes the browser's page:document, elements, events,fetch, andconsole."dom.iterable"adds looping withfor...ofover lists the page gives you, likedocument.querySelectorAll("li"). In TypeScript 6.0 it's part of"dom".
When you write lib, your list replaces the whole default. Try four lists on the same code. It reads the newest item, then writes it into every row of a list on the page:
const cart = ["tea", "cake", "jam"];const newest = cart.at(-1); const rows = document.querySelectorAll("li");for (const row of rows) { row.textContent = newest ?? "";}console.log(`Updated ${rows.length} rows`);["es2022", "dom", "dom.iterable"]No errorsUpdated 3 rowsTypeScript knows everything this code uses: .at() from ES2022, document and console from dom, and looping over a list of elements from dom.iterable. No errors. On the page, it fills all 3 rows with jam and prints Updated 3 rows.
With all three entries, there are no errors. Drop to es2021 and line 2 is marked, as in the overview. Leave out dom.iterable and the for...of loop on line 5 is marked. Leave out dom and TypeScript can't find document or even console. In every case, press Edit & run: the code still fills the rows and prints Updated 3 rows, because the browser has all of these things. lib only changes what TypeScript believes.
lib is a description. With es2022 in it, TypeScript believes .at() exists and reports nothing. A browser from 2020 never had the method, so cart.at(-1) still throws a TypeError there.That cuts both ways. A lib entry the browser doesn't really have hides a crash, as the TypeError above showed. So pick lib to match the oldest browser you support, or add a polyfill for each newer method you use.
When you leave lib out, TypeScript picks it from target: "target": "es2022" gives you es2022, dom, and dom.iterable (and three smaller ones you won't need to think about). Code that runs in Node, not a browser, leaves out dom and adds Node's own types with "types": ["node"], after installing the @types/node package.
module and moduleResolution: how imports work
Real projects split code into files that import from each other (the Import & export lesson covers the syntax). The shop keeps its price formatting in one file:
export function formatPrice(price: number) { return `$${price.toFixed(2)}`;}Line 1 exports formatPrice, and line 2 turns a number like 7 into $7.00. Another file imports it. Pick a module setting to see how tsc writes that import:
import { formatPrice } from "./format"; export function cartLabel(total: number) { return `Total: ${formatPrice(total)}`;} console.log(cartLabel(7));"esnext"import { formatPrice } from "./format";
export function cartLabel(total) {
return `Total: ${formatPrice(total)}`;
}
console.log(cartLabel(7));esnext writes ES modules: the import and export lines stay as you wrote them, and only the types go. Browsers and bundlers read this form.
In cart.ts, line 1 imports formatPrice, line 3 exports cartLabel, and line 7 prints Total: $7.00. With esnext and preserve, the output keeps import and export: these are ES modules, the form browsers and bundlers read. With commonjs, it uses require and exports, the form Node used before ES modules (the CommonJS vs ES modules lesson compares them).
module also tells TypeScript what the place your code runs can do, so it changes checking even when tsc writes nothing. This file waits for prices with await outside any function, which only newer module forms allow. It's checked with "module": "es2015", and the error is on purpose:
import { formatPrice } from "./format"; const prices = await Promise.resolve([3, 4]);console.log(formatPrice(prices[0] + prices[1]));Line 3 waits for a list of two prices, and line 4 prints their total, $7.00. TypeScript marks line 3: Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', and a few more. In plain words: await at the top of a file came with ES2022 modules, and es2015 modules don't have it. With this lesson's default, esnext, the error is gone.
The second setting, moduleResolution, is how TypeScript finds the file an import names. Bundlers and Node find files differently, so TypeScript has a mode for each:
"bundler"works like Vite:"./format"is enough, because the bundler tries endings like.tsand.jsitself."nodenext"works like Node (the Module resolution lesson explains Node's rules). In an ES module, Node wants the whole file name."module": "nodenext"sets it for you.
Try both. The project has cart.ts, format.ts, and a package.json with "type": "module", which tells Node the files are ES modules. Type a path on line 1:
import { formatPrice } from "./format"; console.log(formatPrice(3));"nodenext"./format1 error: line 1Node needs the whole file name in an ES module, so nodenext says Relative import paths need explicit file extensions and suggests the fix: Did you mean './format.js'?
With nodenext, ./format gets Relative import paths need explicit file extensions in ECMAScript imports, and the message suggests ./format.js. That surprises everyone the first time: the file you wrote is format.ts. But tsc turns it into format.js, and that's the file Node loads. tsc never changes the paths you write, so you write the name the file will have. While checking, TypeScript knows that ./format.js means format.ts.
bundler, "./format" is enough: the bundler tries the endings. With nodenext, you write the name the file has after the build, "./format.js". TypeScript reads format.ts for the types; Node later loads format.js.The table below matches the two settings to where your code runs. Read each row as: the situation, then the two settings.
| Where the code runs | module | moduleResolution |
|---|---|---|
| A bundler like Vite builds the page | "esnext" or "preserve" | "bundler": imports can leave out the ending, like ./format. |
| Node runs the JavaScript tsc writes | "nodenext" | Set by nodenext for you. ES module imports need the ending, like ./format.js. |
Node runs your .ts files directly | "nodenext" | The same as above, with .ts endings. The next lesson covers this setup. |
Bundlers and isolatedModules
tsc reads your whole project at once. Fast build tools don't. Vite, esbuild, SWC, and Babel turn each .ts file into JavaScript on its own, without looking at the files it imports (the Bundlers & build tools lesson covers what they do). That's what makes them fast. But some TypeScript code can only be compiled correctly by looking at another file. Here is a types.ts with a type and a function:
export type CartItem = { name: string; price: number }; export function formatPrice(price: number) { return `$${price.toFixed(2)}`;}Line 1 exports a type, CartItem. Line 3 exports a function, formatPrice. Many projects add an index.ts that passes on everything from a folder, so other files import from one place. Pick how index.ts passes them on:
export { CartItem, formatPrice } from "./types";falseexport { formatPrice } from "./types";export { CartItem, formatPrice } from "./types";No errorsSyntaxError: no export named 'CartItem'tsc reads both files, sees that CartItem is only a type, and leaves it out of index.js. A tool that compiles one file at a time can't see types.ts, so it keeps CartItem, and the browser stops: types.js has no export called CartItem. TypeScript reports nothing.
With the first button, tsc and the one-file tool disagree. tsc read types.ts, knew CartItem is only a type, and left it out. The one-file tool kept it, and a browser stops with:
SyntaxError: The requested module './types.js' does not provide an export named 'CartItem'types.js has no CartItem: types don't exist in JavaScript. The setting "isolatedModules": true fixes this before it happens. It tells TypeScript: "warn me about anything a tool that sees one file at a time would get wrong". It doesn't change what tsc writes. The second button shows the warning, and the last two show the fix: mark the type with type, so any tool can remove it from this file alone.
CartItem is only a type. A tool that compiles one file at a time sees index.ts alone. It can't tell, so it keeps CartItem, and the browser can't find it.The table below lists the three cases TypeScript's documentation gives for isolatedModules. Read each row as: the code, the message, and what to write instead.
| When you write | TypeScript says | Write instead |
|---|---|---|
Passing on a type with plain export { CartItem } | Re-exporting a type when 'isolatedModules' is enabled requires using 'export type' | Write export type { CartItem }, or type in front of the name. |
Reading a declare const enum from a library's types | Cannot access ambient const enums when 'isolatedModules' is enabled | Use a plain object or a union of strings instead of the const enum. |
A namespace in a file with no import or export | Namespaces are not allowed in global script files when 'isolatedModules' is enabled | Add an import or export, or set moduleDetection to force. |
Vite's own documentation says to set isolatedModules to true. Many projects go one step further with "verbatimModuleSyntax": true, which turns isolatedModules on and also asks you to mark every imported type with type. The Modules & type imports lesson covers it.
The output settings of a Vite checkout page
Here are the output settings Vite's TypeScript starter gives a new checkout page (the vanilla-ts template, shortened to the lines this lesson covers):
{
"compilerOptions": {
"target": "es2023",
"module": "esnext",
"lib": ["ES2023", "DOM"],
"types": ["vite/client"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true
},
"include": ["src"]
}- Line 3,
target: ES2023. Vite ignores it when it builds, but TypeScript still uses it for checking. - Line 4,
module:esnext, ES modules, the form Vite reads. - Line 5,
lib: ES2023 and the page. Line 6,types, adds Vite's own types, likeimport.meta.env. Line 7,skipLibCheck, skips checking the type files of installed packages, which saves time. - Line 9,
moduleResolution:bundler, so imports can leave out the ending. Line 10 allows imports that end in.ts, because Vite reads those files itself. - Line 11,
verbatimModuleSyntax, makes Vite's one-file compiling safe. Line 12,moduleDetection, treats every file as a module, even one with noimport. - Line 13,
noEmit: tsc only checks. Vite writes the JavaScript.
The template is made for TypeScript 6.0, where "DOM" includes DOM.Iterable. With TypeScript 5.9, its lib line has a gap. Here is checkout code with the template's lib, checked with 5.9, and the error is on purpose:
const rows = document.querySelectorAll("li");for (const row of rows) { row.textContent = row.textContent?.toUpperCase() ?? "";}console.log(`Updated ${rows.length} rows`);Line 1 finds every row of the list. Lines 2 to 4 write each row's text in capitals, and line 5 prints Updated 3 rows. TypeScript marks the loop on line 2: Type 'NodeListOf<HTMLLIElement>' must have a '[Symbol.iterator]()' method. In plain words: "I don't know this list can be looped over". Add "DOM.Iterable" to lib, and the same code has no errors:
const rows = document.querySelectorAll("li");for (const row of rows) { row.textContent = row.textContent?.toUpperCase() ?? "";}console.log(`Updated ${rows.length} rows`);Every message in this lesson points at one setting. Sort the problems below by what fixes them.
- An old browser stops at
?.with aSyntaxError - tsc's output has
varwhere you wroteconst Property 'at' does not exist on type 'string[]'. Do you need to change your target library?Cannot find name 'document'.Relative import paths need explicit file extensions in ECMAScript importsTop-level 'await' expressions are only allowed when the 'module' option is set toRe-exporting a type when 'isolatedModules' is enabled requires using 'export type'.- The browser says a module
does not provide an export named 'CartItem'
Sort each problem by the change that fixes it.
Common mix-ups
- "An older target makes
.at()work in old browsers." No.targetrewrites syntax like?.. Methods stay as you wrote them, and a browser without them throws aTypeError. - "Adding
es2023to lib makes.toSorted()work everywhere." No.libis a description for TypeScript. It adds nothing to the browser. - "
"lib": ["es2022"]adds ES2022 to what I had." No. Your list replaces the default. Without"dom"in it,documentandconsoleare gone. - "With
noEmit,targetandmoduledon't matter." No. They still decide the defaultliband checks like top-levelawait. - "tsc turns
./formatinto./format.jsfor Node." No. tsc writes your import paths as they are. Withnodenext, you write./format.jsyourself. - "
isolatedModuleschanges the JavaScript tsc writes." No. It only adds warnings about code that tools compiling one file at a time would get wrong.
target and lib are the easiest pair to mix up. The table compares them. Read each row as a question, then each setting's answer.
| Question | target | lib |
|---|---|---|
| What does it change? | Which syntax tsc writes in the JavaScript. | Which built-in things TypeScript believes exist while it checks. |
| Does it change the JavaScript tsc writes? | Yes. Older targets mean more rewriting. | No. It only changes which errors you see. |
| Does it add missing methods to old browsers? | No. It rewrites syntax, never methods like .at(). | No. A polyfill or a newer browser has to provide them. |
| What if you leave it out? | TypeScript 5.9 uses es5. TypeScript 6.0 uses the newest edition. | TypeScript picks it from target and adds dom, plus dom.iterable from ES2015 on. |
Practice exercises
Some exercises are TypeScript code, checked with the settings they name; others are a tsconfig.json to fix. For code, press Edit & run on the starter: the errors update as you type. Try a guess first, then use the hints.
This project has "lib": ["es2022", "dom"]. TypeScript marks line 2. Which lib entry does toSorted need?
const prices = [4, 3, 5];const cheapestFirst = prices.toSorted();console.log(cheapestFirst);const prices = [4, 3, 5];const cheapestFirst = prices.toSorted();console.log(cheapestFirst);toSorted came in ES2023, and this project's lib stops at es2022. With "lib": ["es2023", "dom"] the error is gone. The code is the same; only tsconfig.json changes.
Which is the oldest target for which tsc leaves line 4 exactly as written?
type Settings = { theme?: { color: string } }; function themeColor(settings: Settings) { return settings.theme?.color ?? "light";}console.log(themeColor({}));type Settings = { theme?: { color: string } }; // "target": "es2020" or newer keeps line 5 as it is.// "target": "es2019" or older rewrites ?. and ?? into checks.function themeColor(settings: Settings) { return settings.theme?.color ?? "light";}console.log(themeColor({}));?. and ?? came in ES2020, so es2020 is the oldest target that keeps the return line as written. With es2019, tsc rewrites them into a check against null and undefined. Either way it prints light.
This project uses "module": "nodenext", and its package.json says "type": "module". Fix line 1. What should the path be?
import { formatPrice } from "./format"; console.log(formatPrice(4));import { formatPrice } from "./format.js"; console.log(formatPrice(4));With nodenext, an ES module import needs its ending, and the ending is the one Node will load: ./format.js. TypeScript reads format.ts for the types, so the error is gone.
This project has "isolatedModules": true, and index.ts passes on what types.ts exports. Fix the warning. Which name needs type?
export { CartItem, formatPrice } from "./types";export type { CartItem } from "./types";export { formatPrice } from "./types";CartItem is only a type. export type { CartItem } tells a one-file tool to remove it, and formatPrice stays a normal export. isolatedModules has nothing left to warn about.
This project has "target": "es2017" and "lib": ["es2023", "dom"]. TypeScript reports nothing, and a new browser prints 3. What does a 2021 browser show when it runs line 8? Then rewrite it to work there.
type Order = { id: number; paid: boolean }; const orders: Order[] = [ { id: 1, paid: true }, { id: 2, paid: false }, { id: 3, paid: true },];const lastPaid = orders.findLast((order) => order.paid);console.log(lastPaid?.id);type Order = { id: number; paid: boolean }; const orders: Order[] = [ { id: 1, paid: true }, { id: 2, paid: false }, { id: 3, paid: true },];const paidOrders = orders.filter((order) => order.paid);const lastPaid = paidOrders[paidOrders.length - 1];console.log(lastPaid?.id);tsc keeps orders.findLast(…) as written, so a 2021 browser throws TypeError: orders.findLast is not a function. The fix makes lib match target (es2017), and uses filter and the last index instead. It prints 3 everywhere.
A teammate copied the shop's tsconfig.json for a script in scripts/ that tsc builds and Node runs. Fix its output settings. What should module be?
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022", "dom"],
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "dist",
"strict": true
},
"include": ["scripts"]
}{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022"],
"types": ["node"],
"module": "nodenext",
"outDir": "dist",
"strict": true
},
"include": ["scripts"]
}A script Node runs has no page, so lib drops dom, and "types": ["node"] adds Node's own things, like process. "module": "nodenext" writes what Node loads and checks imports Node's way; it also sets moduleResolution, so that line goes.
Check your understanding
For each question, ask which setting is involved: target for syntax, lib for built-in methods, module and moduleResolution for imports. The quiz shows its code without the checker, so the error list doesn't give the answers away.
Question 1 of 7What does
targetchange?Choose an answer to see the explanation.
Question 2 of 7What does TypeScript say about line 3 here?
Read the code, then predict// tsconfig.json: "lib": ["es2021", "dom"] const cart = ["tea", "cake"]; console.log(cart.at(-1));Choose an answer to see the explanation.
Question 3 of 7With this tsconfig, a browser from 2020 runs the JavaScript tsc writes. What happens?
Read the code, then predict// tsconfig.json: "target": "es2017", "lib": ["es2023", "dom"] const cart = ["tea", "cake"]; console.log(cart.at(-1));Choose an answer to see the explanation.
Question 4 of 7Which lib list knows
documentbut, in TypeScript 5.9, can't loop overdocument.querySelectorAll("li")withfor...of?Choose an answer to see the explanation.
Question 5 of 7This file is an ES module checked with
nodenext. What does TypeScript want on line 2?Read the code, then predictTypeScript// tsconfig.json: "module": "nodenext"; package.json: "type": "module" import { formatPrice } from "./format";Choose an answer to see the explanation.
Question 6 of 7What does turning on
isolatedModuleschange in the JavaScript tsc writes?Choose an answer to see the explanation.
Question 7 of 7Why does a bundler need
export type { CartItem }instead ofexport { CartItem }?Choose an answer to see the explanation.
Key takeaways
targetpicks which JavaScript tsc writes. Older targets rewrite newer syntax; no target adds methods.liblists the built-ins TypeScript believes exist. Your list replaces the default, so keepdomfor a page.- In TypeScript 5.9, looping over a
NodeListneedsdom.iterable; 6.0 puts it insidedom. moduledecides how imports are written and which module features are allowed, like top-levelawait.moduleResolutionbundlerlets you leave out endings;nodenextwants./format.js.- Bundlers compile one file at a time.
isolatedModuleswarns about code they'd get wrong;export typefixes it.
Remember the one-liner.
target rewrites syntax, lib describes built-ins, module shapes imports, and none of them changes what the browser has.
Coming next: Type checking vs type stripping. Vite, esbuild, and Node run TypeScript without checking it, so you'll see where tsc still fits, from your editor to CI.