TechLead
🔧
Intermediate
5 min read

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

Continue Learning