Command Palette

Search for a command to run...

Hectal
PHASE 12Intermediate ~25 min· topic 4 of 6

Topic 12.4

Bundlers and Dev Servers

In one line

A bundler follows your imports from one entry file and combines the modules into a few optimised files, transpiling new syntax for older browsers, removing unused code and minifying; a dev server such as Vite serves your code instantly while you work and updates the page as you save.

Think of it like this

Packing for a trip. At home, your things are spread across many drawers and shelves (your source files). Before you travel, you pack only what you'll use into one or two suitcases (the bundle), leave behind what you won't need (tree shaking), roll clothes tightly to save space (minifying), and label each bag so it isn't mixed up with an old one (content hashes). At home you don't pack anything; you just open the drawer you need (the dev server).

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Bundler
A build tool that follows your imports and combines many modules into a few optimised files.
Entry point
The first file the bundler starts from, usually src/main.js or index.html.
Tree shaking
Removing exported code that no module imports, so it isn't shipped.
Minification
Making code smaller without changing what it does: removing spaces and comments, shortening names.
Transpiler
A tool that rewrites code into another version of the language, such as modern JavaScript into older JavaScript.
Polyfill
Extra code that adds a missing built-in feature (like a method) to older browsers.
Dev server
A local web server for development that serves your code and reloads changes as you save.
HMR
Hot Module Replacement: updating one changed module in a running page without reloading everything.
Source map
A file that maps built, minified code back to your original source for debugging.
Content hash
A short fingerprint of a file's contents put in its name, so a changed file gets a new name.

Step by step

01Create a Vite project

npm create vite@latest runs the create-vite package, which asks a few questions (or takes them as options) and writes a starter project. The vanilla template means plain JavaScript, no framework.

terminal
$ npm create vite@latest my-app -- --template vanilla
── expected output ──
◇ Scaffolding project in /home/asha/my-app...
│
└ Done. Now run:
 
cd my-app
npm install
npm run dev

02Look at the structure

index.html sits at the project root and is the entry point: Vite reads its <script type="module" src="/src/main.js"> and follows the imports. public/ holds files copied as-is. package.json has the dev, build and preview scripts.

my-app/whole filetext
my-app/
├── index.html          <- entry: <script type="module" src="/src/main.js">
├── package.json        <- scripts: dev, build, preview
├── public/
│   └── vite.svg        <- copied to dist/ unchanged
└── src/
    ├── main.js
    ├── counter.js
    └── style.css       <- imported from main.js

03Run the dev server

npm run dev starts Vite. Open the address it prints. Edit src/counter.js and save: the change appears at once without a full reload (HMR). Imports like import "./style.css" work too: Vite turns CSS into a <style> in development.

terminal
$ npm run dev
── expected output ──
> my-app@0.0.0 dev
> vite
 
VITE v7.1.5 ready in 287 ms
 
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

04Build for production

npm run build bundles, tree-shakes, transpiles to the target, minifies, hashes file names and writes dist/. This folder is what you upload to a host. npm run preview serves it locally to check it.

terminal
$ npm run build
── expected output ──
> my-app@0.0.0 build
> vite build
 
vite v7.1.5 building for production...
✓ 7 modules transformed.
dist/index.html 0.46 kB │ gzip: 0.30 kB
dist/assets/index-D8b4DwXb.css 1.20 kB │ gzip: 0.62 kB
dist/assets/index-B7xQ2LmA.js 3.10 kB │ gzip: 1.45 kB
✓ built in 141ms

05Development versus production

The two modes optimise for different things: development for instant feedback, production for the smallest, most cacheable files.

Development versus productiondiagram
Rendering diagram…

06Configure only what you need

Vite works without a config file. When you need one, vite.config.js sets options such as the browser target, the public base path (for sites served from a subfolder) and source maps.

vite.config.jswhole filejavascript
import { defineConfig } from "vite";

export default defineConfig({
  base: "/shop/",          // the site lives at example.com/shop/
  build: {
    target: "es2020",      // syntax newer than this is transpiled
    sourcemap: true,       // write .map files for debugging
  },
});

