diff --git a/Cargo.lock b/Cargo.lock index 52590a257dc76..2f8208f68d029 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9148,6 +9148,7 @@ dependencies = [ "turbo-tasks-memory", "turbopack", "turbopack-bench", + "turbopack-build", "turbopack-cli-utils", "turbopack-core", "turbopack-dev", diff --git a/crates/node-file-trace/src/lib.rs b/crates/node-file-trace/src/lib.rs index 8861b1d2c9c53..99988005bd785 100644 --- a/crates/node-file-trace/src/lib.rs +++ b/crates/node-file-trace/src/lib.rs @@ -505,7 +505,11 @@ async fn run>( let console_ui = ConsoleUi::new(log_options); Vc::upcast::>(console_ui) - .report_issues(TransientInstance::new(issues), source) + .report_issues( + TransientInstance::new(issues), + source, + IssueSeverity::Error.cell(), + ) .await?; if has_return_value { diff --git a/crates/turbopack-build/src/chunking_context.rs b/crates/turbopack-build/src/chunking_context.rs index 7e3c6fb8bbe2e..ca18e1e1fd5b0 100644 --- a/crates/turbopack-build/src/chunking_context.rs +++ b/crates/turbopack-build/src/chunking_context.rs @@ -360,8 +360,8 @@ where let chunks = ecmascript_chunks .iter() .copied() - .map(|chunk| Vc::upcast(chunk)) - .chain(css_chunks.iter().copied().map(|chunk| Vc::upcast(chunk))) + .map(Vc::upcast) + .chain(css_chunks.iter().copied().map(Vc::upcast)) .chain(other_chunks) .collect(); diff --git a/crates/turbopack-cli-utils/src/issue.rs b/crates/turbopack-cli-utils/src/issue.rs index 06fd71e823bbf..cfb8aee709bfc 100644 --- a/crates/turbopack-cli-utils/src/issue.rs +++ b/crates/turbopack-cli-utils/src/issue.rs @@ -351,6 +351,7 @@ impl IssueReporter for ConsoleUi { &self, issues: TransientInstance>, source: TransientValue, + min_failing_severity: Vc, ) -> Result> { let issues = &*issues; let LogOptions { @@ -387,7 +388,7 @@ impl IssueReporter for ConsoleUi { } let severity = plain_issue.severity; - if severity == IssueSeverity::Fatal { + if severity <= *min_failing_severity.await? { has_fatal = true; } diff --git a/crates/turbopack-cli/Cargo.toml b/crates/turbopack-cli/Cargo.toml index 1ac25fa4ed7d8..bae0410b2d5b9 100644 --- a/crates/turbopack-cli/Cargo.toml +++ b/crates/turbopack-cli/Cargo.toml @@ -57,6 +57,7 @@ turbo-tasks-fs = { workspace = true } turbo-tasks-malloc = { workspace = true, default-features = false } turbo-tasks-memory = { workspace = true } turbopack = { workspace = true } +turbopack-build = { workspace = true } turbopack-cli-utils = { workspace = true } turbopack-core = { workspace = true } turbopack-dev = { workspace = true } diff --git a/crates/turbopack-cli/benches/bundler.rs b/crates/turbopack-cli/benches/bundler.rs index 0583b9207643d..75c3e29ed2b68 100644 --- a/crates/turbopack-cli/benches/bundler.rs +++ b/crates/turbopack-cli/benches/bundler.rs @@ -57,9 +57,11 @@ impl Bundler for Turbopack { let mut proc = Command::new(binary) .args([ "dev", + "--dir", test_dir .to_str() .ok_or_else(|| anyhow!("failed to convert test directory path to string"))?, + "src/index", "--no-open", "--port", "0", diff --git a/crates/turbopack-cli/src/arguments.rs b/crates/turbopack-cli/src/arguments.rs index c9a4e7ab73d50..cb1277b49026d 100644 --- a/crates/turbopack-cli/src/arguments.rs +++ b/crates/turbopack-cli/src/arguments.rs @@ -21,11 +21,16 @@ impl Arguments { } } -#[derive(Debug, Args)] +#[derive(Debug, Args, Clone)] pub struct CommonArguments { + /// The entrypoints of the project. Resolved relative to the project's + /// directory (`--dir`). + #[clap(value_parser)] + pub entries: Option>, + /// The directory of the application. /// If no directory is provided, the current directory will be used. - #[clap(value_parser)] + #[clap(short, long, value_parser)] pub dir: Option, /// The root directory of the project. Nothing outside of this directory can diff --git a/crates/turbopack-cli/src/contexts.rs b/crates/turbopack-cli/src/contexts.rs new file mode 100644 index 0000000000000..9f8b6185947a4 --- /dev/null +++ b/crates/turbopack-cli/src/contexts.rs @@ -0,0 +1,200 @@ +use std::{collections::HashMap, fmt}; + +use anyhow::Result; +use turbo_tasks::{Value, Vc}; +use turbo_tasks_fs::{FileSystem, FileSystemPath}; +use turbopack::{ + condition::ContextCondition, + module_options::{CustomEcmascriptTransformPlugins, JsxTransformOptions, ModuleOptionsContext}, + resolve_options_context::ResolveOptionsContext, + ModuleAssetContext, +}; +use turbopack_core::{ + compile_time_defines, + compile_time_info::{CompileTimeDefines, CompileTimeInfo}, + context::AssetContext, + environment::{BrowserEnvironment, Environment, ExecutionEnvironment}, + resolve::options::{ImportMap, ImportMapping}, +}; +use turbopack_dev::react_refresh::assert_can_resolve_react_refresh; +use turbopack_ecmascript_plugins::transform::{ + emotion::{EmotionTransformConfig, EmotionTransformer}, + styled_components::{StyledComponentsTransformConfig, StyledComponentsTransformer}, + styled_jsx::StyledJsxTransformer, +}; +use turbopack_node::execution_context::ExecutionContext; + +#[turbo_tasks::value(shared)] +pub enum NodeEnv { + Development, + Production, +} + +impl fmt::Display for NodeEnv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NodeEnv::Development => f.write_str("development"), + NodeEnv::Production => f.write_str("production"), + } + } +} + +async fn foreign_code_context_condition() -> Result { + Ok(ContextCondition::InDirectory("node_modules".to_string())) +} + +#[turbo_tasks::function] +pub async fn get_client_import_map(project_path: Vc) -> Result> { + let mut import_map = ImportMap::empty(); + + import_map.insert_singleton_alias("@swc/helpers", project_path); + import_map.insert_singleton_alias("styled-jsx", project_path); + import_map.insert_singleton_alias("react", project_path); + import_map.insert_singleton_alias("react-dom", project_path); + + import_map.insert_wildcard_alias( + "@vercel/turbopack-ecmascript-runtime/", + ImportMapping::PrimaryAlternative( + "./*".to_string(), + Some(turbopack_ecmascript_runtime::embed_fs().root()), + ) + .cell(), + ); + + Ok(import_map.cell()) +} + +#[turbo_tasks::function] +pub async fn get_client_resolve_options_context( + project_path: Vc, +) -> Result> { + let next_client_import_map = get_client_import_map(project_path); + let module_options_context = ResolveOptionsContext { + enable_node_modules: Some(project_path.root().resolve().await?), + custom_conditions: vec!["development".to_string()], + import_map: Some(next_client_import_map), + browser: true, + module: true, + ..Default::default() + }; + Ok(ResolveOptionsContext { + enable_typescript: true, + enable_react: true, + rules: vec![( + foreign_code_context_condition().await?, + module_options_context.clone().cell(), + )], + ..module_options_context + } + .cell()) +} + +#[turbo_tasks::function] +async fn get_client_module_options_context( + project_path: Vc, + execution_context: Vc, + env: Vc, +) -> Result> { + let module_options_context = ModuleOptionsContext { + preset_env_versions: Some(env), + execution_context: Some(execution_context), + ..Default::default() + }; + + let resolve_options_context = get_client_resolve_options_context(project_path); + + let enable_react_refresh = + assert_can_resolve_react_refresh(project_path, resolve_options_context) + .await? + .is_found(); + + let enable_jsx = Some( + JsxTransformOptions { + react_refresh: enable_react_refresh, + ..Default::default() + } + .cell(), + ); + + let custom_ecma_transform_plugins = Some(CustomEcmascriptTransformPlugins::cell( + CustomEcmascriptTransformPlugins { + source_transforms: vec![ + Vc::cell(Box::new( + EmotionTransformer::new(&EmotionTransformConfig::default()) + .expect("Should be able to create emotion transformer"), + ) as _), + Vc::cell(Box::new(StyledComponentsTransformer::new( + &StyledComponentsTransformConfig::default(), + )) as _), + Vc::cell(Box::new(StyledJsxTransformer::new()) as _), + ], + output_transforms: vec![], + }, + )); + + let module_options_context = ModuleOptionsContext { + enable_jsx, + enable_postcss_transform: Some(Default::default()), + enable_typescript_transform: Some(Default::default()), + rules: vec![( + foreign_code_context_condition().await?, + module_options_context.clone().cell(), + )], + custom_ecma_transform_plugins, + ..module_options_context + } + .cell(); + + Ok(module_options_context) +} + +#[turbo_tasks::function] +pub fn get_client_asset_context( + project_path: Vc, + execution_context: Vc, + compile_time_info: Vc, +) -> Vc> { + let resolve_options_context = get_client_resolve_options_context(project_path); + let module_options_context = get_client_module_options_context( + project_path, + execution_context, + compile_time_info.environment(), + ); + + let context: Vc> = Vc::upcast(ModuleAssetContext::new( + Vc::cell(HashMap::new()), + compile_time_info, + module_options_context, + resolve_options_context, + )); + + context +} + +fn client_defines(node_env: &NodeEnv) -> Vc { + compile_time_defines!( + process.turbopack = true, + process.env.NODE_ENV = node_env.to_string() + ) + .cell() +} + +#[turbo_tasks::function] +pub async fn get_client_compile_time_info( + browserslist_query: String, + node_env: Vc, +) -> Result> { + Ok( + CompileTimeInfo::builder(Environment::new(Value::new(ExecutionEnvironment::Browser( + BrowserEnvironment { + dom: true, + web_worker: false, + service_worker: false, + browserslist_query, + } + .into(), + )))) + .defines(client_defines(&*node_env.await?)) + .cell(), + ) +} diff --git a/crates/turbopack-cli/src/dev/mod.rs b/crates/turbopack-cli/src/dev/mod.rs index f7e6999e15b86..53841b1f95c05 100644 --- a/crates/turbopack-cli/src/dev/mod.rs +++ b/crates/turbopack-cli/src/dev/mod.rs @@ -10,13 +10,12 @@ use std::{ }; use anyhow::{Context, Result}; -use dunce::canonicalize; use owo_colors::OwoColorize; use turbo_tasks::{ util::{FormatBytes, FormatDuration}, StatsType, TransientInstance, TurboTasks, TurboTasksBackendApi, UpdateInfo, Value, Vc, }; -use turbo_tasks_fs::{DiskFileSystem, FileSystem}; +use turbo_tasks_fs::FileSystem; use turbo_tasks_malloc::TurboMalloc; use turbo_tasks_memory::MemoryBackend; use turbopack::evaluate_context::node_build_environment; @@ -41,17 +40,17 @@ use turbopack_env::dotenv::load_env; use turbopack_node::execution_context::ExecutionContext; use self::web_entry_source::create_web_entry_source; -use crate::arguments::DevArguments; +use crate::{ + arguments::DevArguments, + contexts::NodeEnv, + util::{ + normalize_dirs, normalize_entries, output_fs, project_fs, EntryRequest, NormalizedDirs, + }, +}; pub(crate) mod turbo_tasks_viz; pub(crate) mod web_entry_source; -#[derive(Clone)] -pub enum EntryRequest { - Relative(String), - Module(String, String), -} - pub struct TurbopackDevServerBuilder { turbo_tasks: Arc>, project_dir: String, @@ -229,20 +228,6 @@ impl TurbopackDevServerBuilder { } } -#[turbo_tasks::function] -async fn project_fs(project_dir: String) -> Result>> { - let disk_fs = DiskFileSystem::new("project".to_string(), project_dir.to_string()); - disk_fs.await?.start_watching()?; - Ok(Vc::upcast(disk_fs)) -} - -#[turbo_tasks::function] -async fn output_fs(project_dir: String) -> Result>> { - let disk_fs = DiskFileSystem::new("output".to_string(), project_dir.to_string()); - disk_fs.await?.start_watching()?; - Ok(Vc::upcast(disk_fs)) -} - #[allow(clippy::too_many_arguments)] #[turbo_tasks::function] async fn source( @@ -297,6 +282,7 @@ async fn source( server_root, env, eager_compile, + NodeEnv::Development.cell(), browserslist_query, ); let viz = Vc::upcast(turbo_tasks_viz::TurboTasksSource::new(turbo_tasks.into())); @@ -339,26 +325,10 @@ pub async fn start_server(args: &DevArguments) -> Result<()> { console_subscriber::init(); register(); - let dir = args - .common - .dir - .as_ref() - .map(canonicalize) - .unwrap_or_else(current_dir) - .context("project directory can't be found")? - .to_str() - .context("project directory contains invalid characters")? - .to_string(); - - let root_dir = if let Some(root) = args.common.root.as_ref() { - canonicalize(root) - .context("root directory can't be found")? - .to_str() - .context("root directory contains invalid characters")? - .to_string() - } else { - dir.clone() - }; + let NormalizedDirs { + project_dir, + root_dir, + } = normalize_dirs(&args.common.dir, &args.common.root)?; let tt = TurboTasks::new(MemoryBackend::new( args.common @@ -374,9 +344,7 @@ pub async fn start_server(args: &DevArguments) -> Result<()> { let tt_clone = tt.clone(); - #[allow(unused_mut)] - let mut server = TurbopackDevServerBuilder::new(tt, dir, root_dir) - .entry_request(EntryRequest::Relative("src/index".into())) + let mut server = TurbopackDevServerBuilder::new(tt, project_dir, root_dir) .eager_compile(args.eager_compile) .hostname(args.hostname) .port(args.port) @@ -388,6 +356,10 @@ pub async fn start_server(args: &DevArguments) -> Result<()> { .map_or_else(|| IssueSeverity::Warning, |l| l.0), ); + for entry in normalize_entries(&args.common.entries) { + server = server.entry_request(EntryRequest::Relative(entry)) + } + #[cfg(feature = "serializable")] { server = server.allow_retry(args.allow_retry); diff --git a/crates/turbopack-cli/src/dev/web_entry_source.rs b/crates/turbopack-cli/src/dev/web_entry_source.rs index 0b576c63d73d4..63ce8ee948fda 100644 --- a/crates/turbopack-cli/src/dev/web_entry_source.rs +++ b/crates/turbopack-cli/src/dev/web_entry_source.rs @@ -1,27 +1,15 @@ -use std::collections::HashMap; - use anyhow::{anyhow, Result}; use turbo_tasks::{TryJoinIterExt, Value, Vc}; use turbo_tasks_env::ProcessEnv; -use turbo_tasks_fs::{FileSystem, FileSystemPath}; -use turbopack::{ - condition::ContextCondition, - ecmascript::EcmascriptModuleAsset, - module_options::{CustomEcmascriptTransformPlugins, JsxTransformOptions, ModuleOptionsContext}, - resolve_options_context::ResolveOptionsContext, - ModuleAssetContext, -}; +use turbo_tasks_fs::FileSystemPath; +use turbopack::ecmascript::EcmascriptModuleAsset; use turbopack_cli_utils::runtime_entry::{RuntimeEntries, RuntimeEntry}; use turbopack_core::{ chunk::{ChunkableModule, ChunkingContext}, - compile_time_defines, - compile_time_info::{CompileTimeDefines, CompileTimeInfo}, - context::AssetContext, - environment::{BrowserEnvironment, Environment, ExecutionEnvironment}, + environment::Environment, file_source::FileSource, reference_type::{EntryReferenceSubType, ReferenceType}, resolve::{ - options::{ImportMap, ImportMapping}, origin::{PlainResolveOrigin, ResolveOriginExt}, parse::Request, }, @@ -31,169 +19,15 @@ use turbopack_dev_server::{ html::DevHtmlAsset, source::{asset_graph::AssetGraphContentSource, ContentSource}, }; -use turbopack_ecmascript_plugins::transform::{ - emotion::{EmotionTransformConfig, EmotionTransformer}, - styled_components::{StyledComponentsTransformConfig, StyledComponentsTransformer}, - styled_jsx::StyledJsxTransformer, -}; use turbopack_node::execution_context::ExecutionContext; -use crate::embed_js::embed_file_path; - -async fn foreign_code_context_condition() -> Result { - Ok(ContextCondition::InDirectory("node_modules".to_string())) -} - -#[turbo_tasks::function] -pub async fn get_client_import_map(project_path: Vc) -> Result> { - let mut import_map = ImportMap::empty(); - - import_map.insert_singleton_alias("@swc/helpers", project_path); - import_map.insert_singleton_alias("styled-jsx", project_path); - import_map.insert_singleton_alias("react", project_path); - import_map.insert_singleton_alias("react-dom", project_path); - - import_map.insert_wildcard_alias( - "@vercel/turbopack-ecmascript-runtime/", - ImportMapping::PrimaryAlternative( - "./*".to_string(), - Some(turbopack_ecmascript_runtime::embed_fs().root()), - ) - .cell(), - ); - - Ok(import_map.cell()) -} - -#[turbo_tasks::function] -async fn get_client_resolve_options_context( - project_path: Vc, -) -> Result> { - let next_client_import_map = get_client_import_map(project_path); - let module_options_context = ResolveOptionsContext { - enable_node_modules: Some(project_path.root().resolve().await?), - custom_conditions: vec!["development".to_string()], - import_map: Some(next_client_import_map), - browser: true, - module: true, - ..Default::default() - }; - Ok(ResolveOptionsContext { - enable_typescript: true, - enable_react: true, - rules: vec![( - foreign_code_context_condition().await?, - module_options_context.clone().cell(), - )], - ..module_options_context - } - .cell()) -} - -#[turbo_tasks::function] -async fn get_client_module_options_context( - project_path: Vc, - execution_context: Vc, - env: Vc, -) -> Result> { - let module_options_context = ModuleOptionsContext { - preset_env_versions: Some(env), - execution_context: Some(execution_context), - ..Default::default() - }; - - let resolve_options_context = get_client_resolve_options_context(project_path); - - let enable_react_refresh = - assert_can_resolve_react_refresh(project_path, resolve_options_context) - .await? - .is_found(); - - let enable_jsx = Some( - JsxTransformOptions { - react_refresh: enable_react_refresh, - ..Default::default() - } - .cell(), - ); - - let custom_ecma_transform_plugins = Some(CustomEcmascriptTransformPlugins::cell( - CustomEcmascriptTransformPlugins { - source_transforms: vec![ - Vc::cell(Box::new( - EmotionTransformer::new(&EmotionTransformConfig::default()) - .expect("Should be able to create emotion transformer"), - ) as _), - Vc::cell(Box::new(StyledComponentsTransformer::new( - &StyledComponentsTransformConfig::default(), - )) as _), - Vc::cell(Box::new(StyledJsxTransformer::new()) as _), - ], - output_transforms: vec![], - }, - )); - - let module_options_context = ModuleOptionsContext { - enable_jsx, - enable_postcss_transform: Some(Default::default()), - enable_typescript_transform: Some(Default::default()), - rules: vec![( - foreign_code_context_condition().await?, - module_options_context.clone().cell(), - )], - custom_ecma_transform_plugins, - ..module_options_context - } - .cell(); - - Ok(module_options_context) -} - -#[turbo_tasks::function] -fn get_client_asset_context( - project_path: Vc, - execution_context: Vc, - compile_time_info: Vc, -) -> Vc> { - let resolve_options_context = get_client_resolve_options_context(project_path); - let module_options_context = get_client_module_options_context( - project_path, - execution_context, - compile_time_info.environment(), - ); - - let context: Vc> = Vc::upcast(ModuleAssetContext::new( - Vc::cell(HashMap::new()), - compile_time_info, - module_options_context, - resolve_options_context, - )); - - context -} - -pub fn client_defines() -> Vc { - compile_time_defines!( - process.turbopack = true, - process.env.NODE_ENV = "development", - ) - .cell() -} - -#[turbo_tasks::function] -pub fn get_client_compile_time_info(browserslist_query: String) -> Vc { - CompileTimeInfo::builder(Environment::new(Value::new(ExecutionEnvironment::Browser( - BrowserEnvironment { - dom: true, - web_worker: false, - service_worker: false, - browserslist_query, - } - .into(), - )))) - .defines(client_defines()) - .cell() -} +use crate::{ + contexts::{ + get_client_asset_context, get_client_compile_time_info, get_client_resolve_options_context, + NodeEnv, + }, + embed_js::embed_file_path, +}; #[turbo_tasks::function] pub fn get_client_chunking_context( @@ -252,9 +86,10 @@ pub async fn create_web_entry_source( server_root: Vc, _env: Vc>, eager_compile: bool, + node_env: Vc, browserslist_query: String, ) -> Result>> { - let compile_time_info = get_client_compile_time_info(browserslist_query); + let compile_time_info = get_client_compile_time_info(browserslist_query, node_env); let context = get_client_asset_context(project_path, execution_context, compile_time_info); let chunking_context = get_client_chunking_context(project_path, server_root, compile_time_info.environment()); diff --git a/crates/turbopack-cli/src/lib.rs b/crates/turbopack-cli/src/lib.rs index 7a7850615803b..65cd659a7ed15 100644 --- a/crates/turbopack-cli/src/lib.rs +++ b/crates/turbopack-cli/src/lib.rs @@ -2,13 +2,17 @@ #![feature(min_specialization)] #![feature(arbitrary_self_types)] #![feature(async_fn_in_trait)] +#![allow(clippy::too_many_arguments)] pub mod arguments; +pub(crate) mod contexts; pub mod dev; pub(crate) mod embed_js; +pub(crate) mod util; pub fn register() { turbopack::register(); + turbopack_build::register(); turbopack_dev::register(); turbopack_ecmascript_plugins::register(); include!(concat!(env!("OUT_DIR"), "/register.rs")); diff --git a/crates/turbopack-cli/src/util.rs b/crates/turbopack-cli/src/util.rs new file mode 100644 index 0000000000000..eb07b08434138 --- /dev/null +++ b/crates/turbopack-cli/src/util.rs @@ -0,0 +1,74 @@ +use std::{env::current_dir, path::PathBuf}; + +use anyhow::{Context, Result}; +use dunce::canonicalize; +use turbo_tasks::Vc; +use turbo_tasks_fs::{DiskFileSystem, FileSystem}; + +#[turbo_tasks::value(transparent)] +pub struct EntryRequests(Vec>); + +#[turbo_tasks::value(shared)] +#[derive(Clone)] +pub enum EntryRequest { + Relative(String), + Module(String, String), +} + +pub struct NormalizedDirs { + /// Normalized project directory path as an absolute path + pub project_dir: String, + /// Normalized root directory path as an absolute path + pub root_dir: String, +} + +/// Normalizes (canonicalizes and represents as an absolute path in a String) +/// the project and root directories. +pub fn normalize_dirs( + project_dir: &Option, + root_dir: &Option, +) -> Result { + let project_dir = project_dir + .as_ref() + .map(canonicalize) + .unwrap_or_else(current_dir) + .context("project directory can't be found")? + .to_str() + .context("project directory contains invalid characters")? + .to_string(); + + let root_dir = match root_dir.as_ref() { + Some(root) => canonicalize(root) + .context("root directory can't be found")? + .to_str() + .context("root directory contains invalid characters")? + .to_string(), + None => project_dir.clone(), + }; + + Ok(NormalizedDirs { + project_dir, + root_dir, + }) +} + +pub fn normalize_entries(entries: &Option>) -> Vec { + entries + .as_ref() + .cloned() + .unwrap_or_else(|| vec!["src/entry".to_owned()]) +} + +#[turbo_tasks::function] +pub async fn project_fs(project_dir: String) -> Result>> { + let disk_fs = DiskFileSystem::new("project".to_string(), project_dir.to_string()); + disk_fs.await?.start_watching()?; + Ok(Vc::upcast(disk_fs)) +} + +#[turbo_tasks::function] +pub async fn output_fs(project_dir: String) -> Result>> { + let disk_fs = DiskFileSystem::new("output".to_string(), project_dir.to_string()); + disk_fs.await?.start_watching()?; + Ok(Vc::upcast(disk_fs)) +} diff --git a/crates/turbopack-core/src/issue/mod.rs b/crates/turbopack-core/src/issue/mod.rs index e2d46c7845951..e19d12ec3f7a7 100644 --- a/crates/turbopack-core/src/issue/mod.rs +++ b/crates/turbopack-core/src/issue/mod.rs @@ -9,7 +9,7 @@ use std::{ sync::Arc, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use async_trait::async_trait; use auto_hash_map::AutoSet; use turbo_tasks::{ @@ -604,10 +604,22 @@ pub struct PlainIssueProcessingPathItem { #[turbo_tasks::value_trait] pub trait IssueReporter { + /// Reports issues to the user (e.g. to stdio). Returns whether fatal + /// (program-ending) issues were present. + /// + /// # Arguments: + /// + /// * `issues` - A [ReadRef] of [CapturedIssues]. Typically obtained with + /// `source.peek_issues_with_path()`. + /// * `source` - The root [Vc] from which issues are traced. Can be used by + /// implementers to determine which issues are new. + /// * `min_failing_severity` - The minimum Vc<[IssueSeverity]> + /// The minimum issue severity level considered to fatally end the program. fn report_issues( self: Vc, issues: TransientInstance>, source: TransientValue, + min_failing_severity: Vc, ) -> Vc; } @@ -731,3 +743,37 @@ where })) } } + +pub async fn handle_issues( + source: Vc, + issue_reporter: Vc>, + min_failing_severity: Vc, + path: Option<&str>, + operation: Option<&str>, +) -> Result<()> { + let issues = source + .peek_issues_with_path() + .await? + .strongly_consistent() + .await?; + + let has_fatal = issue_reporter.report_issues( + TransientInstance::new(issues.clone()), + TransientValue::new(source.node), + min_failing_severity, + ); + + if *has_fatal.await? { + let mut message = "Fatal issue(s) occurred".to_owned(); + if let Some(path) = path.as_ref() { + message += &format!(" in {path}"); + }; + if let Some(operation) = operation.as_ref() { + message += &format!(" ({operation})"); + }; + + Err(anyhow!(message)) + } else { + Ok(()) + } +} diff --git a/crates/turbopack-core/src/reference/mod.rs b/crates/turbopack-core/src/reference/mod.rs index ae2900651336c..fceede101958b 100644 --- a/crates/turbopack-core/src/reference/mod.rs +++ b/crates/turbopack-core/src/reference/mod.rs @@ -1,12 +1,16 @@ use std::collections::{HashSet, VecDeque}; use anyhow::Result; -use turbo_tasks::{TryJoinIterExt, ValueToString, Vc}; +use turbo_tasks::{ + graph::{AdjacencyMap, GraphTraversal}, + TryJoinIterExt, ValueToString, Vc, +}; use crate::{ asset::Asset, issue::IssueContextExt, module::{convert_asset_to_module, Module, Modules}, + output::{OutputAsset, OutputAssets}, resolve::{PrimaryResolveResult, ResolveResult}, }; pub mod source_map; @@ -178,3 +182,35 @@ pub async fn all_modules(asset: Vc>) -> Result> { } Ok(Vc::cell(assets.into_iter().collect())) } + +/// Walks the asset graph from multiple assets and collect all referenced +/// assets. +#[turbo_tasks::function] +pub async fn all_assets_from_entries(entries: Vc) -> Result> { + Ok(Vc::cell( + AdjacencyMap::new() + .skip_duplicates() + .visit( + entries.await?.iter().copied().map(Vc::upcast), + get_referenced_assets, + ) + .await + .completed()? + .into_inner() + .into_reverse_topological() + .collect(), + )) +} + +/// Computes the list of all chunk children of a given chunk. +pub async fn get_referenced_assets( + asset: Vc>, +) -> Result>> + Send> { + Ok(asset + .references() + .await? + .iter() + .copied() + .collect::>() + .into_iter()) +} diff --git a/crates/turbopack-dev-server/src/http.rs b/crates/turbopack-dev-server/src/http.rs index c5d2e30728347..9caae484387ac 100644 --- a/crates/turbopack-dev-server/src/http.rs +++ b/crates/turbopack-dev-server/src/http.rs @@ -14,15 +14,16 @@ use tokio_util::io::{ReaderStream, StreamReader}; use turbo_tasks::{util::SharedError, CollectiblesSource, ReadRef, TransientInstance, Vc}; use turbo_tasks_bytes::Bytes; use turbo_tasks_fs::FileContent; -use turbopack_core::{asset::AssetContent, issue::IssueReporter, version::VersionedContent}; - -use crate::{ - handle_issues, - source::{ - request::SourceRequest, - resolve::{resolve_source_request, ResolveSourceRequestResult}, - Body, ContentSource, ContentSourceSideEffect, HeaderList, ProxyResult, - }, +use turbopack_core::{ + asset::AssetContent, + issue::{handle_issues, IssueReporter, IssueSeverity}, + version::VersionedContent, +}; + +use crate::source::{ + request::SourceRequest, + resolve::{resolve_source_request, ResolveSourceRequestResult}, + Body, ContentSource, ContentSourceSideEffect, HeaderList, ProxyResult, }; #[turbo_tasks::value(serialization = "none")] @@ -79,7 +80,14 @@ pub async fn process_request_with_content_source( let original_path = request.uri().path().to_string(); let request = http_request_to_source_request(request).await?; let result = get_from_source(source, TransientInstance::new(request)); - handle_issues(result, &original_path, "get_from_source", issue_reporter).await?; + handle_issues( + result, + issue_reporter, + IssueSeverity::Fatal.cell(), + Some(&original_path), + Some("get_from_source"), + ) + .await?; let side_effects: AutoSet>> = result.peek_collectibles().strongly_consistent().await?; match &*result.strongly_consistent().await? { diff --git a/crates/turbopack-dev-server/src/lib.rs b/crates/turbopack-dev-server/src/lib.rs index 4fa523014d4df..898a0056df23d 100644 --- a/crates/turbopack-dev-server/src/lib.rs +++ b/crates/turbopack-dev-server/src/lib.rs @@ -22,7 +22,7 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use hyper::{ server::{conn::AddrIncoming, Builder}, service::{make_service_fn, service_fn}, @@ -33,12 +33,11 @@ use socket2::{Domain, Protocol, Socket, Type}; use tokio::task::JoinHandle; use tracing::{event, info_span, Instrument, Level, Span}; use turbo_tasks::{ - run_once_with_reason, trace::TraceRawVcs, util::FormatDuration, TransientInstance, - TransientValue, TurboTasksApi, Vc, + run_once_with_reason, trace::TraceRawVcs, util::FormatDuration, TurboTasksApi, Vc, }; use turbopack_core::{ error::PrettyPrintError, - issue::{IssueContextExt, IssueReporter}, + issue::{handle_issues, IssueReporter, IssueSeverity}, }; use self::{source::ContentSource, update::UpdateServer}; @@ -77,30 +76,6 @@ pub struct DevServer { pub future: Pin> + Send + 'static>>, } -async fn handle_issues( - source: Vc, - path: &str, - operation: &str, - issue_reporter: Vc>, -) -> Result<()> { - let issues = source - .peek_issues_with_path() - .await? - .strongly_consistent() - .await?; - - let has_fatal = issue_reporter.report_issues( - TransientInstance::new(issues.clone()), - TransientValue::new(source.node), - ); - - if *has_fatal.await? { - Err(anyhow!("Fatal issue(s) occurred in {path} ({operation})")) - } else { - Ok(()) - } -} - impl DevServer { pub fn listen(addr: SocketAddr) -> Result { // This is annoying. The hyper::Server doesn't allow us to know which port was @@ -235,7 +210,14 @@ impl DevServerBuilder { let uri = request.uri(); let path = uri.path().to_string(); let source = source_provider.get_source(); - handle_issues(source, &path, "get source", issue_reporter).await?; + handle_issues( + source, + issue_reporter, + IssueSeverity::Fatal.cell(), + Some(&path), + Some("get source"), + ) + .await?; let resolved_source = source.resolve_strongly_consistent().await?; let (response, side_effects) = http::process_request_with_content_source(