LEARNING OBJECTIVES ⌵
- Understand why Vite treats
index.htmlas source code and the central application entry point rather than a generated artifact. - Master Vite's development architecture: Connect middleware, on-demand TypeScript/CSS transpilation, and WebSocket HMR.
- Configure Single-Page Applications (SPAs) and Multi-Page Applications (MPAs) using
vite.config.jsandrollupOptions.input. - Trace how Vite transforms raw development HTML into hashed, tree-shaken, and optimized production distribution bundles with
<link rel="modulepreload">.
📖 The Mental Model & Story (Intuitive Foundation)
In traditional Webpack architecture, JavaScript was King. You configured a JavaScript file (src/index.js) as the entry point. A plugin (HtmlWebpackPlugin) would then construct an HTML file from scratch or inject script tags into a passive template. The browser's primary document was treated as an afterthought—a mechanical container generated to host the JavaScript bundle.
Vite inverts this paradigm entirely: HTML is King.
Think of index.html as the Grand Entrance and Architectural Blueprint of a building. When a visitor walks through the front door (index.html), they see signs pointing to the electrical room (<link rel="stylesheet" href="/src/styles.css">) and the control center (<script type="module" src="/src/main.ts">).
During development, Vite acts as a smart building concierge. When the browser enters through index.html and asks for /src/main.ts, the concierge instantly translates TypeScript to JavaScript on the fly and hands it over. In production, Vite acts as the master construction contractor, reading index.html, crawling all referenced scripts, styles, images, and fonts, bundling them with Rollup, and outputting an ultra-optimized physical facility in dist/.
Technical Deep Dive & Specifications
1. index.html as the Source Root
In a standard Vite project, index.html is located directly in the project root (not hidden inside a /public or /src folder):
my-vite-project/
├── index.html <-- Application Root & HTML Entry Point
├── package.json
├── vite.config.js
├── public/ <-- Static assets served as-is (unprocessed)
│ └── favicon.ico
└── src/
├── main.ts <-- Referenced directly by index.html
├── style.css <-- Referenced directly by index.html or main.ts
└── components/
Inside index.html, you reference your source files using standard URL paths:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/public/favicon.ico" />
<link rel="stylesheet" href="/src/style.css" />
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
2. Vite Dev Server Request Lifecycle
+-----------------------------------------------------------------------------------+
| VITE DEV SERVER LIFECYCLE |
+-----------------------------------------------------------------------------------+
1. Browser Request (GET /)
|
v
[Vite Connect Server] ---> Reads root /index.html
|
+---> Injects /@vite/client (WebSocket HMR Engine)
+---> Injects HTML Environment Variables (%VITE_APP_TITLE%)
+---> Returns modified index.html to Browser
|
2. Browser Parses HTML & Requests Assets:
|
+---> GET /src/style.css --> Dev Server wraps CSS in JS module with HMR hook
+---> GET /src/main.ts --> esbuild compiles TS -> ESM JS (< 10ms)
|
3. Browser Executes main.ts & Requests Imports:
|
+---> GET /src/App.vue / .tsx / .js -> Transpiled on demand
+---> GET /node_modules/.vite/deps/lodash.js (Pre-bundled by esbuild)
3. Multi-Page Application (MPA) Architecture
While SPAs have a single index.html, enterprise applications frequently require multiple distinct HTML entry points (e.g., Marketing site, User Dashboard, Authentication portal). Vite handles this seamlessly via build.rollupOptions.input:
// vite.config.js
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
dashboard: resolve(__dirname, 'dashboard/index.html'),
auth: resolve(__dirname, 'auth/login.html'),
},
},
},
});
4. Production Build: From Source HTML to Dist
When you execute vite build, Vite uses Rollup under the hood to perform the following operations:
- HTML Parsing: Parses all configured HTML entry points and extracts all
<script type="module">,<link rel="stylesheet">, and asset references (<img>,<video>,<source>). - Tree Shaking: Eliminates unused exports across your entire module dependency tree.
- Asset Hashing: Renames referenced assets to cryptographic hashes (e.g.,
assets/main-Dk92f_a.js,assets/style-Bx10kLm.css). - Modulepreload Injection: Injects
<link rel="modulepreload">tags for direct and transitive chunk dependencies into the generated HTML files.
Source: index.html
<script type="module" src="/src/main.ts"></script>
<link rel="stylesheet" href="/src/style.css">
Production Output: dist/index.html
<link rel="stylesheet" crossorigin href="/assets/style-D28_x1.css">
<link rel="modulepreload" crossorigin href="/assets/vendor-Ak92x.js">
<script type="module" crossorigin src="/assets/main-Bx892L.js"></script>
Vite Configuration Reference Matrix
| Configuration Option | Type | Default | Description |
|---|---|---|---|
root |
string |
process.cwd() |
Project root directory where index.html is located. |
base |
string |
'/' |
Base public path when served in development or production (e.g., '/my-app/'). |
build.outDir |
string |
'dist' |
Output directory for the production build. |
build.assetsDir |
string |
'assets' |
Directory under outDir to place generated assets. |
build.emptyOutDir |
boolean |
true |
Empties outDir automatically on build if it is inside root. |
build.rollupOptions |
RollupOptions |
{} |
Direct low-level options passed into Rollup (inputs, manual chunks). |
💻 Interactive Code Playground
Starter Code: Production-Ready Vite Project Setup
1. Configuration (vite.config.js)
2. Main Entry Point (index.html)
3. TypeScript Entry Point (src/main.ts)
4. Modern Stylesheet (src/style.css)
Line-by-Line Code Breakdown
vite.config.jsLines 11–16: Configures two distinct entry points (index.htmlandanalytics.html). Duringvite build, Rollup compiles both pages, hashes their respective script and style dependencies, and outputs two optimized HTML pages intodist/.index.htmlLine 6 (<link rel="stylesheet" href="/src/style.css" />): In development, Vite intercepts this CSS file, parses it, and injects hot-reloading hooks. In production, it extracts the CSS, minifies it, and replaces this tag with a hashed link.index.htmlLine 19 (<script type="module" src="/src/main.ts"></script>): Vite directly reads TypeScript source files! There is no need to manually runtscbefore launching your dev server.src/main.tsLine 15 (import.meta.env.MODE): Vite provides built-in environment variables via standard ECMAScriptimport.meta.env(e.g.development,production).
Expected Build Output (dist/)
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
base: '/',
server: {
port: 3000,
open: true,
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
analytics: resolve(__dirname, 'analytics.html'),
},
},
},
});import './style.css';
const btn = document.querySelector<HTMLButtonElement>('#counter-btn');
let count = 0;
if (btn) {
btn.addEventListener('click', () => {
count += 1;
btn.textContent = `Clicks: ${count}`;
btn.classList.add('pulse');
setTimeout(() => btn.classList.remove('pulse'), 200);
});
}
console.log(`[Vite Engine] Initialized in ${import.meta.env.MODE} mode`);:root {
--primary: #646cff;
--bg-dark: #242424;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--bg-dark);
color: #ffffff;
margin: 0;
padding: 2rem;
}
button {
background: var(--primary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
border-radius: 8px;
cursor: pointer;
transition: transform 0.1s ease;
}
button.pulse {
transform: scale(1.1);
}dist/
├── index.html
├── analytics.html
└── assets/
├── main-D0g9xLp1.js
├── main-D0g9xLp1.js.map
├── style-Bq18zPq0.css
└── style-Bq18zPq0.css.map🏋️ Hands-On Exercise
🎯 The Challenge: Build a Multi-Page Vite Portal with Environment Variable Interpolation
Instructions:
- Create a Vite configuration supporting two HTML pages:
- Root page:
index.html(Landing Page) - Portal page:
portal/index.html(Customer Dashboard)
- Root page:
- Use Vite's built-in HTML environment variable interpolation (
%VITE_APP_TITLE%and%VITE_API_URL%) in both HTML pages. - Configure
vite.config.jsto output assets intodist/with source maps enabled and clean output directories.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Placing
index.htmlinsidepublic/: Vite treats everything insidepublic/as static assets that bypass compilation. Placingindex.htmlinsidepublic/breaks TypeScript transpilation, CSS extraction, and HMR. - Forgetting Multi-Page Inputs in
vite.config.js: During development, navigating to/portal/index.htmlworks automatically via the Connect dev server. However, runningvite buildwill only compileindex.htmlunless additional pages are explicitly declared inbuild.rollupOptions.input. - Using Root-Relative Paths with Sub-path Hosting: If your app is hosted at
https://example.com/sub-app/, leavingbase: '/'will cause all asset links in HTML (/assets/main.js) to return 404s. Setbase: '/sub-app/'orbase: './'.
💡 Pro Tips
- Custom HTML Transform Plugins: You can write lightweight Vite plugins using the
transformIndexHtmlhook to dynamically inject analytics scripts, CSP nonces, or server-side flags into your HTML during build time. - Leverage
<link rel="modulepreload">: Vite automatically injectsmodulepreloadlinks into the generated HTML head for chunk dependencies. This instructs the browser's preload scanner to fetch and parse transitive module dependencies in parallel, preventing waterfall execution.
📌 Key Takeaways
- Vite treats
index.htmlas the primary application source code and central entry point. - During development, Vite serves native ES modules on-demand without pre-compiling the entire project bundle.
- Multi-Page Applications (MPAs) are configured by declaring HTML file paths in
build.rollupOptions.input. - Vite supports native HTML environment variable interpolation using
%VITE_VAR_NAME%syntax. - In production (
vite build), Rollup traverses HTML entry points, extracts assets, generates content hashes, and injects<link rel="modulepreload">tags. - --