Frontend Development 19 min read

Understanding Bundle‑less: Concepts, Performance Findings, and a Self‑Developed Solution

This article explains the bundle‑less approach—including splitting bundles, no‑bundle development, and modular dependency distribution—presents performance tests that suggest an optimal chunk count of 10‑25, evaluates Vite’s advantages and drawbacks, and outlines a self‑built import‑map‑based solution with code examples.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Understanding Bundle‑less: Concepts, Performance Findings, and a Self‑Developed Solution

How to Understand Bundle‑less

Bundle‑less (or bundless) can be viewed from three angles: (1) splitting a single bundle into dozens or hundreds of smaller bundles to exploit HTTP/2 multiplexing and improve cache hit rates; (2) avoiding bundling the source code during development (no‑bundle) as done by tools like Vite, Snowpack, and WMR; (3) modular distribution of dependency artifacts, where large third‑party libraries are pre‑bundled and served via ESM‑compatible CDNs such as Skypack, esm.sh, or custom solutions.

How Many Bundles Are Appropriate?

Performance tests show that for a fixed total asset size, loading 10‑25 parallel chunks yields the best results, and keeping the dependency import depth at 1 also maximizes performance. However, uncontrolled request counts and depth make a pure no‑bundle strategy unsuitable for production.

No‑bundle Services

1. Vite as a Representative Solution

During development Vite does two things:

Uses esbuild to pre‑bundle external dependencies.

Creates an HTTP server that responds to <script type="module"> requests from the browser.

In production Vite falls back to Rollup for bundling. Vite’s strengths are fast server start‑up and hot‑module replacement, but production builds can be slower and larger.

2. Drawbacks and Solutions

2.1 Long File Compilation Time

No‑bundle saves bundling time but still needs to compile each file (TS, JSX, Less, Sass). Vite mitigates this with ETag‑based caching, but a full restart still recompiles everything. Caching the ModuleGraph on shutdown and hydrating it on restart can reduce this overhead (see vite issue #1309 ).

2.2 Massive Request Load

When request counts exceed ~1000, performance degrades. A test comparing Webpack and Vite shows Vite reduces build time from 80 s to 10 s but page load time remains around 10 s; adding Service Worker caching cuts load time to under 5 s.

2.3 Inconsistent Dev/Prod Behavior

Vite uses esbuild in development and Rollup in production, leading to differences, especially in handling CommonJS dependencies (e.g., ignoreTryCatch behavior in @rollup/plugin-commonjs ). Two remedies are: (a) use esbuild for production dependency bundling (planned for Vite 3.0) or (b) adopt an ESM CDN for both environments.

Modular Distribution of Dependency Artifacts

Pre‑building third‑party dependencies and externalizing them can dramatically reduce bundle size. The key steps are pre‑build, modular packaging, and a distribution mechanism.

Existing Solutions Overview

Vite’s pre‑build uses esbuild to pack dependencies and serve them via the dev server. Community CDNs (Skypack, esm.sh) expose ESM‑compatible URLs such as https://esm.sh/[email protected] .

Problem Analysis

Syntax and polyfill safety: current solutions target modern browsers only, lacking ES3/ES5 fallback and polyfills for legacy devices.

Online loading performance: many sub‑paths cause a large number of requests, leading to waterfall effects.

Compatibility: native ESM is not supported by older browsers (IE11, low‑end mobile).

Local debugging and deployment: CDN‑served artifacts are hard to debug locally and cannot be privately deployed.

Proposed Solutions

Compile dependency artifacts with babel / swc to the required target for polyfill safety.

Analyze the project’s module graph (via esbuild metafile) and combine frequently used dependencies into combo bundles, keeping chunk count within the 10‑25 optimal range.

Use native ESM where possible; otherwise fall back to SystemJS.

Maintain a private CDN and provide a tool to download CDN artifacts for local hosting.

Self‑Developed Bundle‑less Solution

The following architecture was built internally (diagram omitted).

1. Import Map Based Management

Instead of rewriting import paths manually, an importmap centralizes dependency URLs, preventing multiple instances and improving cache hit rates. Example:

<script type="importmap">
{
  "imports": {
    "react": "https://example.com/react/17.0.2.js",
    "react-dom": "https://example.com/react-dom/17.0.2.js"
  }
}
</script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom';
</script>

2. Module Merging

Using esbuild with metafile , the build outputs a dependency graph. Dependencies are grouped and merged into combo bundles.

3. Multi‑Package (Combo) Packaging

Combo packaging can cause export name conflicts and sub‑path explosion. Conflicts are resolved by prefixing export names with the package name during build and stripping the prefix at runtime via a hydrate helper.

// Build‑time injection
export { packageName_exportName } from 'packageName';

// Runtime hydration
var __WEBPACK_EXTERNAL_MODULE_pkg_ = __EDEN_COMBO_HYDRATE__(
  __WEBPACK_EXTERNAL_MODULE_pkg_$0,
  "packageName"
);

4. Sub‑Path Consolidation

Many packages import deep sub‑paths (e.g., @babel/runtime/helpers/esm/inheritsLoose ). By consolidating them under a single import map entry ( @babel/runtime ) and using runtime hydration, the import map size is dramatically reduced.

5. Tree Shaking

During the pre‑build phase, only the specifiers actually used are exported, e.g.:

export { cloneDeep } from 'lodash-es';
export { Spin } from '@douyinfe/semi-ui';

6. Polyfill Safety

Depending on the target runtime (Modern, PC‑Legacy, Mobile‑Legacy), appropriate polyfills are injected: native import‑map polyfill for modern browsers, full browserlist‑based polyfills plus SystemJS for legacy environments.

7. Localized Artifact Deployment

After CDN upload, a plugin fetches the artifacts, writes them to a local directory, and serves them via a dev‑server middleware. The import map URLs are rewritten to point to these local static assets, enabling local debugging and private deployment.

Conclusion

The article covered bundle‑less from the perspectives of bundle splitting, no‑bundle development services, and modular dependency distribution, providing both a conceptual overview and a concrete self‑developed implementation.

ViteTree shakingfrontend performanceesbuildbundle-lessimport mapno-bundle
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.