Web Dev Tools Guide
Master the toolchain behind modern web development โ Node.js and npm, package.json, bundlers and Vite, TypeScript, linters, browser DevTools, environment variables, testing, and shipping to production. Every command has a Copy button.
Copy button on each code block ยท โ Beginner โ Intermediate โ Advanced01 The Modern Web Toolchain Beginner
A single index.html file is enough to start, but real projects quickly grow a toolchain โ a set of programs that work together to run, check, bundle and ship your code. The good news: every tool has one job, and once you know the job, the maze becomes a map.
This guide walks the whole chain from the runtime you install to the command that puts your site online. Nothing here requires a framework โ these are the fundamentals behind React, Vue, Next.js and everything else.
What each tool actually does
A typical day-to-day workflow
- Install dependencies once with
npm install. - Develop with a live dev server:
npm run dev. - Check as you go:
npm run lintandnpm run test. - Build an optimized bundle:
npm run build. - Preview the production build locally:
npm run preview. - Deploy the output folder to a host.
package.json) keep every machine and teammate in sync.02 Node.js & npm Beginner
Node.js is a JavaScript runtime built on Chrome's V8 engine. It lets you run JavaScript from the terminal โ for scripts, servers, and the build tools that power web development. npm (Node Package Manager) ships with Node and is how you install libraries and run project commands.
Check what you have
node --version
npm --version
# Expected output (versions will differ)
# v22.11.0
# 10.9.0
Install Node the right way
Prefer a version manager so you can switch Node versions per project. nvm (macOS/Linux) and fnm are the common choices.
# macOS / Linux โ install nvm from nvm.sh, then:
nvm install --lts
nvm use --lts
nvm alias default lts/*
# pin a version for a project
echo "22" > .nvmrc
# Windows: use nvm-windows or fnm
Run JavaScript without a browser
# run a file
node app.js
# open the interactive REPL
node
# run a one-off expression
node -e "console.log('Hello from Node')"
Start a project
npm init -y creates a default package.json. Without -y it asks you questions interactively.
mkdir my-app && cd my-app
npm init -y
Install a package
# dependency used by your app at runtime
npm install axios
# dev dependency used only while developing
npm install --save-dev eslint
# run a package once without installing it
npx create-vite@latest my-app
npm install -g puts a package on your whole machine. Avoid it for build tools โ a global version can conflict with a project that expects a different one. Use npx or a local install instead.node_modules is huge and machine-specific. It should never be committed โ it belongs in .gitignore. Anyone can recreate it with npm install.03 package.json & Semantic Versioning Intermediate
package.json is the identity card and control panel of a Node project: its name, its dependencies, and the scripts that run it. Understanding this one file unlocks every JavaScript toolchain.
Anatomy of a package.json
{
"name": "my-app",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"test": "vitest run"
},
"dependencies": {
"axios": "^1.7.9"
},
"devDependencies": {
"vite": "^6.0.0",
"eslint": "^9.17.0"
}
}
The three dependency buckets
- dependencies โ needed for the app to run in production (e.g. a UI library, an HTTP client).
- devDependencies โ only needed while building or testing (e.g. Vite, ESLint, TypeScript).
- peerDependencies โ packages a library expects you to provide (common in plugins).
Semantic versioning (semver)
A version is MAJOR.MINOR.PATCH. Bump MAJOR for breaking changes, MINOR for new features, PATCH for fixes. The symbols in front decide how far npm update may move a package.
The lockfile
package-lock.json records the exact version of every installed package (including transitive ones). Commit it. It is what makes installs reproducible: the same lockfile produces the same tree on every machine and in CI.
npm install axios, npm writes ^1.7.9. That allows minor and patch updates automatically โ convenient, but it means fresh installs can drift unless the lockfile is committed."type": "module" to write modern import/export syntax in .js files instead of the older require().04 Essential npm Commands Beginner
You will use a dozen npm commands constantly. Here they are with what they actually do.
# install everything from package.json
npm install
# clean, exact install for CI (uses the lockfile)
npm ci
# add / remove packages
npm install react react-dom
npm install --save-dev vitest
npm uninstall axios
# update within the allowed semver range
npm update
# see what is outdated
npm outdated
# security audit and automatic fixes
npm audit
npm audit fix
# list installed packages
npm ls
# explain why a package is present
npm why axios
Running scripts
npm run dev
npm run build
npm run lint
npm test # shorthand for npm run test
Useful extras
# list every available script
npm run
# run a script without the "run" keyword (if no conflict)
npm start
# clear the local cache when installs get weird
npm cache clean --force
# check the registry is reachable
npm ping
npm install while developing and npm ci in automated builds. npm ci deletes node_modules, installs strictly from the lockfile, and fails if the two disagree โ exactly what you want in CI.npm run with no arguments prints every script, which is the fastest way to rediscover a project's commands when you come back to it months later.05 Bundlers & Vite Intermediate
Modern projects split code across many files and import packages from node_modules. A bundler follows every import and packages it into a few optimized files the browser can load.
Why we bundle
- Combine hundreds of modules into a handful of requests.
- Transform TypeScript, JSX and modern syntax into browser-ready JS.
- Optimize โ minify, drop dead code (tree shaking), and split chunks.
- Develop โ serve a live-reloading dev server with hot module replacement (HMR).
Create and run a Vite project
# scaffold in the current folder
npm create vite@latest . -- --template vanilla
# templates: vanilla, react, vue, svelte, preact, lit, solid
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev # dev server with HMR
What the scripts do
npm run devโ starts a fast dev server; changes appear instantly without a reload.npm run buildโ writes an optimized production bundle todist/.npm run previewโ serves the builtdist/so you can test it locally.
A minimal Vite config
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
server: { port: 5173, open: true },
build: { outDir: 'dist', sourcemap: true }
})
Other bundlers you will meet
06 Transpilers: TypeScript & Babel Intermediate
Browsers cannot run everything we like to write. A transpiler converts modern or typed source into JavaScript that runs everywhere.
TypeScript in one minute
TypeScript adds static types on top of JavaScript. Types are erased at build time but catch whole classes of bugs while you type. Install it and compile a file:
npm install --save-dev typescript
npx tsc --init # create tsconfig.json
npx tsc app.ts # compile one file to app.js
npx tsc --watch # recompile on every save
// app.ts โ types are optional but catch mistakes early
type User = { id: number; name: string; admin?: boolean }
function greet(user: User): string {
return `Hello, ${user.name}`
}
greet({ id: 1, name: 'Laksh' }) // โ
greet({ id: 2 }) // โ error: name is missing
Babel โ the compatibility layer
Babel rewrites new JavaScript syntax into older syntax for legacy browsers, and powers JSX and many plugins. Many projects now use Vite or SWC instead, but you will still see Babel in tools and configs.
npm install --save-dev @babel/core @babel/preset-env
// babel.config.json
{
"presets": [
["@babel/preset-env", { "targets": "defaults" }]
]
}
07 Linters & Formatters Beginner
Two tools keep a codebase healthy: a linter finds likely bugs and bad patterns, and a formatter enforces a consistent look. Run both automatically and you stop arguing about style entirely.
ESLint โ catch problems
npm install --save-dev eslint
npx eslint --init # interactive setup
npx eslint . # lint the whole project
npx eslint . --fix # auto-fix what it can
// eslint.config.js (flat config)
export default [
{
files: ['**/*.js'],
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
eqeqeq: ['error', 'always']
}
}
]
Prettier โ format everything
npm install --save-dev prettier
npx prettier --check .
npx prettier --write .
// .prettierrc
{
"semi": false,
"singleQuote": true,
"printWidth": 100
}
Wire them into scripts and the editor
// package.json
"scripts": {
"lint": "eslint .",
"format": "prettier --write ."
}
08 Browser DevTools Beginner
Every browser ships a built-in development environment. It is the most valuable debugging tool you own โ and it is free. Open it with F12, Ctrl+Shift+I, or โ+Option+I.
The panels that matter
Console superpowers
console.log('value:', value)
console.warn('deprecated')
console.error('something broke')
// inspect objects as a table
console.table([{ id: 1, name: 'A' }, { id: 2, name: 'B' }])
// group related logs
console.group('Request')
console.log('url', url)
console.log('status', status)
console.groupEnd()
// time a block of code
console.time('loop')
for (let i = 0; i < 1e6; i++) {}
console.timeEnd('loop')
// select the page element that triggered this call
const el = document.querySelector('.card')
console.log(el)
// inspect computed styles quickly
getComputedStyle(el).display
09 Debugging JavaScript Intermediate
Printing to the console is a fine start, but a real debugger lets you pause execution and look at everything at that exact moment.
Breakpoints
- Open Sources and find your file (or use the Filesystem workspace).
- Click a line number to set a breakpoint โ the code pauses right before it runs.
- Use the controls to step over, step into, step out, or continue.
A hardcoded breakpoint in code
function processOrder(order) {
const total = order.items.reduce((sum, i) => sum + i.price, 0)
debugger // execution pauses here when DevTools is open
return total
}
Conditional and DOM breakpoints
- Conditional: right-click a line โ Add conditional breakpoint โ e.g.
i === 42, so it only pauses when it matters. - DOM: right-click an element in Elements โ Break on โ subtree modifications to catch what is mutating the page.
- Logpoints: right-click โ Add logpoint to log a value without editing code or pausing.
Reading the error
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
at render (app.js:42:18)
Read it bottom-up: the file and line after at is where it broke, and the message names the value (undefined) and the property (map). Set a breakpoint there and inspect the variable.
.then() or async function may point at the promise machinery. Use DevTools' Async stack traces (the โAsyncโ checkbox in the Call Stack) to see the real caller.console.log calls once the bug is fixed, or add an ESLint rule to flag stray console statements before they reach production.10 Environment Variables & Config Intermediate
API URLs, keys and feature flags differ between your laptop and production. Environment variables keep those values out of your source code.
Node / build-time variables
# .env
DATABASE_URL=postgres://localhost:5432/dev
API_SECRET=super-secret-value
// load .env into process.env (Node)
import 'dotenv/config'
console.log(process.env.DATABASE_URL)
Vite exposes variables to the client
Vite only exposes variables prefixed with VITE_, and only those are inlined into the browser bundle.
# .env
VITE_API_URL=https://api.example.com
// accessible in browser code
const base = import.meta.env.VITE_API_URL
const isDev = import.meta.env.DEV
const mode = import.meta.env.MODE
Keep secrets out of Git
# .gitignore
.env
.env.local
.env.*.local
VITE_ ends up in the JavaScript the browser downloads. Never put API secrets, tokens or passwords there. Secrets belong on a server or a serverless function..env.example listing the variable names (with fake values). It documents what collaborators must set without leaking anything real.11 Testing Tools Intermediate
Tests let you change code with confidence. Start small: a handful of tests around the logic you are most afraid to break.
Unit & component tests with Vitest
npm install --save-dev vitest
npx vitest # watch mode while developing
npx vitest run # single run, for CI
// sum.test.js
import { describe, it, expect } from 'vitest'
import { sum } from './sum.js'
describe('sum', () => {
it('adds two numbers', () => {
expect(sum(2, 3)).toBe(5)
})
it('handles negatives', () => {
expect(sum(-1, 1)).toBe(0)
})
})
End-to-end tests with Playwright
npm install --save-dev @playwright/test
npx playwright install
npx playwright test
// e2e/home.spec.js
import { test, expect } from '@playwright/test'
test('home page greets the user', async ({ page }) => {
await page.goto('http://localhost:5173')
await expect(page.locator('h1')).toContainText('Laksh')
})
"test": "vitest run" to your scripts and run it in CI on every push. A red build on a pull request is far cheaper than a bug in production.12 Build & Deploy Advanced
The final step: turn source code into an optimized bundle and put it on the internet.
Build and preview locally
npm run build # outputs an optimized dist/ folder
npm run preview # serve dist/ locally to test it
What the build does for you
- Minifies JavaScript and CSS to shrink the download.
- Tree-shakes โ removes code you imported but never use.
- Hashes filenames (e.g.
app.4f3a1c.js) so browsers can cache forever and still get updates. - Splits chunks so users download only what a page needs.
- Scopes CSS and extracts stylesheets.
Lazy-load and code-split
// load this module only when needed
const { renderChart } = await import('./chart.js')
// dynamic import in a framework
const Settings = lazy(() => import('./Settings.jsx'))
Deploy the output
dist/ in or link a repo# GitHub Pages: publish dist/ with gh-pages
npm install --save-dev gh-pages
npx gh-pages -d dist
user.github.io/repo/, set base: '/repo/' in the Vite config, or asset URLs will 404. Custom domains use base: '/'.npm run build) and output directory (dist). Every push then deploys automatically, and every pull request gets a preview URL.13 Cheat Sheet & Best Practices Beginner
Commands at a glance
Best practices
- Commit the lockfile (
package-lock.json) and never commitnode_modules. - Use
npm ciin CI andnpm installlocally. - Keep dependencies lean โ every package is code you did not write and must trust.
- Batch your dev tools as devDependencies so production installs stay small.
- Let Prettier format and ESLint catch bugs, then automate both on save.
- Keep secrets in
.envand commit a.env.exampleinstead. - Learn the browser DevTools before reaching for a debugging library.
- Run tests and lint in CI on every push and pull request.
- Read the build output โ it tells you exactly how large your bundle is.