Build Tools
The toolchain that transforms your source code into production-ready bundles.
Why Build
Browsers can't run your source code directly. Build tools bridge the gap.
// Your source code uses things browsers don't understand: // - TypeScript (.ts, .tsx) // - JSX (<Component />) // - CSS Modules, Tailwind // - Import from node_modules // - Environment variables // - Modern syntax older browsers don't support // The build pipeline transforms source → browser-ready code: // // Source files Build process Output // ───────────── ───────────── ────────── // .tsx, .ts → TypeScript compiler → .js // JSX → Babel/SWC transform → createElement calls // 100+ modules → Bundler → few optimized files // Modern JS → Transpiler → compatible JS // Tailwind CSS → PostCSS → optimized CSS // Images/fonts → Asset pipeline → optimized assets // The build toolchain: // Bundler → Vite, Webpack, Rollup, esbuild // Transpiler → SWC, Babel, TypeScript compiler // Linter → ESLint, Biome // Formatter → Prettier, Biome // Type checker → TypeScript (tsc) // Test runner → Vitest, Jest // Dev server → Vite, webpack-dev-server
Bundlers
Combine many files into few optimized bundles.
// Vite — the modern standard:
// Instant dev server (no bundling during dev!)
// Uses Rollup for production builds
// Blazing fast with native ES modules
// Create a Vite project:
// $ pnpm create vite my-app --template react-ts
// vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
build: {
outDir: "dist",
sourcemap: true,
},
server: {
port: 3000,
},
});
// Commands:
// pnpm dev → start dev server (instant)
// pnpm build → production bundle
// pnpm preview → preview production build locallyVite is the recommended bundler for new projects. It's fast because it uses native ES modules in development — no bundling needed.
1 / 2
Transpilers
Transform modern/extended syntax into standard JavaScript.
// TypeScript → JavaScript:
// Input (app.tsx):
interface User {
name: string;
age: number;
}
const greet = (user: User): string => {
return "Hello, " + user.name;
};
// Output (app.js) — types are removed:
const greet = (user) => {
return "Hello, " + user.name;
};
// JSX → JavaScript:
// Input:
const App = () => <div className="app"><h1>Hello</h1></div>;
// Output:
const App = () => createElement("div", { className: "app" },
createElement("h1", null, "Hello")
);
// Modern syntax → older browsers:
// Input (modern):
const items = data?.items ?? [];
const doubled = items.map(x => x * 2);
// Output (for older browsers):
var _data;
var items = ((_data = data) === null ? void 0 : _data.items) || [];
var doubled = items.map(function(x) { return x * 2; });
// Transpiler options:
// SWC — Rust-based, extremely fast (used by Next.js, Vite)
// Babel — JavaScript-based, most plugins, slower
// esbuild — Go-based, fast, used by Vite for dev
// tsc — TypeScript compiler (type-checking + transpiling)
// Most projects: SWC for speed, tsc for type-checking only
// tsconfig.json controls what TypeScript allows/targetsLinters & Formatters
Catch bugs and enforce consistent code style automatically.
// ESLint — catches bugs and enforces rules:
// eslint.config.js (flat config):
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-console": "warn",
"@typescript-eslint/no-explicit-any": "error",
},
},
];
// Run: pnpm eslint .
// Auto-fix: pnpm eslint . --fix
// Prettier — formats code consistently:
// .prettierrc:
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
// Run: pnpm prettier --write .
// Biome — all-in-one (lint + format, fast):
// Single tool replacing ESLint + Prettier
// Written in Rust, 10-100x faster
// Typical setup (scripts in package.json):
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"check": "tsc --noEmit && eslint . && prettier --check ."
}
// Pre-commit hooks (lint-staged + husky):
// Run linting only on staged files before each commit
// Prevents bad code from entering the repo
// .husky/pre-commit: pnpm lint-staged
// lint-staged config: "*.{ts,tsx}": ["eslint --fix", "prettier --write"]Dev Server
Hot Module Replacement — see changes instantly without refreshing.
// Vite dev server features:
// 1. Instant startup (no bundling — serves ES modules directly)
// 2. Hot Module Replacement (HMR) — updates without page reload
// 3. Source maps — debug original TypeScript in browser
// 4. Proxy API requests to avoid CORS
// 5. HTTPS support for local development
// How HMR works:
// 1. You edit a file and save
// 2. Vite detects the change (file watcher)
// 3. Only the changed module is re-compiled
// 4. Update is sent via WebSocket to browser
// 5. Browser applies change WITHOUT full reload
// 6. React state is preserved!
// vite.config.ts — dev server options:
export default defineConfig({
server: {
port: 3000,
open: true, // auto-open browser
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
},
},
},
});
// Environment variables:
// .env:
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
// Access in code (only VITE_ prefixed):
const apiUrl = import.meta.env.VITE_API_URL;
// .env.development — dev only
// .env.production — production build only
// .env.local — local overrides (gitignored)
// Full dev workflow:
// $ pnpm dev → starts dev server with HMR
// Edit code → see changes instantly in browser
// $ pnpm build → creates production bundle
// $ pnpm preview → test production build locallyFAQ
Common questions about build tools.