From 799d134f9f633762f57eabd49cfde70d8e60ea1b Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Tue, 23 Jun 2026 22:03:14 +0200 Subject: [PATCH] Copy public directory files verbatim instead of running the asset optimizer --- packages/cli/src/build/builder.rs | 28 ++++++++++++++++----- packages/cli/src/build/request.rs | 13 +++++++--- packages/cli/src/opt/file.rs | 41 +++++++++++++++++++++++++++++++ packages/cli/src/opt/mod.rs | 2 +- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index ae6c983108..c189fdf3c8 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -2,7 +2,10 @@ use crate::{ BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, BundleFormat, ProgressRx, ProgressTx, Result, StructuredOutput, serve::WebServer, verbosity_or_default, }; -use crate::{BuildPhaseProfile, opt::process_file_to}; +use crate::{ + BuildPhaseProfile, + opt::{copy_file_verbatim, process_file_to}, +}; use anyhow::{Context, Error, bail}; use futures_util::{FutureExt, future::OptionFuture, pin_mut}; use itertools::Itertools; @@ -833,8 +836,13 @@ impl AppBuilder { let to = asset_dir.join(bundled.bundled_path()); tracing::debug!("Copying asset from patch: {}", from.display()); - let esbuild = crate::esbuild::Esbuild::path_if_installed(); - if let Err(e) = process_file_to(bundled.options(), &from, &to, esbuild.as_deref()) { + let res = if self.build.path_is_in_public_dir(&from) { + copy_file_verbatim(&from, &to) + } else { + let esbuild = crate::esbuild::Esbuild::path_if_installed(); + process_file_to(bundled.options(), &from, &to, esbuild.as_deref()) + }; + if let Err(e) = res { tracing::error!("Failed to copy asset: {e}"); continue; } @@ -939,9 +947,17 @@ impl AppBuilder { // And then process the asset with the options into the **old** asset location. If we recompiled, // the asset would be in a new location because the contents and hash have changed. Since we are // hotreloading, we need to use the old asset location it was originally written to. - let options = *resource.options(); - let esbuild = crate::esbuild::Esbuild::path_if_installed(); - let res = process_file_to(&options, &changed_file, &output_path, esbuild.as_deref()); + let res = if self.build.path_is_in_public_dir(&changed_file) { + copy_file_verbatim(&changed_file, &output_path) + } else { + let esbuild = crate::esbuild::Esbuild::path_if_installed(); + process_file_to( + resource.options(), + &changed_file, + &output_path, + esbuild.as_deref(), + ) + }; let bundled_name = PathBuf::from(resource.bundled_path()); if let Err(e) = res { tracing::debug!("Failed to hotreload asset {e}"); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 8d93217328..c4d76f6513 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -200,7 +200,7 @@ use crate::{ }; use crate::{ WorkspaceRustcArgs, - opt::{AppManifest, process_file_to}, + opt::{AppManifest, copy_file_verbatim, process_file_to}, }; use anyhow::{Context, bail}; use cargo_metadata::diagnostic::Diagnostic; @@ -1528,7 +1528,8 @@ impl BuildRequest { .unwrap_or(to.as_path()); tracing::debug!("Copying asset {from_:?} to {to_:?}"); - assets_to_transfer.push((from, to, *bundled.options())); + let verbatim = self.path_is_in_public_dir(&from); + assets_to_transfer.push((from, to, *bundled.options(), verbatim)); } let asset_count = assets_to_transfer.len(); @@ -1544,14 +1545,18 @@ impl BuildRequest { tokio::task::spawn_blocking(move || { assets_to_transfer .par_iter() - .try_for_each(|(from, to, options)| { + .try_for_each(|(from, to, options, verbatim)| { let processing = started_processing.fetch_add(1, Ordering::SeqCst); let from_ = from.strip_prefix(&ws_dir).unwrap_or(from); tracing::trace!( "Starting asset copy {processing}/{asset_count} from {from_:?}" ); - let res = process_file_to(options, from, to, esbuild_path.as_deref()); + let res = if *verbatim { + copy_file_verbatim(from, to) + } else { + process_file_to(options, from, to, esbuild_path.as_deref()) + }; if let Err(err) = res.as_ref() { tracing::error!("Failed to copy asset {from:?}: {err}"); } diff --git a/packages/cli/src/opt/file.rs b/packages/cli/src/opt/file.rs index 05be95bdd3..52324ea6e4 100644 --- a/packages/cli/src/opt/file.rs +++ b/packages/cli/src/opt/file.rs @@ -10,6 +10,24 @@ use super::{ json::process_json, }; +/// Copy a `public/` file verbatim so it is served as-is, bypassing the asset optimizer. +/// +/// Optimizing a worker entry would inline its imports and shift `import.meta.url`, breaking the +/// runtime module re-import that `wasm-bindgen-rayon` uses to spawn its pool. +pub(crate) fn copy_file_verbatim(source: &Path, output_path: &Path) -> anyhow::Result<()> { + if let Some(parent) = output_path.parent() { + std::fs::create_dir_all(parent).context("Failed to create directory")?; + } + std::fs::copy(source, output_path).with_context(|| { + format!( + "Failed to copy {} to {}", + source.display(), + output_path.display() + ) + })?; + Ok(()) +} + /// Process a specific file asset with the given options reading from the source and writing to the output path pub(crate) fn process_file_to( options: &AssetOptions, @@ -144,3 +162,26 @@ fn resolve_unknown_asset_options(source: &Path) -> ResolvedAssetType { _ => ResolvedAssetType::File, } } + +#[cfg(test)] +mod tests { + use super::copy_file_verbatim; + + /// A `public/` module reaches the output untouched, including its imports and ES exports, and + /// nested output directories are created. + #[test] + fn public_js_module_is_copied_byte_for_byte() { + let dir = tempfile::tempdir().unwrap(); + let source = dir.path().join("loader.js"); + let output = dir.path().join("nested/out/loader.js"); + let contents = r#"import init, { initThreadPool } from "./worker.js"; +export * from "./worker.js"; +export { default } from "./worker.js"; +"#; + std::fs::write(&source, contents).unwrap(); + + copy_file_verbatim(&source, &output).unwrap(); + + assert_eq!(std::fs::read_to_string(&output).unwrap(), contents); + } +} diff --git a/packages/cli/src/opt/mod.rs b/packages/cli/src/opt/mod.rs index d63a3b69c3..81780b7b75 100644 --- a/packages/cli/src/opt/mod.rs +++ b/packages/cli/src/opt/mod.rs @@ -12,7 +12,7 @@ mod image; mod js; mod json; -pub(crate) use file::process_file_to; +pub(crate) use file::{copy_file_verbatim, process_file_to}; pub(crate) use hash::add_hash_to_asset; pub(crate) use js::js_is_module;