Skip to content

vite build hangs for minutes/hours: catastrophic backtracking in entirelyImportRE when the entry chunk starts with many /* empty css */ markers #22844

Description

@rhengles

Describe the bug

Describe the bug

vite:build-html's generateBundle decides whether the HTML entry chunk can be inlined by running a single anchored regex over the entire minified entry chunk:

https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/html.ts

const entirelyImportRE =
  /^(?:import\s*(?:"[^"\n]*[^\\\n]"|'[^'\n]*[^\\\n]');*|\/\*[\s\S]*?\*\/|\/\/.*[$\n])*$/

When an app imports many pure-CSS JS modules that end up as separate chunks (e.g. a multi-theme app importing one scope.css per theme), CSS extraction leaves those chunks empty and each import statement in the entry chunk is replaced with a /* empty css */ marker. The minified entry chunk then starts with N consecutive comment blocks followed by real code.

Each comment boundary is a backtracking choice point for the lazy \/\*[\s\S]*?\*\/ alternative inside (?: ... )* (one "comment" match can lazily extend across any number of following blocks). Since the overall match always fails (the chunk contains real code), the regex engine explores ~2^(N-1) paths before returning false. Build time grows ×4 for every +2 markers:

N pure-CSS chunks vite build (vite 8.1.3)
15 0.9s
20 1.0s
24 4.0s
26 13.1s
28 50.3s
30 197.9s

A --cpu-prof of the N=26 build attributes 96.9% of all samples to this regex. In the real-world app where I found this (27 Bootswatch themes → 27 markers, followed by a 200KB one-line chunk whose // occurrences add further choice points), vite build sat inside this single regex.test() call for 3.5 hours, with the process not even responding to SIGTERM (the JS thread never yields). Attaching the inspector to the stuck process showed 39,832 of 39,833 CPU samples inside isEntirelyImport.

Expected behavior: the "is this chunk entirely imports?" check should be linear in chunk size, and answer false quickly for a chunk that contains real code.

Standalone one-liner showing the explosion without Vite:

const re = /^(?:import\s*(?:"[^"\n]*[^\\\n]"|'[^'\n]*[^\\\n]');*|\/\*[\s\S]*?\*\/|\/\/.*[$\n])*$/
re.test('/* empty css */'.repeat(30) + 'const x=1;') // ~30s; ×4 per +2 repeats

Notes:

{
  name: 'defuse-empty-css-regex-bomb',
  apply: 'build',
  generateBundle(_options, bundle) {
    for (const chunk of Object.values(bundle)) {
      if (chunk.type === 'chunk' && chunk.isEntry && !chunk.code.startsWith(';')) {
        chunk.code = `;${chunk.code}`
      }
    }
  },
}
  • Suggested fix: replace entirelyImportRE.test() with a small linear scanner (skip whitespace/comments/import "…"; tokens iteratively), or at least eliminate the ambiguity between the comment alternatives and the outer repetition.

Reproduction

https://stackblitz.com/edit/vitejs-vite-2dzw3zev?file=README.md

Steps to reproduce

Happens during build only (vite build).

  1. npm install
  2. npm run buildsrc/ is pre-generated with N=28 pure-CSS chunks → ~50s inside the regex (baseline N=15 build is <1s)
  3. Optionally scale N to see the exponential curve: node make-repro.mjs 30 && npm run build → ~200s; N=40 extrapolates to hours.

The repro is a vanilla project (no framework, no plugins): main.js statically imports N one-line JS modules that each import a tiny CSS file; manualChunks gives each its own chunk so CSS extraction empties them and the entry chunk starts with N /* empty css */ markers.

System Info

System:
    OS: Linux 6.18 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (4) x64 Intel(R) Xeon(R) Processor @ 2.80GHz
    Memory: 15.11 GB / 15.70 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 22.22.2 - /opt/node22/bin/node
    npm: 10.9.7 - /opt/node22/bin/npm
  npmPackages:
    vite: ^8.1.3 => 8.1.3

Used Package Manager

npm

Logs

No response

Validations

Metadata

Metadata

Labels

feat: buildp4-importantViolate documented behavior or significantly improves performance (priority)performancePerformance related enhancement

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions