From aa38de79b210968213cc3a286d802112a3e208a3 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 3 Apr 2026 22:06:29 +0800 Subject: [PATCH 1/2] fix(cli): override rolldown panic hook with vite-plus branding Since rolldown_binding is bundled into the same NAPI binary, its module_init panic hook catches all panics and misleadingly shows "Rolldown panicked" with a link to rolldown's issue tracker. Replace it at the start of run() with a vite-plus specific hook that correctly attributes panics and links to the vite-plus bug report. Closes #1285 --- packages/cli/binding/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index 4da1358464..7c6d0790bc 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -123,6 +123,29 @@ fn format_error_message(error: &(dyn StdError + 'static)) -> String { message } +/// Override rolldown's panic hook with a vite-plus specific one. +/// +/// rolldown_binding sets a global panic hook in its `#[module_init]` that prints +/// "Rolldown panicked" for ALL panics. Since vite-plus bundles rolldown into the +/// same binary, we need to replace it so panics are correctly attributed to Vite+. +#[allow(clippy::disallowed_macros)] +fn setup_panic_hook() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + // First take_hook discards rolldown's custom hook (which wraps the default + // in a closure that prints "Rolldown panicked"). Second gets the real default. + let _ = std::panic::take_hook(); + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + eprintln!("Vite+ panicked. This is a bug in Vite+, not your code.\n"); + default_hook(info); + eprintln!( + "\nPlease report this issue at: https://github.com/voidzero-dev/vite-plus/issues/new?template=bug_report.yml" + ); + })); + }); +} + /// Main entry point for the CLI, called from JavaScript. /// /// This is an async function that spawns a new thread for the non-Send async code @@ -130,6 +153,7 @@ fn format_error_message(error: &(dyn StdError + 'static)) -> String { /// and process JavaScript callbacks (via ThreadsafeFunction). #[napi] pub async fn run(options: CliOptions) -> Result { + setup_panic_hook(); // Use provided cwd or current directory let mut cwd = current_dir()?; if let Some(options_cwd) = options.cwd { From 09b6e255d668453e18a9ea8f060a3fe8ec277059 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 3 Apr 2026 22:21:52 +0800 Subject: [PATCH 2/2] fix(cli): simplify panic hook setup and improve doc comments Remove rolldown-specific assumptions from comments. The double take_hook pattern works regardless of whether rolldown's hook is present (local dev) or not (future rolldown changes). Add Once guard and remove extra trailing newline. --- packages/cli/binding/src/lib.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/cli/binding/src/lib.rs b/packages/cli/binding/src/lib.rs index 7c6d0790bc..58bea6471d 100644 --- a/packages/cli/binding/src/lib.rs +++ b/packages/cli/binding/src/lib.rs @@ -123,21 +123,19 @@ fn format_error_message(error: &(dyn StdError + 'static)) -> String { message } -/// Override rolldown's panic hook with a vite-plus specific one. +/// Install a Vite+ panic hook so panics are correctly attributed to Vite+. /// -/// rolldown_binding sets a global panic hook in its `#[module_init]` that prints -/// "Rolldown panicked" for ALL panics. Since vite-plus bundles rolldown into the -/// same binary, we need to replace it so panics are correctly attributed to Vite+. +/// Discards any previously set hook (e.g. rolldown's) via double `take_hook`: +/// first call removes the current hook, second captures the restored default. +/// Safe to call regardless of whether a custom hook was installed. #[allow(clippy::disallowed_macros)] fn setup_panic_hook() { static ONCE: std::sync::Once = std::sync::Once::new(); ONCE.call_once(|| { - // First take_hook discards rolldown's custom hook (which wraps the default - // in a closure that prints "Rolldown panicked"). Second gets the real default. let _ = std::panic::take_hook(); let default_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { - eprintln!("Vite+ panicked. This is a bug in Vite+, not your code.\n"); + eprintln!("Vite+ panicked. This is a bug in Vite+, not your code."); default_hook(info); eprintln!( "\nPlease report this issue at: https://github.com/voidzero-dev/vite-plus/issues/new?template=bug_report.yml"