Try it yourself

  1. 1

    Use a tree-shaken function

    In the first example, add import { formatDate } from "./format.js"; to main.js (as a new string in its array). Predict: which name disappears from the "Tree-shaken away" list, and does the order change?

  2. 2

    Check the edge cases

    In the second example, add { profile: null } and { profile: { name: 0 } } to inputs. Predict both results for each function before you run it.

  3. 3

    Change only a comment

    In the third example, make v2 change only the comment text instead of the number. Predict: does the hash change? Why is that good for caching?

Code & diagrams

A tiny bundler: graph, order, tree shaking New tab

Real bundlers parse code properly instead of reading lines, rename variables when two modules use the same name ("scope hoisting"), and only drop code they can prove has no side effects. The idea is the same: follow imports, put dependencies first, leave out what nobody uses.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Order: [ './format.js', './prices.js', './main.js' ]
Tree-shaken away: [ 'formatDate', 'debugPrices' ]
Running the bundle:
Rs 118.00
What transpiling does to modern syntax New tab

Same results, much more code: that's the price of supporting old browsers. `void 0` is a short, safe way to write `undefined`. Notice the empty name stays `""`: `??` only replaces `null` and `undefined`, and the transpiled version keeps that exact rule.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

"guest" "guest"
"guest" "guest"
"" ""
"Asha" "Asha"
Minifying and content hashes New tab

Changing one number gives a completely different file name, so a browser that cached version 1 for a year still fetches version 2 at once. Unchanged files keep their names and stay cached. Real builds use stronger hashes (such as SHA-256) and real minifiers also shorten `price` and `rate` to one letter.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

version 1: 99 -> 70 chars, file index-45fc5ef3.js
version 2: 99 -> 70 chars, file index-e042c929.js

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Open the built page from disk

Run npm run build, then double-click dist/index.html to open it as a file.

terminal
$ Console
── what you'll see ──
The page is blank.
Failed to load resource: net::ERR_FILE_NOT_FOUND file:///C:/assets/index-B7xQ2LmA.js

Break #2

Read an environment variable without the prefix

Put API_URL=https://api.example.com in a .env file and use import.meta.env.API_URL in src/main.js.

terminal
$ Console
── what you'll see ──
undefined

Myth vs fact

Myth

You can't write modern JavaScript without a bundler.

Fact

Browsers run ES modules natively. Small sites and prototypes can skip the build step. Bundlers earn their place with npm packages, many modules and production optimisations.

Myth

Transpiling makes new features like toSorted() work in old browsers.

Fact

Transpilers rewrite syntax. A missing built-in method needs a polyfill (for example from core-js). The two are often configured together, but they are different jobs.

Myth

Minification protects your code from being copied.

Fact

Minified code is harder to read, not secret. Tools can reformat it, and source maps (if published) restore it. Anything sent to the browser is public.

Myth

The dev server is just a slower version of production.

Fact

