diff --git a/crates/pack-core/src/client/context.rs b/crates/pack-core/src/client/context.rs index 08e0ac676..476d307c3 100644 --- a/crates/pack-core/src/client/context.rs +++ b/crates/pack-core/src/client/context.rs @@ -7,6 +7,7 @@ use turbo_tasks::{ FxIndexMap, ResolvedVc, TaskInput, TryJoinIterExt, ValueToString, Vc, trace::TraceRawVcs, }; use turbo_tasks_env::EnvMap; +use turbo_tasks_fs::File; use turbo_tasks_fs::FileSystemPath; use turbopack::{ css::chunk::CssChunkType, @@ -18,6 +19,7 @@ use turbopack::{ }; use turbopack_browser::{BrowserChunkingContext, CurrentChunkMethod}; use turbopack_core::{ + asset::AssetContent, chunk::{ ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapSourceType, SourceMapsType, module_id_strategies::ModuleIdStrategy, @@ -30,6 +32,7 @@ use turbopack_core::{ file_source::FileSource, free_var_references, module_graph::export_usage::OptionExportUsageInfo, + virtual_source::VirtualSource, }; use turbopack_ecmascript::{TypeofWindow, chunk::EcmascriptChunkType}; use turbopack_node::{ @@ -212,6 +215,34 @@ pub async fn get_client_runtime_entries( let watch = *watch.await?; let hot = *hot.await?; + // Add runtime bootstrap code if configured + // This runs synchronously in the runtime chunk before any entry modules execute + let bootstrap_config_opt = config.runtime_bootstrap().await?; + if let Some(bootstrap_config) = bootstrap_config_opt.as_ref() { + match bootstrap_config { + crate::config::RuntimeBootstrapConfig::Code(code) => { + // Inline code: create a virtual source + let bootstrap_source = VirtualSource::new( + project_root.join("__runtime_bootstrap__.js")?, + AssetContent::file(File::from(code.clone()).into()), + ) + .to_resolved() + .await?; + runtime_entries.push( + RuntimeEntry::Source(ResolvedVc::upcast(bootstrap_source)).resolved_cell(), + ); + } + crate::config::RuntimeBootstrapConfig::Path(path) => { + // File path: use FileSource to load the file + let bootstrap_path = project_root.join(path.as_str())?; + let bootstrap_source = FileSource::new(bootstrap_path).to_resolved().await?; + runtime_entries.push( + RuntimeEntry::Source(ResolvedVc::upcast(bootstrap_source)).resolved_cell(), + ); + } + } + } + if is_development && watch { let enable_react_refresh = assert_can_resolve_react_refresh(project_root.clone(), resolve_options_context) diff --git a/crates/pack-core/src/config.rs b/crates/pack-core/src/config.rs index 454426726..6f7af3119 100644 --- a/crates/pack-core/src/config.rs +++ b/crates/pack-core/src/config.rs @@ -120,6 +120,21 @@ pub enum ProviderConfigValue { #[turbo_tasks::value(transparent)] pub struct ProviderConfig(FxIndexMap); +/// Runtime bootstrap configuration - can be either inline code or a file path +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, +)] +#[serde(untagged)] +pub enum RuntimeBootstrapConfig { + /// Inline JavaScript code + Code(RcStr), + /// Path to a JavaScript/TypeScript file + Path(RcStr), +} + +#[turbo_tasks::value(transparent)] +pub struct OptionRuntimeBootstrapConfig(Option); + #[turbo_tasks::value(serialization = "custom", eq = "manual")] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, OperationValue)] #[serde(rename_all = "camelCase")] @@ -142,6 +157,7 @@ pub struct Config { cache_handler: Option, node_polyfill: Option, dev_server: Option, + runtime_bootstrap: Option, #[serde(default)] experimental: ExperimentalConfig, #[cfg(feature = "test")] @@ -1191,6 +1207,11 @@ impl Config { Vc::cell(self.node_polyfill.unwrap_or(false)) } + #[turbo_tasks::function] + pub fn runtime_bootstrap(&self) -> Vc { + OptionRuntimeBootstrapConfig(self.runtime_bootstrap.clone()).cell() + } + #[turbo_tasks::function] pub async fn import_externals(&self) -> Result> { Ok(Vc::cell(match self.experimental.esm_externals {