Improving Development Server Startup Speed with Unbundled Development: From Webpack to Vite and Custom Solutions
This article analyses the performance problems of traditional bundled development with Webpack, explores unbundled tools such as Snowpack, WMR and Vite, and details a custom dev‑server implementation that leverages CJS‑to‑ESM conversion, esbuild bundling, plugin architecture, and fast HMR to dramatically reduce startup time for large monorepos.
Traditional bundled development with tools like Webpack becomes painfully slow as project size grows, especially in monorepos where thousands of modules are compiled together, leading to dev server start‑up times of several minutes.
Common Webpack optimisations (thread‑loader, babel‑loader, lazy compilation, externals) either lack universality or introduce stability issues, and many are insufficient for the author’s internal workflow.
Unbundled development tools (Snowpack, WMR, Vite) delegate module resolution to the browser’s native ESM support, allowing dev servers to start in seconds and compile modules on demand. Their advantages include rapid startup, real‑time on‑the‑fly compilation, and faster hot‑module replacement (HMR) compared to full bundle rebuilds.
To adopt this model, the team built a custom dev server based on Vite 2.0, extending it with their own plugin system to meet internal requirements such as deep dependency preprocessing, custom CSS handling, and unified configuration.
Dependency preprocessing : Internal CJS packages are converted to ESM using a cloud‑based CJS‑to‑ESM service. The resulting ESM files are cached locally and bundled with esbuild to reduce network requests. Example mapping file:
{
"react?16.14.0": "/Library/Caches/__web_modules__/[email protected]",
"react-dom?16.14.0": "/Library/Caches/__web_modules__/[email protected]",
"object-assign?4.1.1": "/Library/Caches/__web_modules__/[email protected]"
}During bundling, an esbuild build configuration resolves these paths via an onResolve hook:
const bundleResult = await require('esbuild').build({
entryPoints: ['react', 'react-dom'],
bundle: true,
splitting: true,
chunkNames: 'chunks/[name]-[hash]',
metafile: true,
outdir: webModulesDir,
format: 'esm',
treeShaking: 'ignore-annotations',
plugins: [{
name: 'resolve-deps-plugin',
setup(build) {
build.onResolve({ filter: /^/ }, async args => {
const { kind, path } = args;
if (['import-statement', 'entry-point', 'dynamic-import'].includes(kind)) {
// Resolve to cached ESM file using the JSON map
}
});
}
}]
});JSX/TSX compilation : The server uses esbuild.transform with the tsx loader for fast conversion, injecting import React from 'react' when needed for React 17, and falling back to the TypeScript API for const enum cases.
const result = await esbuild.transform(code, {
loader: 'tsx',
sourcefile: importer,
sourcemap: true,
target: 'chrome63'
});When bare imports (e.g., import React from 'react' ) appear, they are rewritten to point to the locally cached ESM files:
import React from "/node_modules/.web_modules/react.js";Asset handling : CSS files are processed with PostCSS and injected as style tags; JSON files are exported as default objects; image and other assets are served via a ?assets query that returns the asset URL or an inline base64 string.
// Example CSS injection
const code = "body {\n margin: 0;\n}\n";
const styleEl = document.createElement('style');
styleEl.type = 'text/css';
styleEl.appendChild(document.createTextNode(code));
document.head.appendChild(styleEl);Hot Module Replacement (HMR) : The server implements both the legacy module.hot API and the newer import.meta.hot API, tracking dependency graphs so that only affected modules are re‑compiled and re‑requested, resulting in faster updates than full Webpack rebuilds.
import.meta.hot.accept();
import.meta.hot.accept(['./dep1', './dep2'], () => {});Overall, the custom unbundled dev server delivers a noticeable improvement in developer experience, while production builds still rely on Webpack for stable bundle generation. The approach also accumulates reusable solutions for CJS‑to‑ESM conversion, cloud‑based caching, and asset handling that can benefit other projects.
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
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.