| | use std::borrow::Cow; |
| |
|
| | use anyhow::{Ok, Result}; |
| | use either::Either; |
| | use futures::join; |
| | use next_core::{ |
| | next_client_reference::{ |
| | ClientReference, ClientReferenceGraphResult, ClientReferenceType, ServerEntries, |
| | find_server_entries, |
| | }, |
| | next_dynamic::NextDynamicEntryModule, |
| | next_manifests::ActionLayer, |
| | next_server_utility::server_utility_module::NextServerUtilityModule, |
| | }; |
| | use rustc_hash::FxHashMap; |
| | use tracing::Instrument; |
| | use turbo_rcstr::{RcStr, rcstr}; |
| | use turbo_tasks::{ |
| | CollectiblesSource, FxIndexMap, FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc, |
| | }; |
| | use turbo_tasks_fs::FileSystemPath; |
| | use turbopack_core::{ |
| | context::AssetContext, |
| | issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString}, |
| | module::Module, |
| | module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraphWithBindingUsage}, |
| | }; |
| | use turbopack_css::{CssModuleAsset, ModuleCssAsset}; |
| |
|
| | use crate::{ |
| | client_references::{ClientManifestEntryType, ClientReferenceData, map_client_references}, |
| | dynamic_imports::{DynamicImportEntries, DynamicImportEntriesMapType, map_next_dynamic}, |
| | server_actions::{AllActions, AllModuleActions, map_server_actions, to_rsc_context}, |
| | }; |
| |
|
| | #[turbo_tasks::value] |
| | pub struct NextDynamicGraph { |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| |
|
| | |
| | data: ResolvedVc<DynamicImportEntries>, |
| | } |
| |
|
| | #[turbo_tasks::value] |
| | pub struct NextDynamicGraphs(Vec<ResolvedVc<NextDynamicGraph>>); |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl NextDynamicGraphs { |
| | #[turbo_tasks::function(operation)] |
| | async fn new_operation( |
| | graphs: ResolvedVc<ModuleGraph>, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let graphs_ref = &graphs.await?; |
| | let next_dynamic = async { |
| | graphs_ref |
| | .iter_graphs() |
| | .map(|graph| { |
| | NextDynamicGraph::new_with_entries(graph, is_single_page).to_resolved() |
| | }) |
| | .try_join() |
| | .await |
| | } |
| | .instrument(tracing::info_span!("generating next/dynamic graphs")) |
| | .await?; |
| | Ok(Self(next_dynamic).cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn new(graphs: ResolvedVc<ModuleGraph>, is_single_page: bool) -> Result<Vc<Self>> { |
| | |
| | |
| | let result_op = Self::new_operation(graphs, is_single_page); |
| | let result_vc = if !is_single_page { |
| | let result_vc = result_op.resolve_strongly_consistent().await?; |
| | result_op.drop_collectibles::<Box<dyn Issue>>(); |
| | *result_vc |
| | } else { |
| | result_op.connect() |
| | }; |
| | Ok(result_vc) |
| | } |
| |
|
| | |
| | |
| | #[turbo_tasks::function] |
| | pub async fn get_next_dynamic_imports_for_endpoint( |
| | &self, |
| | entry: Vc<Box<dyn Module>>, |
| | ) -> Result<Vc<DynamicImportEntriesWithImporter>> { |
| | let span = tracing::info_span!("collect all next/dynamic imports for endpoint"); |
| | async move { |
| | if let [graph] = &self.0[..] { |
| | |
| | Ok(graph.get_next_dynamic_imports_for_endpoint(entry)) |
| | } else { |
| | let result = self |
| | .0 |
| | .iter() |
| | .map(|graph| async move { |
| | Ok(graph |
| | .get_next_dynamic_imports_for_endpoint(entry) |
| | .await? |
| | .into_iter() |
| | .map(|(k, v)| (*k, *v)) |
| | |
| | .collect::<Vec<_>>()) |
| | }) |
| | .try_flat_join() |
| | .await?; |
| |
|
| | Ok(Vc::cell(result.into_iter().collect())) |
| | } |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value(transparent)] |
| | pub struct DynamicImportEntriesWithImporter( |
| | pub Vec<( |
| | ResolvedVc<NextDynamicEntryModule>, |
| | Option<ClientReferenceType>, |
| | )>, |
| | ); |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl NextDynamicGraph { |
| | #[turbo_tasks::function] |
| | pub async fn new_with_entries( |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let mapped = map_next_dynamic(*graph.graph); |
| |
|
| | Ok(NextDynamicGraph { |
| | is_single_page, |
| | graph, |
| | data: mapped.to_resolved().await?, |
| | } |
| | .cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn get_next_dynamic_imports_for_endpoint( |
| | &self, |
| | entry: ResolvedVc<Box<dyn Module>>, |
| | ) -> Result<Vc<DynamicImportEntriesWithImporter>> { |
| | let span = tracing::info_span!("collect next/dynamic imports for endpoint"); |
| | async move { |
| | let data = &*self.data.await?; |
| | let graph = self.graph.read().await?; |
| |
|
| | #[derive(Clone, PartialEq, Eq)] |
| | enum VisitState { |
| | Entry, |
| | InClientReference(ClientReferenceType), |
| | } |
| |
|
| | let entries = if !self.is_single_page { |
| | if !graph.graphs.first().unwrap().has_entry_module(entry) { |
| | |
| | return Ok(Vc::cell(vec![])); |
| | } |
| | Either::Left(std::iter::once(entry)) |
| | } else { |
| | Either::Right(graph.graphs.first().unwrap().entry_modules()) |
| | }; |
| |
|
| | let mut result = vec![]; |
| |
|
| | |
| | let mut state_map = FxHashMap::default(); |
| | graph.traverse_edges_dfs( |
| | entries, |
| | &mut (), |
| | |parent_info, node, _| { |
| | let module = node; |
| | let Some((parent_node, _)) = parent_info else { |
| | state_map.insert(module, VisitState::Entry); |
| | return Ok(GraphTraversalAction::Continue); |
| | }; |
| | let parent_module = parent_node; |
| |
|
| | let module_type = data.get(&module); |
| | let parent_state = state_map.get(&parent_module).unwrap().clone(); |
| | let parent_client_reference = |
| | if let Some(DynamicImportEntriesMapType::ClientReference(module)) = |
| | module_type |
| | { |
| | Some(ClientReferenceType::EcmascriptClientReference(*module)) |
| | } else if let VisitState::InClientReference(ty) = parent_state { |
| | Some(ty) |
| | } else { |
| | None |
| | }; |
| |
|
| | Ok(match module_type { |
| | Some(DynamicImportEntriesMapType::DynamicEntry(dynamic_entry)) => { |
| | result.push((*dynamic_entry, parent_client_reference)); |
| |
|
| | state_map.insert(module, parent_state); |
| | GraphTraversalAction::Skip |
| | } |
| | Some(DynamicImportEntriesMapType::ClientReference(client_reference)) => { |
| | state_map.insert( |
| | module, |
| | VisitState::InClientReference( |
| | ClientReferenceType::EcmascriptClientReference( |
| | *client_reference, |
| | ), |
| | ), |
| | ); |
| | GraphTraversalAction::Continue |
| | } |
| | None => { |
| | state_map.insert(module, parent_state); |
| | GraphTraversalAction::Continue |
| | } |
| | }) |
| | }, |
| | |_, _, _| Ok(()), |
| | )?; |
| | Ok(Vc::cell(result)) |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value] |
| | pub struct ServerActionsGraph { |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| |
|
| | |
| | data: ResolvedVc<AllModuleActions>, |
| | } |
| |
|
| | #[turbo_tasks::value] |
| | pub struct ServerActionsGraphs(Vec<ResolvedVc<ServerActionsGraph>>); |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl ServerActionsGraphs { |
| | #[turbo_tasks::function(operation)] |
| | async fn new_operation( |
| | graphs: ResolvedVc<ModuleGraph>, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let graphs_ref = &graphs.await?; |
| | let server_actions = async { |
| | graphs_ref |
| | .iter_graphs() |
| | .map(|graph| { |
| | ServerActionsGraph::new_with_entries(graph, is_single_page).to_resolved() |
| | }) |
| | .try_join() |
| | .await |
| | } |
| | .instrument(tracing::info_span!("generating server actions graphs")) |
| | .await?; |
| | Ok(Self(server_actions).cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn new(graphs: ResolvedVc<ModuleGraph>, is_single_page: bool) -> Result<Vc<Self>> { |
| | |
| | |
| | let result_op = Self::new_operation(graphs, is_single_page); |
| | let result_vc = if !is_single_page { |
| | let result_vc = result_op.resolve_strongly_consistent().await?; |
| | result_op.drop_collectibles::<Box<dyn Issue>>(); |
| | *result_vc |
| | } else { |
| | result_op.connect() |
| | }; |
| | Ok(result_vc) |
| | } |
| |
|
| | |
| | #[turbo_tasks::function] |
| | pub async fn get_server_actions_for_endpoint( |
| | &self, |
| | entry: Vc<Box<dyn Module>>, |
| | rsc_asset_context: Vc<Box<dyn AssetContext>>, |
| | ) -> Result<Vc<AllActions>> { |
| | let span = tracing::info_span!("collect all server actions for endpoint"); |
| | async move { |
| | if let [graph] = &self.0[..] { |
| | |
| | Ok(graph.get_server_actions_for_endpoint(entry, rsc_asset_context)) |
| | } else { |
| | let result = self |
| | .0 |
| | .iter() |
| | .map(|graph| async move { |
| | graph |
| | .get_server_actions_for_endpoint(entry, rsc_asset_context) |
| | .owned() |
| | .await |
| | }) |
| | .try_flat_join() |
| | .await?; |
| |
|
| | Ok(Vc::cell(result)) |
| | } |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl ServerActionsGraph { |
| | #[turbo_tasks::function] |
| | pub async fn new_with_entries( |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let mapped = map_server_actions(*graph.graph); |
| |
|
| | Ok(ServerActionsGraph { |
| | is_single_page, |
| | graph, |
| | data: mapped.to_resolved().await?, |
| | } |
| | .cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn get_server_actions_for_endpoint( |
| | &self, |
| | entry: ResolvedVc<Box<dyn Module>>, |
| | rsc_asset_context: Vc<Box<dyn AssetContext>>, |
| | ) -> Result<Vc<AllActions>> { |
| | let span = tracing::info_span!("collect server actions for endpoint"); |
| | async move { |
| | let data = &*self.data.await?; |
| | let data = if self.is_single_page { |
| | |
| | Cow::Borrowed(data) |
| | } else { |
| | |
| | let graph = self.graph.read().await?; |
| |
|
| | if !graph.graphs.first().unwrap().has_entry_module(entry) { |
| | |
| | return Ok(Vc::cell(Default::default())); |
| | } |
| |
|
| | let mut result = FxIndexMap::default(); |
| | graph.traverse_nodes_dfs( |
| | vec![entry], |
| | &mut result, |
| | |node, result| { |
| | if let Some(node_data) = data.get(&node) { |
| | result.insert(node, *node_data); |
| | } |
| | Ok(GraphTraversalAction::Continue) |
| | }, |
| | |_, _| Ok(()), |
| | )?; |
| | Cow::Owned(result) |
| | }; |
| |
|
| | let actions = data |
| | .iter() |
| | .map(|(module, (layer, actions))| async move { |
| | let actions = actions.await?; |
| | actions |
| | .actions |
| | .iter() |
| | .map(async |(hash, name)| { |
| | Ok(( |
| | hash.to_string(), |
| | ( |
| | *layer, |
| | name.to_string(), |
| | if *layer == ActionLayer::Rsc { |
| | *module |
| | } else { |
| | to_rsc_context( |
| | **module, |
| | &actions.entry_path, |
| | &actions.entry_query, |
| | rsc_asset_context, |
| | ) |
| | .await? |
| | }, |
| | ), |
| | )) |
| | }) |
| | .try_join() |
| | .await |
| | }) |
| | .try_flat_join() |
| | .await?; |
| | Ok(Vc::cell(actions)) |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value] |
| | pub struct ClientReferencesGraph { |
| | is_single_page: bool, |
| | graph: SingleModuleGraphWithBindingUsage, |
| |
|
| | |
| | data: ResolvedVc<ClientReferenceData>, |
| | } |
| |
|
| | #[turbo_tasks::value] |
| | pub struct ClientReferencesGraphs(Vec<ResolvedVc<ClientReferencesGraph>>); |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl ClientReferencesGraphs { |
| | #[turbo_tasks::function(operation)] |
| | async fn new_operation( |
| | graphs: ResolvedVc<ModuleGraph>, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let graphs_ref = &graphs.await?; |
| | let client_references = async { |
| | graphs_ref |
| | .iter_graphs() |
| | .map(|graph| { |
| | ClientReferencesGraph::new_with_entries(graph, is_single_page).to_resolved() |
| | }) |
| | .try_join() |
| | .await |
| | } |
| | .instrument(tracing::info_span!("generating client references graphs")) |
| | .await?; |
| | Ok(Self(client_references).cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | pub async fn new(graphs: ResolvedVc<ModuleGraph>, is_single_page: bool) -> Result<Vc<Self>> { |
| | |
| | |
| | let result_op = Self::new_operation(graphs, is_single_page); |
| | let result_vc = if !is_single_page { |
| | let result_vc = result_op.resolve_strongly_consistent().await?; |
| | result_op.drop_collectibles::<Box<dyn Issue>>(); |
| | *result_vc |
| | } else { |
| | result_op.connect() |
| | }; |
| | Ok(result_vc) |
| | } |
| |
|
| | |
| | #[turbo_tasks::function] |
| | pub async fn get_client_references_for_endpoint( |
| | &self, |
| | entry: Vc<Box<dyn Module>>, |
| | has_layout_segments: bool, |
| | include_traced: bool, |
| | include_binding_usage: bool, |
| | ) -> Result<Vc<ClientReferenceGraphResult>> { |
| | let span = tracing::info_span!("collect all client references for endpoint"); |
| | async move { |
| | let result = if let [graph] = &self.0[..] { |
| | |
| | |
| | graph.get_client_references_for_endpoint(entry) |
| | } else { |
| | let results = self |
| | .0 |
| | .iter() |
| | .map(|graph| graph.get_client_references_for_endpoint(entry)) |
| | .try_join(); |
| | |
| | |
| | let server_entries = async { |
| | if has_layout_segments { |
| | let server_entries = |
| | find_server_entries(entry, include_traced, include_binding_usage) |
| | .await?; |
| | Ok(Some(server_entries)) |
| | } else { |
| | Ok(None) |
| | } |
| | }; |
| | |
| | |
| | let (results, server_entries) = join!(results, server_entries); |
| |
|
| | let mut result = ClientReferenceGraphResult { |
| | client_references: results? |
| | .iter() |
| | .flat_map(|r| r.client_references.iter().copied()) |
| | .collect(), |
| | ..Default::default() |
| | }; |
| | if let Some(ServerEntries { |
| | server_utils, |
| | server_component_entries, |
| | }) = server_entries?.as_deref() |
| | { |
| | result.server_utils = server_utils.clone(); |
| | result.server_component_entries = server_component_entries.clone(); |
| | } |
| | result.cell() |
| | }; |
| | Ok(result) |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl ClientReferencesGraph { |
| | #[turbo_tasks::function] |
| | pub async fn new_with_entries( |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| | ) -> Result<Vc<Self>> { |
| | let mapped = map_client_references(*graph.graph); |
| |
|
| | Ok(Self { |
| | is_single_page, |
| | graph, |
| | data: mapped.to_resolved().await?, |
| | } |
| | .cell()) |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | async fn get_client_references_for_endpoint( |
| | &self, |
| | entry: ResolvedVc<Box<dyn Module>>, |
| | ) -> Result<Vc<ClientReferenceGraphResult>> { |
| | let span = tracing::info_span!("collect client references for endpoint"); |
| | async move { |
| | let data = &*self.data.await?; |
| | let graph = self.graph.read().await?; |
| |
|
| | let entries = if !self.is_single_page { |
| | if !graph.graphs.first().unwrap().has_entry_module(entry) { |
| | |
| | return Ok(ClientReferenceGraphResult::default().cell()); |
| | } |
| | Either::Left(std::iter::once(entry)) |
| | } else { |
| | Either::Right(graph.graphs.first().unwrap().entry_modules()) |
| | }; |
| |
|
| | |
| | |
| | let mut client_references = Vec::new(); |
| | let mut server_utils = FxIndexSet::default(); |
| |
|
| | let mut server_components = FxIndexSet::default(); |
| |
|
| | |
| | graph.traverse_nodes_dfs( |
| | entries, |
| | &mut (), |
| | |node, _| { |
| | let module_type = data.get(&node); |
| | Ok(match module_type { |
| | Some( |
| | ClientManifestEntryType::EcmascriptClientReference { .. } |
| | | ClientManifestEntryType::CssClientReference { .. } |
| | | ClientManifestEntryType::ServerComponent { .. }, |
| | ) => GraphTraversalAction::Skip, |
| | None => GraphTraversalAction::Continue, |
| | }) |
| | }, |
| | |node, _| { |
| | if let Some(server_util_module) = |
| | ResolvedVc::try_downcast_type::<NextServerUtilityModule>(node) |
| | { |
| | |
| | server_utils.insert(server_util_module); |
| | return Ok(()); |
| | } |
| |
|
| | let module_type = data.get(&node); |
| |
|
| | let ty = match module_type { |
| | Some(ClientManifestEntryType::EcmascriptClientReference { |
| | module, |
| | ssr_module: _, |
| | }) => ClientReferenceType::EcmascriptClientReference(*module), |
| | Some(ClientManifestEntryType::CssClientReference(module)) => { |
| | ClientReferenceType::CssClientReference(*module) |
| | } |
| | Some(ClientManifestEntryType::ServerComponent(sc)) => { |
| | server_components.insert(*sc); |
| | return Ok(()); |
| | } |
| | None => { |
| | return Ok(()); |
| | } |
| | }; |
| |
|
| | |
| | client_references.push(ClientReference { |
| | server_component: None, |
| | ty, |
| | }); |
| |
|
| | Ok(()) |
| | }, |
| | )?; |
| |
|
| | |
| | |
| | |
| | for sc in server_components.iter().copied() { |
| | graph.traverse_nodes_dfs( |
| | std::iter::once(ResolvedVc::upcast(sc)), |
| | &mut (), |
| | |node, _| { |
| | let module = node; |
| | let module_type = data.get(&module); |
| |
|
| | Ok(match module_type { |
| | Some( |
| | ClientManifestEntryType::EcmascriptClientReference { .. } |
| | | ClientManifestEntryType::CssClientReference { .. }, |
| | ) => GraphTraversalAction::Skip, |
| | _ => GraphTraversalAction::Continue, |
| | }) |
| | }, |
| | |node, _| { |
| | let module = node; |
| | if let Some(server_util_module) = |
| | ResolvedVc::try_downcast_type::<NextServerUtilityModule>(module) |
| | { |
| | server_utils.insert(server_util_module); |
| | } |
| |
|
| | let Some(module_type) = data.get(&module) else { |
| | return Ok(()); |
| | }; |
| |
|
| | let ty = match module_type { |
| | ClientManifestEntryType::EcmascriptClientReference { |
| | module, |
| | ssr_module: _, |
| | } => ClientReferenceType::EcmascriptClientReference(*module), |
| | ClientManifestEntryType::CssClientReference(module) => { |
| | ClientReferenceType::CssClientReference(*module) |
| | } |
| | ClientManifestEntryType::ServerComponent(_) => { |
| | return Ok(()); |
| | } |
| | }; |
| |
|
| | client_references.push(ClientReference { |
| | server_component: Some(sc), |
| | ty, |
| | }); |
| |
|
| | Ok(()) |
| | }, |
| | )?; |
| | } |
| |
|
| | Ok(ClientReferenceGraphResult { |
| | client_references: client_references.into_iter().collect(), |
| | |
| | server_utils: server_utils.into_iter().collect(), |
| | server_component_entries: server_components.into_iter().collect(), |
| | } |
| | .cell()) |
| | } |
| | .instrument(span) |
| | .await |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value(shared)] |
| | struct CssGlobalImportIssue { |
| | pub parent_module: ResolvedVc<Box<dyn Module>>, |
| | pub module: ResolvedVc<Box<dyn Module>>, |
| | } |
| |
|
| | impl CssGlobalImportIssue { |
| | fn new( |
| | parent_module: ResolvedVc<Box<dyn Module>>, |
| | module: ResolvedVc<Box<dyn Module>>, |
| | ) -> Self { |
| | Self { |
| | parent_module, |
| | module, |
| | } |
| | } |
| | } |
| |
|
| | #[turbo_tasks::value_impl] |
| | impl Issue for CssGlobalImportIssue { |
| | #[turbo_tasks::function] |
| | async fn title(&self) -> Vc<StyledString> { |
| | StyledString::Stack(vec![ |
| | StyledString::Text(rcstr!("Failed to compile")), |
| | StyledString::Text(rcstr!( |
| | "Global CSS cannot be imported from files other than your Custom <App>. Due to \ |
| | the Global nature of stylesheets, and to avoid conflicts, Please move all \ |
| | first-party global CSS imports to pages/_app.js. Or convert the import to \ |
| | Component-Level CSS (CSS Modules)." |
| | )), |
| | StyledString::Text(rcstr!( |
| | "Read more: https://nextjs.org/docs/messages/css-global" |
| | )), |
| | ]) |
| | .cell() |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | async fn description(&self) -> Result<Vc<OptionStyledString>> { |
| | let parent_path = self.parent_module.ident().path().owned().await?; |
| | let module_path = self.module.ident().path().owned().await?; |
| | let relative_import_location = parent_path.parent(); |
| |
|
| | let import_path = match relative_import_location.get_relative_path_to(&module_path) { |
| | Some(path) => path, |
| | None => module_path.path.clone(), |
| | }; |
| | let cleaned_import_path = |
| | if import_path.ends_with(".scss.css") || import_path.ends_with(".sass.css") { |
| | RcStr::from(import_path.trim_end_matches(".css")) |
| | } else { |
| | import_path |
| | }; |
| |
|
| | Ok(Vc::cell(Some( |
| | StyledString::Stack(vec![ |
| | StyledString::Text(format!("Location: {}", parent_path.path).into()), |
| | StyledString::Text(format!("Import path: {cleaned_import_path}",).into()), |
| | ]) |
| | .resolved_cell(), |
| | ))) |
| | } |
| |
|
| | fn severity(&self) -> IssueSeverity { |
| | IssueSeverity::Error |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | fn file_path(&self) -> Vc<FileSystemPath> { |
| | self.parent_module.ident().path() |
| | } |
| |
|
| | #[turbo_tasks::function] |
| | fn stage(&self) -> Vc<IssueStage> { |
| | IssueStage::ProcessModule.cell() |
| | } |
| |
|
| | |
| | } |
| |
|
| | type FxModuleNameMap = FxIndexMap<ResolvedVc<Box<dyn Module>>, RcStr>; |
| |
|
| | #[turbo_tasks::value(transparent)] |
| | struct ModuleNameMap(#[bincode(with = "turbo_bincode::indexmap")] pub FxModuleNameMap); |
| |
|
| | #[tracing::instrument(level = "info", name = "validate pages css imports", skip_all)] |
| | #[turbo_tasks::function] |
| | async fn validate_pages_css_imports_individual( |
| | graph: SingleModuleGraphWithBindingUsage, |
| | is_single_page: bool, |
| | entry: Vc<Box<dyn Module>>, |
| | app_module: ResolvedVc<Box<dyn Module>>, |
| | ) -> Result<()> { |
| | let graph = graph.read().await?; |
| | let entry = entry.to_resolved().await?; |
| |
|
| | let entries = if !is_single_page { |
| | if !graph.graphs.first().unwrap().has_entry_module(entry) { |
| | |
| | return Ok(()); |
| | } |
| | Either::Left(std::iter::once(entry)) |
| | } else { |
| | Either::Right(graph.graphs.first().unwrap().entry_modules()) |
| | }; |
| |
|
| | let mut candidates = vec![]; |
| |
|
| | graph.traverse_edges_dfs( |
| | entries, |
| | &mut (), |
| | |parent_info, node, _| { |
| | let module = node; |
| |
|
| | |
| | |
| | let Some((parent_node, _)) = parent_info else { |
| | return Ok(GraphTraversalAction::Continue); |
| | }; |
| | let parent_module = parent_node; |
| |
|
| | |
| | if parent_module == app_module { |
| | return Ok(GraphTraversalAction::Continue); |
| | } |
| |
|
| | |
| | |
| | let module_is_global_css = |
| | ResolvedVc::try_downcast_type::<CssModuleAsset>(module).is_some(); |
| |
|
| | if !module_is_global_css { |
| | return Ok(GraphTraversalAction::Continue); |
| | } |
| |
|
| | let parent_is_css_module = |
| | ResolvedVc::try_downcast_type::<ModuleCssAsset>(parent_module).is_some() |
| | || ResolvedVc::try_downcast_type::<CssModuleAsset>(parent_module).is_some(); |
| |
|
| | |
| | |
| | if parent_is_css_module { |
| | return Ok(GraphTraversalAction::Continue); |
| | } |
| |
|
| | |
| | |
| | |
| | if parent_module != app_module { |
| | candidates.push(CssGlobalImportIssue::new(parent_module, module)) |
| | } |
| |
|
| | Ok(GraphTraversalAction::Continue) |
| | }, |
| | |_, _, _| Ok(()), |
| | )?; |
| |
|
| | candidates |
| | .into_iter() |
| | .map(async |issue| { |
| | |
| | Ok( |
| | if !issue.module.ident().path().await?.is_in_node_modules() { |
| | Some(issue) |
| | } else { |
| | None |
| | }, |
| | ) |
| | }) |
| | .try_flat_join() |
| | .await? |
| | .into_iter() |
| | .for_each(|issue| { |
| | issue.resolved_cell().emit(); |
| | }); |
| |
|
| | Ok(()) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | #[turbo_tasks::function] |
| | pub async fn validate_pages_css_imports( |
| | graph: Vc<ModuleGraph>, |
| | is_single_page: bool, |
| | entry: Vc<Box<dyn Module>>, |
| | app_module: Vc<Box<dyn Module>>, |
| | ) -> Result<()> { |
| | let graphs = &graph.await?; |
| | graphs |
| | .iter_graphs() |
| | .map(|graph| { |
| | validate_pages_css_imports_individual(graph, is_single_page, entry, app_module) |
| | .as_side_effect() |
| | }) |
| | .try_join() |
| | .await?; |
| |
|
| | Ok(()) |
| | } |
| |
|