Build Tools & Bundlers
Webpack, Vite, npm, and modern build tools
Build tools transform your source code into optimised assets for production. Understanding what they do — and when to configure them — is essential for diagnosing slow builds, large bundle sizes, and deployment issues.
What a Bundler Does
A bundler starts from your entry point, follows all import and require statements to build a dependency graph, then combines everything into output files browsers can load. Along the way it applies transforms (TypeScript, JSX, CSS processing) and optimisations (minification, tree shaking).
Vite — Modern Default
Vite uses native ES modules in development (no bundling — instant start) and Rollup in production. It is the recommended choice for new projects.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'], // split vendor bundle
charts: ['recharts'], // lazy-loadable chunk
},
},
},
sourcemap: true, // enable source maps for production debugging
minify: 'esbuild', // fastest minifier
},
server: {
proxy: { '/api': 'http://localhost:3001' }, // avoid CORS in development
},
});
Tree Shaking
Tree shaking eliminates dead code — exports that are imported nowhere. It requires ES module syntax (import/export) and side-effect-free modules.
// Import only what you use — tree shaking removes the rest
import { debounce } from 'lodash-es'; // tree-shakeable
import debounce from 'lodash/debounce'; // also fine
import _ from 'lodash'; // imports everything — avoid
const { debounce } = require('lodash'); // CommonJS — not tree-shakeable
// Mark your library as side-effect free in package.json
// "sideEffects": false
// "sideEffects": ["*.css"] — CSS files have side effects
Code Splitting
// Route-level — each page is a separate chunk
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));
// Component-level — heavy components load on demand
const RichEditor = React.lazy(() => import('./components/RichEditor'));
// Preload on hover — reduces perceived wait time
<Link
to="/dashboard"
onMouseEnter={() => import('./pages/Dashboard')} // preload before click
>Dashboard</Link>
Analysing Bundle Size
# Vite bundle analyser
npx vite-bundle-visualizer
# Webpack bundle analyser
npx webpack-bundle-analyzer stats.json
# Check package cost before installing
npx bundlephobia lodash # shows size and alternatives
TypeScript and JSX Compilation
Vite uses esbuild for TypeScript transpilation (type-stripping only, no type checking) and Babel or SWC for JSX. Type checking is a separate step run by tsc --noEmit in CI — this separation keeps hot reload instantaneous.
Environment Variables
# .env.local (gitignored)
VITE_API_URL=http://localhost:3001
# .env.production
VITE_API_URL=https://api.example.com
// Access in code — only VITE_ prefix is exposed to client
const apiUrl = import.meta.env.VITE_API_URL;
// NEVER put secrets here — they are bundled into the client JS