Lesson 31 · Module 5.2 — Tooling & ecosystem
tsconfig: module vs moduleResolutionTwo tsconfig options sound like synonyms and constantly get confused:
module and moduleResolution. They answer different questions. One controls
the import/export syntax TS writes out; the other controls how TS locates a file you import.
Separate those two questions and the whole config stops being a guessing game.
module = what module syntax TypeScript emits (ESM
import/export, or CommonJS require).
moduleResolution = how TypeScript finds the file behind an import
(which folders and extensions it searches, and whether it reads package.json). Emit vs find.
They are set independently.
| option | question it answers | example values |
|---|---|---|
| module | what does the output import/export syntax look like? | esnext, commonjs, nodenext |
| moduleResolution | how is "./x" or "lodash" resolved to a file? | bundler, node16, nodenext |
module shapes the JavaScript that comes out. moduleResolution shapes the
lookup that happens during checking — it never appears in the output, only decides which file a
specifier points at. A modern setup usually picks a matched pair (e.g. both nodenext) so emit
and resolution agree.
tsconfig, decoded{
"compilerOptions": {
"module": "nodenext", // EMIT: ESM or CJS, picked per package.json "type"
"moduleResolution": "nodenext", // FIND: use Node's rules — read "exports", check extensions
"target": "es2022" // (separate concern: which JS features to down-level)
}
}
Read it as two sentences: "emit module code the way modern Node expects" and "resolve imports the way modern Node does." The values happen to share a name here, but they are answering the two different questions from the rule above.
package.json joins inTypeScript does not decide everything alone — it reads two fields from package.json:
"type": "module" tells the toolchain a .js file is ESM (so import
stays import); its absence (or "commonjs") means CJS. Under
module: "nodenext", this field is what flips the emitted syntax per file."exports" is a map of which files a package lets you import and under which names. Modern
moduleResolution (node16/nodenext/bundler) honors
it — so an import can resolve to a different file than the old "look for index.js" rule found.So three things cooperate: module (emit), moduleResolution (find), and
package.json (the package's own declared rules that both consult).
Recall, don’t re-read. (Answers reveal on click.)
module compiler option actually control?import x from "lodash" into a real file path, that is governed by…nodenext, what does a package's "type": "module" field do?The canonical references are the
tsconfig reference — module and
moduleResolution,
plus the handbook's
Modules — Theory and
Modules — Reference (package.json type & exports).
Best way to feel it: flip module between commonjs and esnext
in the TS Playground and watch the emitted
require turn into import.
Got a "cannot find module" or a surprising require in your output? Paste the
tsconfig snippet and ask “is this an emit problem (module) or a
find problem (moduleResolution)?” Next lesson (5.3) closes the course — where
TypeScript itself is headed.