Developer tooling for TypeScript and React
Budget: 2-3 days.
Why this matters
Coming from Python you already have a stable mental model of the
toolchain: uv manages dependencies, ruff lints and formats, mypy or
pyright type-checks, pylsp powers the editor, and pyproject.toml
holds it all together. The JavaScript world has the same roles, but the
tools are split differently and the ecosystem moves faster. Knowing the
map up front means you spend time learning React, not fighting configs.
Python -> JS/TS map
| Role | Python | JavaScript / TypeScript |
|---|---|---|
| Runtime | CPython | Node.js (or Bun) |
| Package registry | PyPI | npm registry (registry.npmjs.org) |
| Package manager | uv, pip, poetry | npm, pnpm, bun |
| Lockfile | uv.lock, poetry.lock | package-lock.json, pnpm-lock.yaml, bun.lockb |
| Project config | pyproject.toml | package.json (+ tsconfig.json for TS) |
| Linter | ruff, pylint, flake8 | ESLint, Biome |
| Formatter | ruff format, black | Prettier, Biome |
| Type checker | mypy, pyright | tsc (the TypeScript compiler itself) |
| Editor LSP | pylsp, ruff-lsp, pyright | tsserver (built into VS Code) |
| Test runner | pytest | Vitest, Jest |
| Build tool / bundler | (none, Python is interpreted) | Vite, esbuild, Rollup, webpack |
What's actually different
- The compiler is the type checker.
tscplays the role ofmypy. It does not produce JavaScript at runtime in modern setups; Vite uses esbuild/swc for that. You runtsc --noEmitpurely to type-check, the same way you runmypy .. - There is no Python equivalent of bundlers. Browsers can't
importthousands of small files efficiently, so a build tool concatenates and tree-shakes your code. Vite is the current default. You already use it infrontend/. package.jsonispyproject.tomlplus a Makefile. Dependencies live underdependenciesanddevDependencies, and thescriptsblock is where you keepdev,build,lint,test. Run them withnpm run <name>.- Lint and format are still separate by default. ESLint lints, Prettier formats. Biome is the Rust-based newcomer that does both, and it's the closest thing to
ruffin spirit (single tool, fast, opinionated). For a new project today, Biome is reasonable; for an existing project with ESLint already wired up (like yours), staying on ESLint + adding Prettier is the path of least resistance. - Package manager choice matters less than in Python.
npmis fine.pnpmis faster and uses a content-addressable store (closest touv's vibe).bunis the all-in-one upstart. Pick one and stick with it; yourfrontend/already usesnpmbased onpackage-lock.json. tsconfig.jsonis where TypeScript's strictness lives. Turn onstrict: truefrom day one. That single flag enablesstrictNullChecksand friends, which is where most of TypeScript's value comes from.
What to learn
| Topic | Time |
|---|---|
package.json anatomy: dependencies, devDependencies, scripts, the type: module field | 0.5 day |
npm commands you'll actually use: install, run, ci, update, outdated | 0.5 day |
tsconfig.json: strict, target, module, moduleResolution, paths | 0.5 day |
ESLint v9 flat config: how eslint.config.js works, how plugins compose | 0.5 day |
| Prettier (or Biome) setup and editor-on-save formatting | 0.5 day |
Vite: dev server, vite build, plugins, env vars (import.meta.env) | 0.5 day |
| Vitest: writing a first test, running in watch mode | 0.5 day |
| VS Code TypeScript integration: go-to-def, rename symbol, organize imports | 0.5 day |
Resources
- Node.js docs: skim the "Learn" section, especially modules and
package.json. - npm docs: package.json reference: the canonical reference for fields and scripts.
- TypeScript tsconfig reference: every option, with examples. Use as a lookup, not a read-through.
- ESLint flat config docs: v9 ditched the old
.eslintrcformat. Read this before touchingeslint.config.js. - Prettier docs: tiny surface area on purpose. Read the "Options" page once.
- Biome docs: if you want to try the all-in-one alternative.
- Vite guide: start with "Why Vite" and "Features."
- Vitest docs: Jest-compatible API, but Vite-native and fast.
Starter project
Before you touch the FastVRP frontend, scaffold a small throwaway app from scratch. The goal is to feel each tool's role on its own, with no legacy config in the way.
Check your Node and npm
node -v # v20 or newer
npm -v # v10 or newer
If either is missing or old, install Node from nodejs.org
or via nvm. npm ships with Node, so you only install one thing.
Scaffold with the shadcn Vite starter
One command gives you React + TypeScript + Vite + Tailwind v4 + shadcn, with path aliases already wired:
npx shadcn@latest init -t vite
npm install
That saves you from manually configuring Tailwind, the @ alias in both
tsconfig.json and vite.config.ts, and the shadcn init. You could do
each step by hand (and it's worth reading the shadcn Vite guide
once to see what the starter is doing), but for a throwaway app the
starter is the cleanest path.
Add a component or two when you need them:
npx shadcn@latest add button input checkbox
The components land in src/components/ui/. Read them. They are short,
typed, and built on Radix primitives. This is the easiest way to see what
"good" React + TS + Tailwind code looks like.
Add Biome
The shadcn starter does not include a linter or formatter, so layer Biome on top:
npm install --save-dev --save-exact @biomejs/biome
npx biome init
Then add scripts to package.json:
"scripts": {
"format": "biome format --write .",
"lint": "biome lint .",
"check": "biome check --write ."
}
check runs lint and format together and fixes what it safely can. Install
the Biome VS Code extension and turn on "format on save" so you stop
thinking about formatting.
Check the .gitignore
The shadcn Vite starter ships with a .gitignore. Make sure it covers at
least:
node_modules
dist
.env
.env.local
.DS_Store
node_modules and dist are the big two: never commit your dependency
tree or build output. .env* keeps secrets out of git. .DS_Store keeps
macOS Finder droppings out of your commits.
Run Biome in CI
Add .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx biome ci .
biome ci is the read-only flavor: it lints, format-checks, and
import-sorts, and fails the job if anything is off. It does not write
files. That's the right behavior for CI: a PR that drifts from your rules
should fail, not silently auto-fix.
If you want type-checking on every push, add a second step:
- run: npx tsc --noEmit
What to build
A todo list with three features:
- Add, remove, and toggle items, using shadcn's
Button,Input, andCheckboxfor the UI. - Persist to
localStorageso a refresh keeps the list. - One typed shape,
type Todo = { id: string; text: string; done: boolean }, threaded through state, props, and the storage helper.
Style only with Tailwind utility classes. No router, no state library, no
backend. The point is to use useState, useEffect, typed props, and
utility-first styling on something that runs end to end.
What you'll learn
- How
tsc --noEmitcatches a typo that would have crashed at runtime. - What Biome flags out of the box, and which rules you actually want.
- How
npm run devandnpm run builddiffer in what they produce. - How Tailwind classes compose, and when to extract a component instead of duplicating a long class string.
- How shadcn components are wired: Radix primitives,
cn()for class merging, variants viaclass-variance-authority.
Throw it away when you're done. The reps are the point.
Exercise
Audit frontend/ and write down what role each tool currently plays:
package.json: dependencies, dev dependencies, scripts.package-lock.json: lockfile.eslint.config.js: lint rules.vite.config.js: build and dev server config.- (Missing)
tsconfig.json: add this when you start chapter 1's TypeScript port. - (Missing)
prettierconfig or Biome: add one before the TS port so formatting stays consistent. - (Missing) Vitest: add when you write the first test.
Then run each script once (npm run dev, npm run lint, npm run build) and read the output. The point is to know what each tool actually does, not to change anything yet.