Setup Summary
TypeScript modules, resolution and bundlers
Two settings in tsconfig.json
| Setting | Question it answers |
|---|---|
module | What import/export style does the output JS use? |
moduleResolution | How does TypeScript find the file for import x from "./db"? |
module values
commonjs: output usesrequire()(old Node style)esnext/es2022: output usesimport/export, but assumes a bundler or browser runs itnodenext: follows Node."type": "module"in package.json gives ESM, otherwise CommonJS- Note: this is what typescript compile to
moduleResolution values
node(also callednode10): old CommonJS rules, no file extension needed. Being deprecated in newer TypeScript.nodenext: modern Node rules. Relative imports in ESM need the extension:./db.js- so we need to do
import something from "file.js"
- so we need to do
bundler: no extension needed, trusts a bundler to find the files
What was there before
- Old default:
module: commonjs+moduleResolution: node nodenextwas added in TypeScript 4.7 (2022) because Node got real ESM support with stricter rules
Convention now
| Situation | module | moduleResolution |
|---|---|---|
Node backend, run with plain node | NodeNext | NodeNext |
| Frontend with a bundler (Vite, SvelteKit) | ESNext | Bundler |
| Old Node project (CommonJS) | CommonJS | Node |
- The two settings go as a pair. Do not mix them.
- New project: use ESM (
"type": "module"). - Existing project: keep what it already uses. Check package.json.
.mjs= always ESM,.cjs= always CommonJS
ESM things to remember
- Import with
.jseven though the source is.ts:import { x } from "./db.js" - Top-level
awaitworks - No
__dirname. Useimport.meta.dirname
Why Node does not need a bundler
tscdoes NOT bundle. It translates each.tsto one.js, file by file.- Node reads files from the local disk at startup (fast), including
node_modules. - So
node dist/server.jsjust works.
Why a browser needs a bundler
- Every file is a network request, so hundreds of files is slow.
- A browser cannot read
node_modules, and does not understand TypeScript or.svelte.
What a bundler does
- Starts at the entry file and follows every
import - Joins the code into a few files
- Transforms TypeScript, Svelte, SCSS into plain JS/CSS
- Shrinks the code (minify) and removes unused code (tree shaking)
- Handles images and CSS, with fingerprinted names for caching
tsc = translator and type checker. Bundler = translator + packer.
Bundler examples
- Vite: most popular for frontends. SvelteKit uses it.
- esbuild: very fast, written in Go. Used inside
tsxandvitest. - webpack: older, common in existing projects, slower
- Rollup, Parcel, Turbopack, tsup: other options (
tsupis popular for Node libraries)
When backends bundle anyway
- One small file for serverless (AWS Lambda), faster start
- Faster builds in big repos
- Smaller Docker images
Note: most bundlers do NOT check types. Teams often run tsc --noEmit for type checks and a bundler to build.