Frontend Development 12 min read

Understanding Vite: ESM‑Based Dev Server, HMR, Pre‑Bundling, and Rollup Integration

This article explains how Vite improves development experience over traditional bundlers by leveraging native ESM support for a fast dev server, efficient hot‑module replacement, pre‑bundling of Node modules, and seamless integration with Rollup plugins for production builds and SSR.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Understanding Vite: ESM‑Based Dev Server, HMR, Pre‑Bundling, and Rollup Integration

1. Background

Before native ES modules (ESM) were widely supported, developers relied on bundlers such as Webpack to package JavaScript for the browser, which caused long startup times for large projects. Vite was created to address these performance bottlenecks by providing an ultra‑fast dev server and lightweight hot updates.

2. ESM‑Based Dev Server + HMR

Comparing a simple Vue "hello world" project shows that Vite starts the dev server in about 368 ms versus Webpack's 934 ms, while page load time is comparable.

Service start time (ms)

Page load time (ms)

Webpack

934

203

Vite

368

231

Webpack’s dev server bundles the entire application before serving, which forces it to wait for all modules to be built. Vite, by contrast, serves native ESM modules directly; the browser fetches each module on demand, eliminating the bundling step during development.

2.1 How Webpack’s Bundle‑Based Dev Server Works

Webpack analyzes the entry point, bundles all dependencies, injects the bundle into index.html , and then starts a dev server that must wait for the whole bundle to finish before responding to requests.

Dev server must wait for all modules to be built; larger apps mean longer startup.

Even split chunks are built eagerly.

2.2 How Vite Improves Performance

Vite’s dev server relies on browsers’ native ESM support, so developers only need to write <script type="module" src="./main.js"></script> . The server serves source files directly and transforms them on‑the‑fly when requested.

2.3 New Challenges Introduced by Vite

File transform performance – Vite uses fast tools like esbuild and caches transform results.

Compatibility with non‑ESM modules (TS/JSX) – Vite converts them to ESM using esbuild.

Browser ESM cannot load Node built‑ins – Vite scans imports with es-module-lexer and rewrites paths using magic-string .

Node CJS module handling – Vite pre‑bundles Node modules into a single file and caches the result on disk.

2.4 Pre‑Bundle Node Modules

Before the dev server starts, Vite scans the project for used Node modules and bundles them into a single file. This step is expensive once, but the result is cached on disk and reused on subsequent starts, also providing metadata for named imports.

2.5 ESM HMR

Vite’s HMR API mirrors Webpack’s. When a module changes, the browser receives a WebSocket message, reloads only the affected module, and executes the registered update callbacks without a full page refresh.

import foo from './foo.js'

foo()

if (import.meta.hot) {
  import.meta.hot.accept('./foo.js', (newFoo) => {
    newFoo.foo()
  })
}

After transformation, Vite injects a helper to create the hot context:

import { createHotContext as __vite__createHotContext } from "/@vite/client"
import.meta.hot = __vite__createHotContext("/hmrDep.js")
import foo from '/foo.js'

foo()

if (import.meta.hot) {
  import.meta.hot.accept('/foo.js', (newFoo) => {
    newFoo.foo()
  })
}

3. Rollup‑Based Bundle and Plugins

Vite uses Rollup for production builds because Rollup is ESM‑first, has a flexible plugin API, and produces smaller, faster bundles. Vite ships with zero‑config plugins for TypeScript/JSX, CSS preprocessors, assets, JSON, web workers, module resolution, and more.

Official plugins include @vitejs/plugin-vue for Vue 3 and @vitejs/plugin-react-refresh for React, providing out‑of‑the‑box support for popular frameworks.

4. SSR

Vite also supports server‑side rendering. Node 12.22+ offers a native ESM loader API (experimental) which Vite extends with its own loader. Developers import the entry with ssrLoadModule , which recursively loads and executes dependencies. Vite transforms import syntax using estree and magic-string to a vite_ssr_import helper.

Node CJS modules can be required directly without transformation, simplifying mixed‑module environments.

References

[1] Vite popularity – https://www.npmtrends.com/vite

[2] import.meta – https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import.meta

[3] @vitejs/plugin-vue – https://github.com/vitejs/vite/tree/main/packages/plugin-vue

[4] @vitejs/plugin-react-refresh – https://github.com/vitejs/vite/tree/main/packages/plugin-react-refresh

[5] Node Loaders API – https://nodejs.org/api/esm.html#esm_loaders

Frontend DevelopmentviteESMRollupDev ServerHMR
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.