It's a different setup: unbundled native modules, on-demand transforms and HMR. That's why you should always test the production build with vite preview before deploying.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Tree shaking depends on side-effect knowledge: a module that runs code at import time (adds a global, registers a plugin, imports CSS) can't be dropped even if none of its exports are used. Library authors mark safe files with "sideEffects": false in package.json (or list the files that do have side effects). CommonJS packages tree-shake poorly because require and module.exports are dynamic.

  • ▸

    Vite 7's default production target is "baseline-widely-available": browsers that have supported features for at least 30 months under the Baseline project (Chrome and Edge 107, Firefox 104, Safari 16). Newer syntax is transpiled down to that level by esbuild/Oxc; polyfills are not added unless you use @vitejs/plugin-legacy.

  • ▸

    Hashing strategy matters for caching: with long-lived Cache-Control: max-age=31536000, immutable on /assets/* and a short cache on index.html, users get new code immediately after a deploy while unchanged chunks stay cached. Splitting rarely changing vendor code into its own chunk keeps its hash stable between releases.

  • ▸

    Much modern tooling is written in Rust or Go for speed: esbuild (Go), SWC, Oxc, Rolldown, Rspack and Turbopack (Rust). They are often 10 to 100 times faster than JavaScript-based predecessors like Babel and webpack, which is why rebuilds now take milliseconds.

Remember this

  1. 1

    A bundler is a build tool that starts at your entry file (often src/main.js), follows every import to build the module graph (Topic 12.1), and writes a small number of output files, usually into a dist folder. Why not ship hundreds of separate modules? Each file is a request, and the browser only discovers a module's imports after downloading it, which causes waterfalls. Bundlers also resolve bare specifiers like "date-fns" from node_modules (Topic 12.3), which browsers can't do on their own, and handle non-JavaScript imports such as CSS, images and JSON.

  2. 2

    On the way, the bundler optimises. Tree shaking removes exports nobody imports; it works because ES module imports are static. Minification shortens the code without changing what it does: it removes spaces and comments and renames local variables (totalPrice becomes t). Code splitting creates chunks at each import() (Topic 12.2). Content hashing puts a fingerprint of the file's contents in its name (index-B7xQ2LmA.js), so browsers can cache it for a year and a new version gets a new name. (Hectal's /system-design course covers HTTP caching in depth.)

  3. 3

    Transpiling means translating code into a different version of the same language: for example, rewriting user?.name ?? "guest" (ES2020) into older syntax that a 2019 browser understands, or removing TypeScript types (Topic 12.6). The tool is a transpiler (Babel, SWC, esbuild, TypeScript's tsc). You choose the target: which browsers must be supported, often written as a browserslist query such as "> 0.5%, last 2 versions, not dead". Note that transpiling changes syntax only; a missing function such as Array.prototype.toSorted needs a polyfill (extra code that adds it), not a transpiler.

  4. 4

    A dev server is a local web server for development. Vite (French for "quick", said "veet") is today's most common choice for new projects. In development it does not bundle your code: it serves each source file as a native ES module and transforms it the moment the browser asks for it, so start-up is near-instant even in large apps. It pre-bundles npm packages once, because some packages contain hundreds of small files. HMR (Hot Module Replacement) swaps an edited module into the running page without a full reload, so you keep your place and state.

  5. 5

    For production, vite build runs a full bundler (Rollup in Vite 7, its faster Rust-based successor Rolldown in newer versions) to produce optimised files in dist/, and vite preview serves that folder locally so you can test the real output. Other tools you'll meet: webpack (older, very configurable, still in many company codebases), esbuild and Rollup (used directly, often for libraries), Parcel, Rspack and Turbopack (used by Next.js). The ideas in this topic apply to all of them.

  6. 6

    Source maps connect the minified output back to your original files. The bundler writes index-B7xQ2LmA.js.map, and DevTools uses it to show your real code, variable names and line numbers in errors and breakpoints (Phase 13). Without them, a production error points to "line 1, column 48211" of one giant line. Whether to publish source maps is a choice: they make debugging easier but reveal your original source.

Explain it without notes

01

Why do we bundle JavaScript for production if browsers support ES modules?

02

What is the difference between transpiling and polyfilling?

03

How does Vite's dev server differ from its production build?

04

What is tree shaking, and why does it need ES modules?

Practice

01

Write minifyNumbers(code) that removes every space except those inside double-quoted strings. Test it on const total = 1 + 2; const s = "a b";.

02

Write the package.json scripts and the vite.config.js for a site deployed to https://example.com/blog/, with source maps turned on.

Trade-offs

  • ↔

    A build step gives smaller, faster, cached production files and lets you use npm packages and TypeScript, but adds configuration, a slower feedback loop for some changes, and differences between dev and production to test for.

  • ↔

    Targeting very old browsers means more transpiled code and polyfills for everyone unless you ship two builds; a modern baseline target keeps bundles small but drops some users.

  • ↔

    Publishing source maps makes production errors easy to read; keeping them private (uploaded only to your error-tracking service) keeps your original source less exposed.

Done when you can

  • I can explain why bundling helps: requests, waterfalls, bare specifiers, optimisations.

  • I can create a Vite project and use dev, build and preview.

  • I can explain tree shaking, minification, code splitting and content hashes.

  • I know transpiling changes syntax, polyfills add features, and how a browser target is chosen.

  • I know what source maps are for, and that VITE_ variables end up public.