From 953e052127d7b4deb3ed4397fd53c0da5c08cc5a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 6 Jun 2026 20:04:32 +0000 Subject: [PATCH] bundler: don't emit a CSS chunk for files whose import resolution failed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a CSS file parses successfully but one of its url()/@import records fails to resolve, run_resolution_for_parse_task converts the result to an error and parks the parsed stylesheet on the graph's css row so teardown can drop it. The comment there assumes the linker never runs after a resolution error, but the dev server intentionally proceeds with failed files. finish_from_bake_dev_server's CSS entry-point recovery loop keyed only on css.is_some(), so the failed file — already removed from css_entry_points by the empty-parts filter — was re-added and became a CSS chunk in a bundle whose css_file_count is 0. Debug builds died on the !chunk.content.is_css() assertion in generate_chunks_in_parallel (killing the dev server on any CSS edit with an unresolvable url), and release builds emitted a bogus empty CSS chunk for the failed file. Gate the recovery loop on the file having parts, the same invariant the loop above uses to filter failed files; successfully parsed CSS always has a lazy-export part. Covered by the existing "syntax error crash" and "css import before create project relative" tests in test/bake/dev/css.test.ts, which previously panicked in debug builds (13 pass / 0 fail after this change, 11/2 before). --- src/bundler/bundle_v2.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 6c390e509c65..ae5dbdc459a7 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -5250,9 +5250,19 @@ pub mod bv2_impl { // Find CSS entry points. Originally, this was computed up front, but // failed files do not remember their loader, and plugins can // asynchronously decide a file is CSS. + // + // A file whose import-record resolution failed also has a `Some` + // css row: `run_resolution_for_parse_task` parks the parsed + // stylesheet there so teardown can drop it. Such files have no + // parts (the invariant the loop above filters by), and must not + // become CSS chunks — the loop above already removed them from + // `css_entry_points`. let css = asts.items_css(); + let parts = asts.items_parts(); for entry_point in &self.graph.entry_points { - if css[entry_point.get() as usize].is_some() { + if css[entry_point.get() as usize].is_some() + && parts[entry_point.get() as usize].len() != 0 + { start.css_entry_points.put( Index::init(entry_point.get()), CssEntryPointMeta {