diff --git a/Cargo.lock b/Cargo.lock index 48da205fb4ebc..18256de23872c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7755,6 +7755,7 @@ dependencies = [ "anyhow", "async-trait", "indexmap", + "indoc", "once_cell", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 33df72cdf08b7..cb524233d8755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ opt-level = 3 # ref: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace [workspace.dependencies] indexmap = { version = "1.9.2" } +indoc = "1.0" # Keep consistent with preset_env_base through swc_core browserslist-rs = { version = "0.12.2" } swc_core = { version = "0.59.26" } diff --git a/crates/next-core/Cargo.toml b/crates/next-core/Cargo.toml index 7688666af2def..9cc7e894740cd 100644 --- a/crates/next-core/Cargo.toml +++ b/crates/next-core/Cargo.toml @@ -12,7 +12,7 @@ bench = false anyhow = "1.0.47" auto-hash-map = { path = "../auto-hash-map" } indexmap = { workspace = true, features = ["serde"] } -indoc = "1.0" +indoc = { workspace = true } mime = "0.3.16" once_cell = "1.13.0" qstring = "0.7.2" diff --git a/crates/next-core/src/manifest.rs b/crates/next-core/src/manifest.rs index 21a438b671306..795f96de125e5 100644 --- a/crates/next-core/src/manifest.rs +++ b/crates/next-core/src/manifest.rs @@ -3,8 +3,9 @@ use indexmap::IndexMap; use mime::{APPLICATION_JAVASCRIPT_UTF_8, APPLICATION_JSON}; use serde::Serialize; use turbo_tasks::{ + graph::{GraphTraversal, NonDeterministic}, primitives::{StringVc, StringsVc}, - TryFlatMapRecursiveJoinIterExt, TryJoinIterExt, + TryJoinIterExt, }; use turbo_tasks_fs::File; use turbopack_core::asset::AssetContentVc; @@ -60,19 +61,18 @@ impl DevManifestContentSourceVc { Ok(content_source.get_children().await?.clone_value()) } - let mut routes = this - .page_roots - .iter() - .copied() - .try_flat_map_recursive_join(get_content_source_children) - .await? - .into_iter() - .map(content_source_to_pathname) - .try_join() - .await? - .into_iter() - .flatten() - .collect::>(); + let mut routes = GraphTraversal::>::visit( + this.page_roots.iter().copied(), + get_content_source_children, + ) + .await? + .into_iter() + .map(content_source_to_pathname) + .try_join() + .await? + .into_iter() + .flatten() + .collect::>(); routes.sort_by_cached_key(|s| s.split('/').map(PageSortKey::from).collect::>()); diff --git a/crates/turbo-tasks/src/graph/get_children.rs b/crates/turbo-tasks/src/graph/get_children.rs new file mode 100644 index 0000000000000..196e9656f0a88 --- /dev/null +++ b/crates/turbo-tasks/src/graph/get_children.rs @@ -0,0 +1,121 @@ +use std::{collections::HashSet, future::Future}; + +use anyhow::Result; + +/// A trait that allows a graph traversal to get the children of a node. +pub trait GetChildren { + type Children: IntoIterator; + type Future: Future>; + + fn get_children(&mut self, item: &T) -> Option; +} + +// The different `Impl*` here are necessary in order to avoid the `Conflicting +// implementations of trait` error when implementing `GetChildren` on different +// kinds of `FnMut`. +// See https://users.rust-lang.org/t/conflicting-implementation-when-implementing-traits-for-fn/53359/3 + +pub struct ImplRef; + +impl GetChildren for C +where + C: FnMut(&T) -> F, + F: Future>, + CI: IntoIterator, +{ + type Children = CI; + type Future = F; + + fn get_children(&mut self, item: &T) -> Option { + Some((self)(item)) + } +} + +pub struct ImplRefOption; + +impl GetChildren for C +where + C: FnMut(&T) -> Option, + F: Future>, + CI: IntoIterator, +{ + type Children = CI; + type Future = F; + + fn get_children(&mut self, item: &T) -> Option { + (self)(item) + } +} + +pub struct ImplValue; + +impl GetChildren for C +where + T: Copy, + C: FnMut(T) -> F, + F: Future>, + CI: IntoIterator, +{ + type Children = CI; + type Future = F; + + fn get_children(&mut self, item: &T) -> Option { + Some((self)(*item)) + } +} + +pub struct ImplValueOption; + +impl GetChildren for C +where + T: Copy, + C: FnMut(T) -> Option, + F: Future>, + CI: IntoIterator, +{ + type Children = CI; + type Future = F; + + fn get_children(&mut self, item: &T) -> Option { + (self)(*item) + } +} + +/// A [`GetChildren`] implementation that skips nodes that have already been +/// visited. This is necessary to avoid repeated work when traversing non-tree +/// graphs (i.e. where a child can have more than one parent). +#[derive(Debug)] +pub struct SkipDuplicates { + get_children: C, + visited: HashSet, + _phantom: std::marker::PhantomData, +} + +impl SkipDuplicates { + /// Create a new [`SkipDuplicates`] that wraps the given [`GetChildren`]. + pub fn new(get_children: C) -> Self { + Self { + get_children, + visited: HashSet::new(), + _phantom: std::marker::PhantomData, + } + } +} + +impl GetChildren for SkipDuplicates +where + T: Eq + std::hash::Hash + Clone, + C: GetChildren, +{ + type Children = C::Children; + type Future = C::Future; + + fn get_children(&mut self, item: &T) -> Option { + if !self.visited.contains(item) { + self.visited.insert(item.clone()); + self.get_children.get_children(item) + } else { + None + } + } +} diff --git a/crates/turbo-tasks/src/graph/graph_store.rs b/crates/turbo-tasks/src/graph/graph_store.rs new file mode 100644 index 0000000000000..7fda8a5b3f6d6 --- /dev/null +++ b/crates/turbo-tasks/src/graph/graph_store.rs @@ -0,0 +1,10 @@ +/// A graph store is a data structure that will be built up during a graph +/// traversal. It is used to store the results of the traversal. +pub trait GraphStore: Default { + type Handle: Clone; + + // TODO(alexkirsz) An `entry(parent_handle) -> Entry` API would be more + // efficient, as right now we're getting the same key multiple times. + /// Inserts a node into the graph store, and returns a handle to it. + fn insert(&mut self, parent_handle: Option, node: T) -> (Self::Handle, &T); +} diff --git a/crates/turbo-tasks/src/graph/graph_traversal.rs b/crates/turbo-tasks/src/graph/graph_traversal.rs new file mode 100644 index 0000000000000..ff7dac484b044 --- /dev/null +++ b/crates/turbo-tasks/src/graph/graph_traversal.rs @@ -0,0 +1,131 @@ +use std::{future::Future, pin::Pin, task::ready}; + +use anyhow::Result; +use futures::{stream::FuturesUnordered, Stream}; +use pin_project_lite::pin_project; + +use super::{graph_store::GraphStore, GetChildren}; + +/// [`GraphTraversal`] is a utility type that can be used to traverse a graph of +/// nodes, where each node can have a variable number of children. The traversal +/// is done in parallel, and the order of the nodes in the traversal result is +/// determined by the [`GraphStore`] parameter. +pub struct GraphTraversal { + _store: std::marker::PhantomData, +} + +impl GraphTraversal { + /// Visits the graph starting from the given `roots`, and returns a future + /// that will resolve to the traversal result. + pub fn visit(roots: I, mut get_children: C) -> GraphTraversalFuture + where + S: GraphStore, + I: IntoIterator, + C: GetChildren, + { + let mut store = S::default(); + let futures = FuturesUnordered::new(); + for item in roots { + let (parent_handle, item) = store.insert(None, item); + if let Some(future) = get_children.get_children(item) { + futures.push(WithHandle::new(future, parent_handle)); + } + } + GraphTraversalFuture { + store, + futures, + get_children, + } + } +} + +/// A future that resolves to a [`GraphStore`] containing the result of a graph +/// traversal. +pub struct GraphTraversalFuture +where + S: GraphStore, + C: GetChildren, +{ + store: S, + futures: FuturesUnordered>, + get_children: C, +} + +impl Future for GraphTraversalFuture +where + S: GraphStore, + C: GetChildren, +{ + type Output = Result; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let this = unsafe { self.get_unchecked_mut() }; + loop { + let futures = unsafe { Pin::new_unchecked(&mut this.futures) }; + if let Some((parent_handle, result)) = ready!(futures.poll_next(cx)) { + match result { + Ok(children) => { + for item in children { + let (child_handle, item) = + this.store.insert(Some(parent_handle.clone()), item); + + if let Some(future) = this.get_children.get_children(item) { + this.futures.push(WithHandle::new(future, child_handle)); + } + } + } + Err(err) => return std::task::Poll::Ready(Err(err)), + } + } else { + return std::task::Poll::Ready(Ok(std::mem::take(&mut this.store))); + } + } + } +} + +pin_project! { + struct WithHandle + where + T: Future, + { + #[pin] + future: T, + handle: Option

, + } +} + +impl WithHandle +where + T: Future, +{ + pub fn new(future: T, handle: H) -> Self { + Self { + future, + handle: Some(handle), + } + } +} + +impl Future for WithHandle +where + T: Future, +{ + type Output = (H, T::Output); + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let this = self.project(); + match this.future.poll(cx) { + std::task::Poll::Ready(result) => std::task::Poll::Ready(( + this.handle.take().expect("polled after completion"), + result, + )), + std::task::Poll::Pending => std::task::Poll::Pending, + } + } +} diff --git a/crates/turbo-tasks/src/graph/mod.rs b/crates/turbo-tasks/src/graph/mod.rs new file mode 100644 index 0000000000000..c5505c9366e95 --- /dev/null +++ b/crates/turbo-tasks/src/graph/mod.rs @@ -0,0 +1,10 @@ +mod get_children; +mod graph_store; +mod graph_traversal; +mod non_deterministic; +mod reverse_topological; + +pub use get_children::{GetChildren, SkipDuplicates}; +pub use graph_traversal::GraphTraversal; +pub use non_deterministic::NonDeterministic; +pub use reverse_topological::ReverseTopological; diff --git a/crates/turbo-tasks/src/graph/non_deterministic.rs b/crates/turbo-tasks/src/graph/non_deterministic.rs new file mode 100644 index 0000000000000..c90f59bd991a1 --- /dev/null +++ b/crates/turbo-tasks/src/graph/non_deterministic.rs @@ -0,0 +1,31 @@ +use super::graph_store::GraphStore; + +/// A graph traversal that does not guarantee any particular order, and may not +/// return the same order every time it is run. +pub struct NonDeterministic { + output: Vec, +} + +impl Default for NonDeterministic { + fn default() -> Self { + Self { output: Vec::new() } + } +} + +impl GraphStore for NonDeterministic { + type Handle = (); + + fn insert(&mut self, _parent_handle: Option, node: T) -> (Self::Handle, &T) { + self.output.push(node); + ((), self.output.last().unwrap()) + } +} + +impl IntoIterator for NonDeterministic { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.output.into_iter() + } +} diff --git a/crates/turbo-tasks/src/graph/reverse_topological.rs b/crates/turbo-tasks/src/graph/reverse_topological.rs new file mode 100644 index 0000000000000..c6e26c9e03a6b --- /dev/null +++ b/crates/turbo-tasks/src/graph/reverse_topological.rs @@ -0,0 +1,118 @@ +use std::collections::{HashMap, HashSet}; + +use super::graph_store::GraphStore; + +/// A graph traversal that returns nodes in reverse topological order. +pub struct ReverseTopological +where + T: Eq + std::hash::Hash + Clone, +{ + adjacency_map: HashMap>, + roots: Vec, +} + +impl Default for ReverseTopological +where + T: Eq + std::hash::Hash + Clone, +{ + fn default() -> Self { + Self { + adjacency_map: HashMap::new(), + roots: Vec::new(), + } + } +} + +impl GraphStore for ReverseTopological +where + T: Eq + std::hash::Hash + Clone, +{ + type Handle = T; + + fn insert(&mut self, parent: Option, node: T) -> (Self::Handle, &T) { + let vec = if let Some(parent) = parent { + self.adjacency_map + .entry(parent) + .or_insert_with(|| Vec::with_capacity(1)) + } else { + &mut self.roots + }; + + vec.push(node.clone()); + (node, vec.last().unwrap()) + } +} + +#[derive(Debug)] +enum ReverseTopologicalPass { + Pre, + Post, +} + +impl IntoIterator for ReverseTopological +where + T: Eq + std::hash::Hash + Clone, +{ + type Item = T; + type IntoIter = ReverseTopologicalIntoIter; + + fn into_iter(self) -> Self::IntoIter { + ReverseTopologicalIntoIter { + adjacency_map: self.adjacency_map, + stack: self + .roots + .into_iter() + .map(|root| (ReverseTopologicalPass::Pre, root)) + .collect(), + visited: HashSet::new(), + } + } +} + +pub struct ReverseTopologicalIntoIter +where + T: Eq + std::hash::Hash + Clone, +{ + adjacency_map: HashMap>, + stack: Vec<(ReverseTopologicalPass, T)>, + visited: HashSet, +} + +impl Iterator for ReverseTopologicalIntoIter +where + T: Eq + std::hash::Hash + Clone, +{ + type Item = T; + + fn next(&mut self) -> Option { + let current = loop { + let (pass, current) = self.stack.pop()?; + + match pass { + ReverseTopologicalPass::Post => { + break current; + } + ReverseTopologicalPass::Pre => { + if self.visited.contains(¤t) { + continue; + } + + self.visited.insert(current.clone()); + + let Some(children) = self.adjacency_map.get(¤t) else { + break current; + }; + + self.stack.push((ReverseTopologicalPass::Post, current)); + self.stack.extend( + children + .iter() + .map(|child| (ReverseTopologicalPass::Pre, child.clone())), + ); + } + } + }; + + Some(current) + } +} diff --git a/crates/turbo-tasks/src/join_iter_ext.rs b/crates/turbo-tasks/src/join_iter_ext.rs index 31c3fc7fd1983..263eb5f54705f 100644 --- a/crates/turbo-tasks/src/join_iter_ext.rs +++ b/crates/turbo-tasks/src/join_iter_ext.rs @@ -1,18 +1,10 @@ -use std::{ - future::{Future, IntoFuture}, - hash::Hash, - mem::take, - pin::Pin, - task::ready, -}; +use std::future::{Future, IntoFuture}; use anyhow::Result; use futures::{ future::{join_all, JoinAll}, - stream::FuturesOrdered, - FutureExt, Stream, + FutureExt, }; -use indexmap::IndexSet; /// Future for the [JoinIterExt::join] method. pub struct Join @@ -37,6 +29,16 @@ where } } +pub trait JoinIterExt: Iterator +where + T: Unpin, + F: Future, +{ + /// Returns a future that resolves to a vector of the outputs of the futures + /// in the iterator. + fn join(self) -> Join; +} + /// Future for the [TryJoinIterExt::try_join] method. pub struct TryJoin where @@ -65,63 +67,6 @@ where } } -pub struct TryFlatMapRecursiveJoin -where - T: Hash + PartialEq + Eq + Clone, - C: Fn(T) -> F, - F: Future>, - CI: IntoIterator, -{ - set: IndexSet, - futures: FuturesOrdered, - get_children: C, -} - -impl Future for TryFlatMapRecursiveJoin -where - T: Hash + PartialEq + Eq + Clone, - C: Fn(T) -> F, - F: Future>, - CI: IntoIterator, -{ - type Output = Result>; - fn poll( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - let this = unsafe { self.get_unchecked_mut() }; - loop { - let futures = unsafe { Pin::new_unchecked(&mut this.futures) }; - if let Some(result) = ready!(futures.poll_next(cx)) { - match result { - Ok(children) => { - for item in children { - let (index, new) = this.set.insert_full(item); - if new { - this.futures - .push_back((this.get_children)(this.set[index].clone())); - } - } - } - Err(err) => return std::task::Poll::Ready(Err(err)), - } - } else { - return std::task::Poll::Ready(Ok(take(&mut this.set))); - } - } - } -} - -pub trait JoinIterExt: Iterator -where - T: Unpin, - F: Future, -{ - /// Returns a future that resolves to a vector of the outputs of the futures - /// in the iterator. - fn join(self) -> Join; -} - pub trait TryJoinIterExt: Iterator where T: Unpin, @@ -135,24 +80,6 @@ where fn try_join(self) -> TryJoin; } -pub trait TryFlatMapRecursiveJoinIterExt: Iterator -where - T: Hash + PartialEq + Eq + Clone, - C: Fn(T) -> F, - F: Future>, - CI: IntoIterator, -{ - /// Applies the `get_children` function on each item in the iterator, and on - /// each item that is returned by `get_children`. Collects all items from - /// the iterator and all items returns by `get_children` into an index set. - /// The order of items is equal to a breadth-first traversal of the tree, - /// but `get_children` will execute concurrently. It will handle circular - /// references gracefully. Returns a future that resolve to a - /// [Result]. It will resolve to the first error that occur in - /// breadth-first order. - fn try_flat_map_recursive_join(self, get_children: C) -> TryFlatMapRecursiveJoin; -} - impl JoinIterExt for It where T: Unpin, @@ -180,28 +107,3 @@ where } } } - -impl TryFlatMapRecursiveJoinIterExt for It -where - T: Hash + PartialEq + Eq + Clone, - C: Fn(T) -> F, - F: Future>, - CI: IntoIterator, - It: Iterator, -{ - fn try_flat_map_recursive_join(self, get_children: C) -> TryFlatMapRecursiveJoin { - let mut set = IndexSet::new(); - let mut futures = FuturesOrdered::new(); - for item in self { - let (index, new) = set.insert_full(item); - if new { - futures.push_back(get_children(set[index].clone())); - } - } - TryFlatMapRecursiveJoin { - set, - futures, - get_children, - } - } -} diff --git a/crates/turbo-tasks/src/lib.rs b/crates/turbo-tasks/src/lib.rs index 59d2ccd2da647..571cbd140efa1 100644 --- a/crates/turbo-tasks/src/lib.rs +++ b/crates/turbo-tasks/src/lib.rs @@ -39,6 +39,7 @@ mod completion; pub mod debug; mod display; pub mod event; +pub mod graph; mod id; mod id_factory; mod join_iter_ext; @@ -70,7 +71,7 @@ pub use id::{ with_task_id_mapping, without_task_id_mapping, FunctionId, IdMapping, TaskId, TraitTypeId, ValueTypeId, }; -pub use join_iter_ext::{JoinIterExt, TryFlatMapRecursiveJoinIterExt, TryJoinIterExt}; +pub use join_iter_ext::{JoinIterExt, TryJoinIterExt}; pub use manager::{ dynamic_call, emit, get_invalidator, mark_stateful, run_once, spawn_blocking, spawn_thread, trait_call, turbo_tasks, Invalidator, StatsType, TaskIdProvider, TurboTasks, TurboTasksApi, diff --git a/crates/turbopack-core/src/chunk/mod.rs b/crates/turbopack-core/src/chunk/mod.rs index 3624cdae9f2f6..ca05e9f49af4c 100644 --- a/crates/turbopack-core/src/chunk/mod.rs +++ b/crates/turbopack-core/src/chunk/mod.rs @@ -12,9 +12,10 @@ use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use turbo_tasks::{ debug::ValueDebugFormat, + graph::{GraphTraversal, ReverseTopological, SkipDuplicates}, primitives::{BoolVc, StringVc}, trace::TraceRawVcs, - TryFlatMapRecursiveJoinIterExt, TryJoinIterExt, ValueToString, ValueToStringVc, + TryJoinIterExt, ValueToString, ValueToStringVc, }; use turbo_tasks_fs::FileSystemPathVc; use turbo_tasks_hash::DeterministicHash; @@ -125,52 +126,15 @@ impl ChunkGroupVc { /// All chunks should be loaded in parallel. #[turbo_tasks::function] pub async fn chunks(self) -> Result { - async fn reference_to_chunks( - r: AssetReferenceVc, - ) -> Result + Send> { - let mut result = Vec::new(); - if let Some(pc) = ParallelChunkReferenceVc::resolve_from(r).await? { - if *pc.is_loaded_in_parallel().await? { - result = r - .resolve_reference() - .await? - .primary - .iter() - .map(|r| async move { - Ok(if let PrimaryResolveResult::Asset(a) = r { - ChunkVc::resolve_from(a).await? - } else { - None - }) - }) - .try_join() - .await?; - } - } - Ok(result.into_iter().flatten()) - } - - async fn get_chunk_children( - chunk: ChunkVc, - ) -> Result + Send> { - Ok(chunk - .references() - .await? - .iter() - .copied() - .map(reference_to_chunks) - .try_join() - .await? - .into_iter() - .flatten()) - } - - let chunks = [self.await?.entry] - .into_iter() - .try_flat_map_recursive_join(get_chunk_children) - .await?; - - let chunks = ChunksVc::cell(chunks.into_iter().collect()); + let chunks: Vec<_> = GraphTraversal::>::visit( + [self.await?.entry], + SkipDuplicates::new(get_chunk_children), + ) + .await? + .into_iter() + .collect(); + + let chunks = ChunksVc::cell(chunks); let chunks = optimize(chunks, self); let chunks = ChunksVc::cell( chunks @@ -184,6 +148,44 @@ impl ChunkGroupVc { } } +/// Computes the list of all chunk children of a given chunk. +async fn get_chunk_children(parent: ChunkVc) -> Result + Send> { + Ok(parent + .references() + .await? + .iter() + .copied() + .map(reference_to_chunks) + .try_join() + .await? + .into_iter() + .flatten()) +} + +/// Get all parallel chunks from a parallel chunk reference. +async fn reference_to_chunks(r: AssetReferenceVc) -> Result + Send> { + let mut result = Vec::new(); + if let Some(pc) = ParallelChunkReferenceVc::resolve_from(r).await? { + if *pc.is_loaded_in_parallel().await? { + result = r + .resolve_reference() + .await? + .primary + .iter() + .map(|r| async move { + Ok(if let PrimaryResolveResult::Asset(a) = r { + ChunkVc::resolve_from(a).await? + } else { + None + }) + }) + .try_join() + .await?; + } + } + Ok(result.into_iter().flatten()) +} + #[turbo_tasks::value_impl] impl ValueToString for ChunkGroup { #[turbo_tasks::function] diff --git a/crates/turbopack-core/src/reference_type.rs b/crates/turbopack-core/src/reference_type.rs index 26cd954cce458..4b23764f53014 100644 --- a/crates/turbopack-core/src/reference_type.rs +++ b/crates/turbopack-core/src/reference_type.rs @@ -24,6 +24,7 @@ pub enum EcmaScriptModulesReferenceSubType { #[derive(Debug, Clone, PartialOrd, Ord, Hash)] pub enum CssReferenceSubType { AtImport, + Compose, Custom(u8), Undefined, } diff --git a/crates/turbopack-create-test-app/Cargo.toml b/crates/turbopack-create-test-app/Cargo.toml index 8ce8dee06d896..09588cffb0605 100644 --- a/crates/turbopack-create-test-app/Cargo.toml +++ b/crates/turbopack-create-test-app/Cargo.toml @@ -19,7 +19,7 @@ bench = false [dependencies] anyhow = "1.0.47" clap = { version = "4.0.18", features = ["derive"] } -indoc = "1.0" +indoc = { workspace = true } pathdiff = "0.2.1" serde_json = "1.0.85" tempfile = "3.3.0" diff --git a/crates/turbopack-css/Cargo.toml b/crates/turbopack-css/Cargo.toml index 88d25b554fe5b..538738a7c0afb 100644 --- a/crates/turbopack-css/Cargo.toml +++ b/crates/turbopack-css/Cargo.toml @@ -13,6 +13,7 @@ bench = false anyhow = "1.0.47" async-trait = "0.1.56" indexmap = { workspace = true } +indoc = { workspace = true } once_cell = "1.13.0" regex = "1.6.0" serde = "1.0.136" diff --git a/crates/turbopack-css/src/asset.rs b/crates/turbopack-css/src/asset.rs index 5fd32f10ba9d3..e8194d482d754 100644 --- a/crates/turbopack-css/src/asset.rs +++ b/crates/turbopack-css/src/asset.rs @@ -28,7 +28,10 @@ use crate::{ code_gen::{CodeGenerateable, CodeGenerateableVc}, parse::{parse, ParseResult, ParseResultSourceMap, ParseResultVc}, path_visitor::ApplyVisitors, - references::{analyze_css_stylesheet, import::ImportAssetReferenceVc}, + references::{ + analyze_css_stylesheet, compose::CssModuleComposeReferenceVc, + import::ImportAssetReferenceVc, + }, transform::CssInputTransformsVc, CssModuleAssetType, }; @@ -170,17 +173,27 @@ impl CssChunkItem for ModuleChunkItem { let context = self.context; for reference in references.iter() { - if let Some(import) = ImportAssetReferenceVc::resolve_from(reference).await? { - for result in import.resolve_reference().await?.primary.iter() { + if let Some(import_ref) = ImportAssetReferenceVc::resolve_from(reference).await? { + for result in import_ref.resolve_reference().await?.primary.iter() { if let PrimaryResolveResult::Asset(asset) = result { if let Some(placeable) = CssChunkPlaceableVc::resolve_from(asset).await? { imports.push(CssImport::Internal( - import, + import_ref, placeable.as_chunk_item(context), )); } } } + } else if let Some(compose_ref) = + CssModuleComposeReferenceVc::resolve_from(reference).await? + { + for result in compose_ref.resolve_reference().await?.primary.iter() { + if let PrimaryResolveResult::Asset(asset) = result { + if let Some(placeable) = CssChunkPlaceableVc::resolve_from(asset).await? { + imports.push(CssImport::Composes(placeable.as_chunk_item(context))); + } + } + } } } diff --git a/crates/turbopack-css/src/chunk/mod.rs b/crates/turbopack-css/src/chunk/mod.rs index e77531f7335f1..e866c06e6b39f 100644 --- a/crates/turbopack-css/src/chunk/mod.rs +++ b/crates/turbopack-css/src/chunk/mod.rs @@ -388,6 +388,7 @@ pub struct CssChunkPlaceables(Vec); pub enum CssImport { External(StringVc), Internal(ImportAssetReferenceVc, CssChunkItemVc), + Composes(CssChunkItemVc), } #[turbo_tasks::value(shared)] diff --git a/crates/turbopack-css/src/chunk/optimize.rs b/crates/turbopack-css/src/chunk/optimize.rs index c6786cc76b83f..fac6a583100c3 100644 --- a/crates/turbopack-css/src/chunk/optimize.rs +++ b/crates/turbopack-css/src/chunk/optimize.rs @@ -1,11 +1,8 @@ -use std::mem::take; - use anyhow::{bail, Result}; use indexmap::IndexSet; use turbo_tasks::TryJoinIterExt; -use turbo_tasks_fs::FileSystemPathOptionVc; use turbopack_core::chunk::{ - optimize::{optimize_by_common_parent, ChunkOptimizer, ChunkOptimizerVc}, + optimize::{ChunkOptimizer, ChunkOptimizerVc}, ChunkGroupVc, ChunkVc, ChunkingContextVc, ChunksVc, }; @@ -26,7 +23,19 @@ impl CssChunkOptimizerVc { impl ChunkOptimizer for CssChunkOptimizer { #[turbo_tasks::function] async fn optimize(&self, chunks: ChunksVc, _chunk_group: ChunkGroupVc) -> Result { - optimize_by_common_parent(chunks, get_common_parent, optimize_css).await + // The CSS optimizer works under the constraint that the order in which + // CSS chunks are loaded must be preserved, as CSS rules + // precedence is determined by the order in which they are + // loaded. This means that we may not merge chunks that are not + // adjacent to each other in a valid reverse topological order. + + // TODO(alexkirsz) It might be more interesting to only merge adjacent + // chunks when they are part of the same chunk subgraph. + // However, the optimizer currently does not have access to this + // information, as chunks are already fully flattened by the + // time they reach the optimizer. + + merge_adjacent_chunks(chunks).await } } @@ -38,13 +47,11 @@ async fn css(chunk: ChunkVc) -> Result { } } -#[turbo_tasks::function] -async fn get_common_parent(chunk: ChunkVc) -> Result { - Ok(css(chunk).await?.common_parent()) -} - -async fn merge_chunks(first: CssChunkVc, chunks: &[CssChunkVc]) -> Result { - let chunks = chunks.iter().copied().try_join().await?; +async fn merge_chunks( + first: CssChunkVc, + chunks: impl IntoIterator, +) -> Result { + let chunks = chunks.into_iter().copied().try_join().await?; let main_entries = chunks .iter() .map(|c| c.main_entries) @@ -59,49 +66,62 @@ async fn merge_chunks(first: CssChunkVc, chunks: &[CssChunkVc]) -> Result, children: Vec) -> Result { - let mut chunks = Vec::new(); - // TODO optimize - if let Some(local) = local { - // Local chunks have the same common_parent and could be merged into fewer - // chunks. (We use a pretty large threshold for that.) - let mut local = local.await?.iter().copied().map(css).try_join().await?; - // Merge all local chunks when they are too many - if local.len() > LOCAL_CHUNK_MERGE_THRESHOLD { - let merged = take(&mut local); - if let Some(first) = merged.first().copied() { - local.push(merge_chunks(first, &merged).await?); - } +/// The maximum number of chunks to exist in a single chunk group. The optimizer +/// will merge chunks into groups until it has at most this number of chunks. +const MAX_CHUNK_COUNT: usize = 20; + +/// Groups adjacent chunks into at most `MAX_CHUNK_COUNT` groups. +fn aggregate_adjacent_chunks(chunks: &[ChunkVc]) -> Vec> { + // Each of the resulting merged chunks will have `chunks_per_merged_chunk` + // chunks in them, except for the first `chunks_mod` chunks, which will have + // one more chunk. + let chunks_per_merged_chunk = chunks.len() / MAX_CHUNK_COUNT; + let mut chunks_mod = chunks.len() % MAX_CHUNK_COUNT; + + let mut chunks_vecs = vec![]; + let mut current_chunks = vec![]; + + for chunk in chunks.iter().copied() { + if current_chunks.len() < chunks_per_merged_chunk { + current_chunks.push(chunk); + } else if current_chunks.len() == chunks_per_merged_chunk && chunks_mod > 0 { + current_chunks.push(chunk); + chunks_mod -= 1; + chunks_vecs.push(std::mem::take(&mut current_chunks)); + } else { + chunks_vecs.push(std::mem::take(&mut current_chunks)); + current_chunks.push(chunk); } - chunks.append(&mut local); } - for children in children { - let mut children = children.await?.iter().copied().map(css).try_join().await?; - chunks.append(&mut children); + + if !current_chunks.is_empty() { + chunks_vecs.push(current_chunks); } - // Multiple very small chunks could be merged to avoid requests. (We use a small - // threshold for that.) - // TODO implement that - - // When there are too many chunks, try hard to reduce the number of chunks to - // limit the request count. - if chunks.len() > TOTAL_CHUNK_MERGE_THRESHOLD { - let size = chunks.len().div_ceil(TOTAL_CHUNK_MERGE_THRESHOLD); - // TODO be smarter in selecting the chunks to merge - // see ecmascript implementation - for merged in take(&mut chunks).chunks(size) { - chunks.push(merge_chunks(*merged.first().unwrap(), merged).await?); - } + + chunks_vecs +} + +/// Merges adjacent chunks into at most `MAX_CHUNK_COUNT` chunks. +async fn merge_adjacent_chunks(chunks_vc: ChunksVc) -> Result { + let chunks = chunks_vc.await?; + + if chunks.len() <= MAX_CHUNK_COUNT { + return Ok(chunks_vc); } - Ok(ChunksVc::cell( - chunks.into_iter().map(|c| c.as_chunk()).collect(), - )) + + let chunks = aggregate_adjacent_chunks(&chunks); + + let chunks = chunks + .into_iter() + .map(|chunks| async move { + let chunks = chunks.iter().copied().map(css).try_join().await?; + merge_chunks(*chunks.first().unwrap(), &chunks).await + }) + .try_join() + .await? + .into_iter() + .map(|chunk| chunk.as_chunk()) + .collect(); + + Ok(ChunksVc::cell(chunks)) } diff --git a/crates/turbopack-css/src/chunk/writer.rs b/crates/turbopack-css/src/chunk/writer.rs index 73782bd522f0e..4f8ffa5297d54 100644 --- a/crates/turbopack-css/src/chunk/writer.rs +++ b/crates/turbopack-css/src/chunk/writer.rs @@ -1,4 +1,7 @@ -use std::{collections::VecDeque, io::Write}; +use std::{ + collections::{HashSet, VecDeque}, + io::Write, +}; use anyhow::Result; use turbo_tasks::{primitives::StringVc, ValueToString}; @@ -18,12 +21,22 @@ pub async fn expand_imports( "".to_string(), )]; let mut external_imports = vec![]; + let mut imported_chunk_items: HashSet<(String, String, CssChunkItemVc)> = HashSet::default(); + let mut composed_chunk_items: HashSet = HashSet::default(); while let Some((chunk_item, imports, close)) = stack.last_mut() { match imports.pop_front() { Some(CssImport::Internal(import, imported_chunk_item)) => { let (open, close) = import.await?.attributes.await?.print_block()?; + if !imported_chunk_items.insert(( + open.clone(), + close.clone(), + imported_chunk_item.resolve().await?, + )) { + continue; + } + let id = &*imported_chunk_item.to_string().await?; writeln!(code, "/* import({}) */", id)?; writeln!(code, "{}", open)?; @@ -36,6 +49,22 @@ pub async fn expand_imports( close, )); } + Some(CssImport::Composes(composed_chunk_item)) => { + if !composed_chunk_items.insert(composed_chunk_item.resolve().await?) { + continue; + } + + let id = &*composed_chunk_item.to_string().await?; + writeln!(code, "/* composes({}) */", id)?; + + let composed_content_vc = composed_chunk_item.content(); + let composed_content = &*composed_content_vc.await?; + stack.push(( + composed_chunk_item, + composed_content.imports.iter().cloned().collect(), + "".to_string(), + )); + } Some(CssImport::External(url_vc)) => { external_imports.push(url_vc); } diff --git a/crates/turbopack-css/src/module_asset.rs b/crates/turbopack-css/src/module_asset.rs index 019b2f3a418f5..1d50aaadf36dd 100644 --- a/crates/turbopack-css/src/module_asset.rs +++ b/crates/turbopack-css/src/module_asset.rs @@ -1,11 +1,13 @@ use std::{fmt::Write, sync::Arc}; use anyhow::Result; +use indexmap::IndexMap; +use indoc::formatdoc; use swc_core::{ common::{BytePos, FileName, LineCol, SourceMap}, css::modules::CssClassName, }; -use turbo_tasks::{primitives::StringVc, ValueToString, ValueToStringVc}; +use turbo_tasks::{primitives::StringVc, Value, ValueToString, ValueToStringVc}; use turbo_tasks_fs::FileSystemPathVc; use turbopack_core::{ asset::{Asset, AssetContentVc, AssetVc}, @@ -15,9 +17,11 @@ use turbopack_core::{ ChunkingTypeOptionVc, }, context::AssetContextVc, + issue::{Issue, IssueSeverity, IssueSeverityVc, IssueVc}, reference::{AssetReference, AssetReferenceVc, AssetReferencesVc}, resolve::{ origin::{ResolveOrigin, ResolveOriginVc}, + parse::RequestVc, ResolveResult, ResolveResultVc, }, }; @@ -31,7 +35,16 @@ use turbopack_ecmascript::{ ParseResultSourceMap, ParseResultSourceMapVc, }; -use crate::{parse::ParseResult, transform::CssInputTransformsVc, CssModuleAssetVc}; +use crate::{ + chunk::{ + CssChunkItem, CssChunkItemContentVc, CssChunkItemVc, CssChunkPlaceable, + CssChunkPlaceableVc, CssChunkVc, + }, + parse::ParseResult, + references::compose::CssModuleComposeReferenceVc, + transform::CssInputTransformsVc, + CssModuleAssetVc, +}; #[turbo_tasks::value] #[derive(Clone)] @@ -62,8 +75,118 @@ impl Asset for ModuleCssModuleAsset { } #[turbo_tasks::function] - fn references(&self) -> AssetReferencesVc { - self.inner.references() + async fn references(self_vc: ModuleCssModuleAssetVc) -> Result { + let references = self_vc.await?.inner.references().await?; + let module_references = self_vc.module_references().await?; + + let references: Vec<_> = references + .iter() + .copied() + .chain(module_references.iter().copied()) + .collect(); + + Ok(AssetReferencesVc::cell(references)) + } +} + +/// A CSS class that is exported from a CSS module. +/// +/// See [`ModuleCssClasses`] for more information. +#[turbo_tasks::value(transparent)] +#[derive(Debug, Clone)] +enum ModuleCssClass { + Local { + name: String, + }, + Global { + name: String, + }, + Import { + original: String, + from: CssModuleComposeReferenceVc, + }, +} + +/// A map of CSS classes exported from a CSS module. +/// +/// ## Example +/// +/// ```css +/// :global(.class1) { +/// color: red; +/// } +/// +/// .class2 { +/// color: blue; +/// } +/// +/// .class3 { +/// composes: class4 from "./other.module.css"; +/// } +/// ``` +/// +/// The above CSS module would have the following exports: +/// 1. class1: [Global("exported_class1")] +/// 2. class2: [Local("exported_class2")] +/// 3. class3: [Local("exported_class3), Import("class4", "./other.module.css")] +#[turbo_tasks::value(transparent)] +#[derive(Debug, Clone)] +struct ModuleCssClasses(IndexMap>); + +#[turbo_tasks::value_impl] +impl ModuleCssModuleAssetVc { + #[turbo_tasks::function] + async fn classes(self) -> Result { + let inner = self.await?.inner; + let parse_result = inner.parse().await?; + let mut classes = IndexMap::default(); + + // TODO(alexkirsz) Should we report an error on parse error here? + if let ParseResult::Ok { exports, .. } = &*parse_result { + for (class_name, export_class_names) in exports { + let mut export = Vec::default(); + + for export_class_name in export_class_names { + export.push(match export_class_name { + CssClassName::Import { from, name } => ModuleCssClass::Import { + original: name.to_string(), + from: CssModuleComposeReferenceVc::new( + self.as_resolve_origin(), + RequestVc::parse(Value::new(from.to_string().into())), + ), + }, + CssClassName::Local { name } => ModuleCssClass::Local { + name: name.to_string(), + }, + CssClassName::Global { name } => ModuleCssClass::Global { + name: name.to_string(), + }, + }) + } + + classes.insert(class_name.to_string(), export); + } + } + + Ok(ModuleCssClassesVc::cell(classes)) + } + + #[turbo_tasks::function] + async fn module_references(self) -> Result { + let mut references = vec![]; + + for (_, class_names) in &*self.classes().await? { + for class_name in class_names { + match class_name { + ModuleCssClass::Import { from, .. } => { + references.push((*from).into()); + } + ModuleCssClass::Local { .. } | ModuleCssClass::Global { .. } => {} + } + } + } + + Ok(AssetReferencesVc::cell(references)) } } @@ -78,10 +201,13 @@ impl ChunkableAsset for ModuleCssModuleAsset { #[turbo_tasks::value_impl] impl EcmascriptChunkPlaceable for ModuleCssModuleAsset { #[turbo_tasks::function] - fn as_chunk_item(&self, context: ChunkingContextVc) -> EcmascriptChunkItemVc { + fn as_chunk_item( + self_vc: ModuleCssModuleAssetVc, + context: ChunkingContextVc, + ) -> EcmascriptChunkItemVc { ModuleChunkItem { context, - module: self.inner, + module: self_vc, } .cell() .into() @@ -108,7 +234,7 @@ impl ResolveOrigin for ModuleCssModuleAsset { #[turbo_tasks::value] struct ModuleChunkItem { - module: CssModuleAssetVc, + module: ModuleCssModuleAssetVc, context: ChunkingContextVc, } @@ -118,7 +244,7 @@ impl ValueToString for ModuleChunkItem { async fn to_string(&self) -> Result { Ok(StringVc::cell(format!( "{} (css module)", - self.module.await?.source.path().to_string().await? + self.module.path().to_string().await? ))) } } @@ -126,27 +252,136 @@ impl ValueToString for ModuleChunkItem { #[turbo_tasks::value_impl] impl ChunkItem for ModuleChunkItem { #[turbo_tasks::function] - fn references(&self) -> AssetReferencesVc { - AssetReferencesVc::cell(vec![CssProxyToCssAssetReference { + async fn references(&self) -> Result { + // The proxy reference must come first so it is processed before other potential + // references inside of the CSS, like `@import` and `composes:`. + // This affects the order in which the resulting CSS chunks will be loaded: + // later references are processed first in the post-order traversal of the + // reference tree, and as such they will be loaded first in the resulting HTML. + let mut references = vec![CssProxyToCssAssetReference { module: self.module, - context: self.context, } .cell() - .into()]) + .into()]; + + references.extend(self.module.references().await?.iter().copied()); + + Ok(AssetReferencesVc::cell(references)) + } +} + +#[turbo_tasks::value_impl] +impl EcmascriptChunkItem for ModuleChunkItem { + #[turbo_tasks::function] + fn chunking_context(&self) -> ChunkingContextVc { + self.context + } + + #[turbo_tasks::function] + fn related_path(&self) -> FileSystemPathVc { + self.module.path() + } + + #[turbo_tasks::function] + async fn content(&self) -> Result { + let classes = self.module.classes().await?; + + let mut code = "__turbopack_export_value__({\n".to_string(); + for (export_name, class_names) in &*classes { + let mut exported_class_names = Vec::with_capacity(class_names.len()); + + for class_name in class_names { + match class_name { + ModuleCssClass::Import { + original: original_name, + from, + } => { + let resolved_module = from.resolve_reference().first_asset().await?; + + let Some(resolved_module) = &*resolved_module else { + CssModuleComposesIssue { + severity: IssueSeverity::Error.cell(), + path: self.module.path(), + message: StringVc::cell(formatdoc! { + r#" + Module {from} referenced in `composes: ... from {from};` can't be resolved. + "#, + from = &*from.await?.request.to_string().await? + }), + }.cell().as_issue().emit(); + continue; + }; + + let Some(css_module) = ModuleCssModuleAssetVc::resolve_from(resolved_module).await? else { + CssModuleComposesIssue { + severity: IssueSeverity::Error.cell(), + path: self.module.path(), + message: StringVc::cell(formatdoc! { + r#" + Module {from} referenced in `composes: ... from {from};` is not a CSS module. + "#, + from = &*from.await?.request.to_string().await? + }), + }.cell().as_issue().emit(); + continue; + }; + + // TODO(alexkirsz) We should also warn if `original_name` can't be found in + // the target module. + + let Some(placeable) = EcmascriptChunkPlaceableVc::resolve_from(css_module).await? else { + unreachable!("ModuleCssModuleAsset implements EcmascriptChunkPlaceableVc"); + }; + + let module_id = + stringify_js(&*placeable.as_chunk_item(self.context).id().await?); + let original_name = stringify_js(original_name); + exported_class_names.push(format! { + "__turbopack_import__({module_id})[{original_name}]" + }); + } + ModuleCssClass::Local { name: class_name } + | ModuleCssClass::Global { name: class_name } => { + exported_class_names.push(stringify_js(class_name)); + } + } + } + + writeln!( + code, + " {}: {},", + stringify_js(export_name), + exported_class_names.join(" + \" \" + ") + )?; + } + code += "});\n"; + Ok(EcmascriptChunkItemContent { + inner_code: code.clone().into(), + // We generate a minimal map for runtime code so that the filename is + // displayed in dev tools. + source_map: Some(generate_minimal_source_map( + format!("{}.js", self.module.path().await?.path), + code, + )), + ..Default::default() + } + .cell()) } } #[turbo_tasks::value] struct CssProxyToCssAssetReference { - module: CssModuleAssetVc, - context: ChunkingContextVc, + module: ModuleCssModuleAssetVc, } #[turbo_tasks::value_impl] impl ValueToString for CssProxyToCssAssetReference { #[turbo_tasks::function] - fn to_string(&self) -> StringVc { - StringVc::cell("css".to_string()) + async fn to_string(&self) -> Result { + Ok(StringVc::cell(format!( + "proxy(css) {}", + self.module.path().to_string().await?, + ))) } } @@ -154,7 +389,14 @@ impl ValueToString for CssProxyToCssAssetReference { impl AssetReference for CssProxyToCssAssetReference { #[turbo_tasks::function] fn resolve_reference(&self) -> ResolveResultVc { - ResolveResult::asset(self.module.into()).cell() + ResolveResult::asset( + CssProxyModuleAsset { + module: self.module, + } + .cell() + .into(), + ) + .cell() } } @@ -166,62 +408,112 @@ impl ChunkableAssetReference for CssProxyToCssAssetReference { } } +/// This structure exists solely in order to extend the `references` returned by +/// a standard [`CssModuleAsset`] with CSS modules' `composes:` references. +#[turbo_tasks::value] +#[derive(Clone)] +struct CssProxyModuleAsset { + module: ModuleCssModuleAssetVc, +} + #[turbo_tasks::value_impl] -impl EcmascriptChunkItem for ModuleChunkItem { +impl Asset for CssProxyModuleAsset { #[turbo_tasks::function] - fn chunking_context(&self) -> ChunkingContextVc { - self.context + fn path(&self) -> FileSystemPathVc { + self.module.path() } #[turbo_tasks::function] - fn related_path(&self) -> FileSystemPathVc { + fn content(&self) -> AssetContentVc { + self.module.content() + } + + #[turbo_tasks::function] + async fn references(&self) -> Result { + // The original references must come first so they're processed before other + // potential references inside of the CSS, like `@import` and `composes:`. This + // affects the order in which the resulting CSS chunks will be loaded: + // later references are processed first in the post-order traversal of + // the reference tree, and as such they will be loaded first in the + // resulting HTML. + let mut references = self.module.await?.inner.references().await?.clone_value(); + + references.extend(self.module.module_references().await?.iter().copied()); + + Ok(AssetReferencesVc::cell(references)) + } +} + +#[turbo_tasks::value_impl] +impl ChunkableAsset for CssProxyModuleAsset { + #[turbo_tasks::function] + fn as_chunk(self_vc: CssProxyModuleAssetVc, context: ChunkingContextVc) -> ChunkVc { + CssChunkVc::new(context, self_vc.into()).into() + } +} + +#[turbo_tasks::value_impl] +impl CssChunkPlaceable for CssProxyModuleAsset { + #[turbo_tasks::function] + fn as_chunk_item(&self, context: ChunkingContextVc) -> CssChunkItemVc { + CssProxyModuleChunkItemVc::cell(CssProxyModuleChunkItem { + module: self.module, + context, + }) + .into() + } +} + +#[turbo_tasks::value_impl] +impl ResolveOrigin for CssProxyModuleAsset { + #[turbo_tasks::function] + fn origin_path(&self) -> FileSystemPathVc { self.module.path() } #[turbo_tasks::function] - async fn content(&self) -> Result { - let parsed = self.module.parse().await?; - Ok(match &*parsed { - ParseResult::Ok { exports, .. } => { - let mut code = "__turbopack_export_value__({\n".to_string(); - for (key, elements) in exports { - let content = elements - .iter() - .map(|element| match element { - CssClassName::Local { name } | CssClassName::Global { name } => &**name, - CssClassName::Import { .. } => "TODO", - }) - .collect::>() - .join(" "); - writeln!(code, " {}: {},", stringify_js(key), stringify_js(&content))?; - } - code += "});\n"; - EcmascriptChunkItemContent { - inner_code: code.clone().into(), - // We generate a minimal map for runtime code so that the filename is - // displayed in dev tools. - source_map: Some(generate_minimal_source_map( - format!("{}.js", self.module.path().await?.path), - code, - )), - ..Default::default() - } - } - ParseResult::NotFound | ParseResult::Unparseable => { - let code = "__turbopack_export_value__({});\n"; - EcmascriptChunkItemContent { - inner_code: code.into(), - // We generate a minimal map for runtime code so that the filename is - // displayed in dev tools. - source_map: Some(generate_minimal_source_map( - format!("{}.js", self.module.path().await?.path), - code.into(), - )), - ..Default::default() - } - } - } - .cell()) + fn context(&self) -> AssetContextVc { + self.module.context() + } +} + +#[turbo_tasks::value] +struct CssProxyModuleChunkItem { + module: ModuleCssModuleAssetVc, + context: ChunkingContextVc, +} + +#[turbo_tasks::value_impl] +impl ValueToString for CssProxyModuleChunkItem { + #[turbo_tasks::function] + fn to_string(&self) -> StringVc { + self.module.as_chunk_item(self.context).to_string() + } +} + +#[turbo_tasks::value_impl] +impl ChunkItem for CssProxyModuleChunkItem { + #[turbo_tasks::function] + fn references(&self) -> AssetReferencesVc { + self.module.references() + } +} + +#[turbo_tasks::value_impl] +impl CssChunkItem for CssProxyModuleChunkItem { + #[turbo_tasks::function] + async fn content(&self) -> Result { + Ok(self + .module + .await? + .inner + .as_chunk_item(self.context) + .content()) + } + + #[turbo_tasks::function] + fn chunking_context(&self) -> ChunkingContextVc { + self.context } } @@ -244,3 +536,40 @@ fn generate_minimal_source_map(filename: String, source: String) -> ParseResultS let map = ParseResultSourceMap::new(sm, mappings); map.cell() } + +#[turbo_tasks::value(shared)] +struct CssModuleComposesIssue { + severity: IssueSeverityVc, + path: FileSystemPathVc, + message: StringVc, +} + +#[turbo_tasks::value_impl] +impl Issue for CssModuleComposesIssue { + #[turbo_tasks::function] + fn severity(&self) -> IssueSeverityVc { + self.severity + } + + #[turbo_tasks::function] + async fn title(&self) -> Result { + Ok(StringVc::cell( + "An issue occurred while resolving a CSS module `composes:` rule".to_string(), + )) + } + + #[turbo_tasks::function] + fn category(&self) -> StringVc { + StringVc::cell("css".to_string()) + } + + #[turbo_tasks::function] + fn context(&self) -> FileSystemPathVc { + self.path + } + + #[turbo_tasks::function] + fn description(&self) -> StringVc { + self.message + } +} diff --git a/crates/turbopack-css/src/references/compose.rs b/crates/turbopack-css/src/references/compose.rs new file mode 100644 index 0000000000000..2f5ccc19c0a2a --- /dev/null +++ b/crates/turbopack-css/src/references/compose.rs @@ -0,0 +1,53 @@ +use anyhow::Result; +use turbo_tasks::{primitives::StringVc, Value, ValueToString, ValueToStringVc}; +use turbopack_core::{ + chunk::{ChunkableAssetReference, ChunkableAssetReferenceVc}, + reference::{AssetReference, AssetReferenceVc}, + reference_type::CssReferenceSubType, + resolve::{origin::ResolveOriginVc, parse::RequestVc, ResolveResultVc}, +}; + +use crate::references::css_resolve; + +/// A `composes: ... from ...` CSS module reference. +#[turbo_tasks::value] +#[derive(Hash, Debug)] +pub struct CssModuleComposeReference { + pub origin: ResolveOriginVc, + pub request: RequestVc, +} + +#[turbo_tasks::value_impl] +impl CssModuleComposeReferenceVc { + /// Creates a new [`CssModuleComposeReference`]. + #[turbo_tasks::function] + pub fn new(origin: ResolveOriginVc, request: RequestVc) -> Self { + Self::cell(CssModuleComposeReference { origin, request }) + } +} + +#[turbo_tasks::value_impl] +impl AssetReference for CssModuleComposeReference { + #[turbo_tasks::function] + fn resolve_reference(&self) -> ResolveResultVc { + css_resolve( + self.origin, + self.request, + Value::new(CssReferenceSubType::Compose), + ) + } +} + +#[turbo_tasks::value_impl] +impl ValueToString for CssModuleComposeReference { + #[turbo_tasks::function] + async fn to_string(&self) -> Result { + Ok(StringVc::cell(format!( + "compose(url) {}", + self.request.to_string().await?, + ))) + } +} + +#[turbo_tasks::value_impl] +impl ChunkableAssetReference for CssModuleComposeReference {} diff --git a/crates/turbopack-css/src/references/mod.rs b/crates/turbopack-css/src/references/mod.rs index dcabbb0d86a55..2828ec1410364 100644 --- a/crates/turbopack-css/src/references/mod.rs +++ b/crates/turbopack-css/src/references/mod.rs @@ -27,6 +27,7 @@ use crate::{ CssInputTransformsVc, CssModuleAssetType, }; +pub(crate) mod compose; pub(crate) mod import; pub(crate) mod url; diff --git a/crates/turbopack-ecmascript/Cargo.toml b/crates/turbopack-ecmascript/Cargo.toml index e79647e9e41fd..42800585bd280 100644 --- a/crates/turbopack-ecmascript/Cargo.toml +++ b/crates/turbopack-ecmascript/Cargo.toml @@ -15,7 +15,7 @@ async-trait = "0.1.56" easy-error = "1.0.0" fxhash = "0.2.1" indexmap = { workspace = true } -indoc = "1.0" +indoc = { workspace = true } lazy_static = "1.4.0" next-font = { path = "../next-font" } next-transform-dynamic = { path = "../next-transform-dynamic" } diff --git a/crates/turbopack-tests/tests/snapshot/css/css/input/style.css b/crates/turbopack-tests/tests/snapshot/css/css/input/style.css index f8017cfb225af..38ceb56638d8e 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/input/style.css +++ b/crates/turbopack-tests/tests/snapshot/css/css/input/style.css @@ -1,3 +1,7 @@ +@import url("./imported.css"); +/* De-duplicate similar imports */ +@import url("../input/imported.css"); +/* But not if they have different attributes */ @import url("./imported.css") layer(layer) print; .style { color: yellow; diff --git a/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css b/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css index fc4e8827b82ac..cd9abdebd64a2 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css +++ b/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css @@ -5,3 +5,13 @@ background: purple; } } + +.composed-module-style { + composes: foo-module-style from "foo/style.module.css"; + color: green; +} + +.another-composed-module-style { + composes: foo-module-style from "foo/style.module.css"; + color: yellow; +} \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css b/crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css index 6e4de4f4868f6..fd1cad7f446ce 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css @@ -1,5 +1,5 @@ /* chunk [workspace]/crates/turbopack-tests/tests/snapshot/css/css/output/8697f_foo_style.module.css */ -@layer s\[project\]\/crates\/turbopack-tests\/tests\/snapshot\/css\/css\/input\/node_modules\/foo\/style\.module\.css\ \(css\) { +@layer s\[project\]\/crates\/turbopack-tests\/tests\/snapshot\/css\/css\/input\/node_modules\/foo\/style\.module\.css\ \(css\ module\) { .foo-module-style__style__abf9e738 { color: blue; } diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js index 9fc4b235675aa..9fbdba47cebd8 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js @@ -16,6 +16,8 @@ console.log(__TURBOPACK__imported__module__$5b$project$5d2f$crates$2f$turbopack$ "[project]/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css (css module)": (({ r: __turbopack_require__, x: __turbopack_external_require__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, c: __turbopack_cache__, l: __turbopack_load__, j: __turbopack_cjs__, p: process, g: global, __dirname }) => (() => { __turbopack_export_value__({ + "another-composed-module-style": "another-composed-module-style__style__9bcf751c" + " " + __turbopack_import__("[project]/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css (css module)")["foo-module-style"], + "composed-module-style": "composed-module-style__style__9bcf751c" + " " + __turbopack_import__("[project]/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css (css module)")["foo-module-style"], "inner": "inner__style__9bcf751c", "module-style": "module-style__style__9bcf751c", }); diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js.map b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js.map index 7b9b68b138e03..73b0d8ea4ae2f 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js.map +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_index_531223.js.map @@ -3,6 +3,6 @@ "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/index.js"],"sourcesContent":["import \"foo/style.css\";\nimport \"foo\";\nimport \"./style.css\";\nimport fooStyle from \"foo/style.module.css\";\nimport style from \"./style.module.css\";\n\nconsole.log(style, fooStyle);\n"],"names":[],"mappings":";;;;;;;;AAMA,QAAQ,GAAG"}}, {"offset": {"line": 13, "column": 0}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}, - {"offset": {"line": 17, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css.js"],"sourcesContent":["__turbopack_export_value__({\n \"inner\": \"inner__style__9bcf751c\",\n \"module-style\": \"module-style__style__9bcf751c\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA"}}, - {"offset": {"line": 21, "column": 0}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] + {"offset": {"line": 17, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css.js"],"sourcesContent":["__turbopack_export_value__({\n \"another-composed-module-style\": \"another-composed-module-style__style__9bcf751c\" + \" \" + __turbopack_import__(\"[project]/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css (css module)\")[\"foo-module-style\"],\n \"composed-module-style\": \"composed-module-style__style__9bcf751c\" + \" \" + __turbopack_import__(\"[project]/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css (css module)\")[\"foo-module-style\"],\n \"inner\": \"inner__style__9bcf751c\",\n \"module-style\": \"module-style__style__9bcf751c\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA"}}, + {"offset": {"line": 23, "column": 0}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css index 32febf3713d4d..9f2a8ac05b1f6 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css @@ -1,4 +1,13 @@ /* chunk [workspace]/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css */ +/* import([project]/crates/turbopack-tests/tests/snapshot/css/css/input/imported.css (css)) */ + +@layer s\[project\]\/crates\/turbopack-tests\/tests\/snapshot\/css\/css\/input\/imported\.css\ \(css\) { +.imported { + color: cyan; +} +} + + /* import([project]/crates/turbopack-tests/tests/snapshot/css/css/input/imported.css (css)) */ @layer layer { @media print { diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css.map b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css.map index ce2530f0e339f..fec14d9064dfe 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css.map +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.css.map @@ -1,8 +1,10 @@ { "version": 3, "sections": [ - {"offset": {"line": 5, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/imported.css"],"sourcesContent":[".imported {\n color: cyan;\n}\n"],"names":[],"mappings":"AAAA,CAAC,QAAQ,CAAC,CAAC;EACT,KAAK,EAAE,IAAI;AACb,CAAC"}}, - {"offset": {"line": 7, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}, - {"offset": {"line": 13, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.css"],"sourcesContent":["@import url(\"./imported.css\") layer(layer) print;\n.style {\n color: yellow;\n}\n"],"names":[],"mappings":"AACA,CAAC,KAAK,CAAC,CAAC;EACN,KAAK,EAAE,MAAM;AACf,CAAC"}}, - {"offset": {"line": 15, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] + {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/imported.css"],"sourcesContent":[".imported {\n color: cyan;\n}\n"],"names":[],"mappings":"AAAA,CAAC,QAAQ,CAAC,CAAC;EACT,KAAK,EAAE,IAAI;AACb,CAAC"}}, + {"offset": {"line": 6, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}, + {"offset": {"line": 14, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/imported.css"],"sourcesContent":[".imported {\n color: cyan;\n}\n"],"names":[],"mappings":"AAAA,CAAC,QAAQ,CAAC,CAAC;EACT,KAAK,EAAE,IAAI;AACb,CAAC"}}, + {"offset": {"line": 16, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}, + {"offset": {"line": 22, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.css"],"sourcesContent":["@import url(\"./imported.css\");\n/* De-duplicate similar imports */\n@import url(\"../input/imported.css\");\n/* But not if they have different attributes */\n@import url(\"./imported.css\") layer(layer) print;\n.style {\n color: yellow;\n}\n"],"names":[],"mappings":"AAKA,CAAC,KAAK,CAAC,CAAC;EACN,KAAK,EAAE,MAAM;AACf,CAAC"}}, + {"offset": {"line": 24, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css index daaf02703a5e8..3f3eed4b19c53 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css @@ -1,5 +1,5 @@ /* chunk [workspace]/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css */ -@layer s\[project\]\/crates\/turbopack-tests\/tests\/snapshot\/css\/css\/input\/style\.module\.css\ \(css\) { +@layer s\[project\]\/crates\/turbopack-tests\/tests\/snapshot\/css\/css\/input\/style\.module\.css\ \(css\ module\) { .module-style__style__9bcf751c { color: magenta; } @@ -7,6 +7,12 @@ .module-style__style__9bcf751c + .inner__style__9bcf751c { background: purple; } +.composed-module-style__style__9bcf751c { + color: green; +} +.another-composed-module-style__style__9bcf751c { + color: yellow; +} } diff --git a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css.map b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css.map index 50a6ef92f1c9a..4aaa78e2bf72a 100644 --- a/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css.map +++ b/crates/turbopack-tests/tests/snapshot/css/css/output/crates_turbopack-tests_tests_snapshot_css_css_input_style.module.css.map @@ -1,6 +1,6 @@ { "version": 3, "sections": [ - {"offset": {"line": 2, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css"],"sourcesContent":[".module-style {\n color: magenta;\n > h1,\n + .inner {\n background: purple;\n }\n}\n"],"names":[],"mappings":"AAAA,CAAC,6BAAY,CAAC,CAAC;EACb,KAAK,EAAE,OAAO;AAKhB,CAAC;AAND,CAAC,6BAAY,CAEX,CAAC,CAAC,EAAE;AAFN,CAAC,6BAAY,CAGX,CAAC,CAAC,CAAC,sBAAK,CAAC,CAAC;EACR,UAAU,EAAE,MAAM;AACpB,CAAC"}}, - {"offset": {"line": 8, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] + {"offset": {"line": 2, "column": 0}, "map": {"version":3,"sources":["/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css"],"sourcesContent":[".module-style {\n color: magenta;\n > h1,\n + .inner {\n background: purple;\n }\n}\n\n.composed-module-style {\n composes: foo-module-style from \"foo/style.module.css\";\n color: green;\n}\n\n.another-composed-module-style {\n composes: foo-module-style from \"foo/style.module.css\";\n color: yellow;\n}"],"names":[],"mappings":"AAAA,CAAC,6BAAY,CAAC,CAAC;EACb,KAAK,EAAE,OAAO;AAKhB,CAAC;AAND,CAAC,6BAAY,CAEX,CAAC,CAAC,EAAE;AAFN,CAAC,6BAAY,CAGX,CAAC,CAAC,CAAC,sBAAK,CAAC,CAAC;EACR,UAAU,EAAE,MAAM;AACpB,CAAC;AAGH,CAAC,sCAAqB,CAAC,CAAC;EAEtB,KAAK,EAAE,KAAK;AACd,CAAC;AAED,CAAC,8CAA6B,CAAC,CAAC;EAE9B,KAAK,EAAE,MAAM;AACf,CAAC"}}, + {"offset": {"line": 14, "column": 1}, "map": {"version":3,"sources":[],"names":[],"mappings":"A"}}] } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js b/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js similarity index 98% rename from crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js rename to crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js index 9f094626a8880..37c777498018f 100644 --- a/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js +++ b/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js @@ -1,4 +1,4 @@ -(self.TURBOPACK = self.TURBOPACK || []).push(["output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js", { +(self.TURBOPACK = self.TURBOPACK || []).push(["output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js", { "[project]/crates/turbopack-tests/tests/snapshot/emotion/emotion/input/index.js (ecmascript)": (({ r: __turbopack_require__, x: __turbopack_external_require__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, c: __turbopack_cache__, l: __turbopack_load__, j: __turbopack_cjs__, p: process, g: global, __dirname }) => (() => { @@ -29,7 +29,7 @@ console.log(StyledButton, ClassNameButton); })()), }, ({ loadedChunks, instantiateRuntimeModule }) => { - if(!(true && loadedChunks.has("output/63a02_@emotion_react_jsx-dev-runtime.js") && loadedChunks.has("output/63a02_@emotion_react_index.js") && loadedChunks.has("output/63a02_@emotion_styled_index.js") && loadedChunks.has("output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_ccc906.js"))) return true; + if(!(true && loadedChunks.has("output/63a02_@emotion_styled_index.js") && loadedChunks.has("output/63a02_@emotion_react_index.js") && loadedChunks.has("output/63a02_@emotion_react_jsx-dev-runtime.js") && loadedChunks.has("output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_8c70a7.js"))) return true; instantiateRuntimeModule("[project]/crates/turbopack-tests/tests/snapshot/emotion/emotion/input/index.js (ecmascript)"); }]); (() => { @@ -1079,4 +1079,4 @@ globalThis.TURBOPACK = { })(); -//# sourceMappingURL=crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js.map \ No newline at end of file +//# sourceMappingURL=crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js.map \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js.map b/crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js.map similarity index 100% rename from crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_f0bbb5.js.map rename to crates/turbopack-tests/tests/snapshot/emotion/emotion/output/crates_turbopack-tests_tests_snapshot_emotion_emotion_input_index_6545dc.js.map diff --git a/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js b/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js similarity index 98% rename from crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js rename to crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js index ba449bf058035..3f8a0c2a48181 100644 --- a/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js +++ b/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js @@ -1,4 +1,4 @@ -(self.TURBOPACK = self.TURBOPACK || []).push(["output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js", { +(self.TURBOPACK = self.TURBOPACK || []).push(["output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js", { "[project]/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/input/packages/app/index.js (ecmascript)": (({ r: __turbopack_require__, x: __turbopack_external_require__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, c: __turbopack_cache__, l: __turbopack_load__, j: __turbopack_cjs__, p: process, g: global, __dirname }) => (() => { @@ -11,7 +11,7 @@ console.log(__TURBOPACK__imported__module__$5b$project$5d2f$crates$2f$turbopack$ })()), }, ({ loadedChunks, instantiateRuntimeModule }) => { - if(!(true && loadedChunks.has("output/63a02_react_jsx-dev-runtime.js") && loadedChunks.has("output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_c4293a.js") && loadedChunks.has("output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_component_index.js") && loadedChunks.has("output/7b7bf_third_party_component_index.js"))) return true; + if(!(true && loadedChunks.has("output/63a02_react_jsx-dev-runtime.js") && loadedChunks.has("output/7b7bf_third_party_component_index.js") && loadedChunks.has("output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_component_index.js") && loadedChunks.has("output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_569e79.js"))) return true; instantiateRuntimeModule("[project]/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/input/packages/app/index.js (ecmascript)"); }]); (() => { @@ -1061,4 +1061,4 @@ globalThis.TURBOPACK = { })(); -//# sourceMappingURL=a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js.map \ No newline at end of file +//# sourceMappingURL=a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js.map \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js.map b/crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js.map similarity index 100% rename from crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_a3868e.js.map rename to crates/turbopack-tests/tests/snapshot/swc_transforms/mono_transforms/output/a587c_tests_snapshot_swc_transforms_mono_transforms_input_packages_app_index_968e59.js.map diff --git a/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js b/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js similarity index 99% rename from crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js rename to crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js index 1e08534ab1d46..97b3e07e8c5f3 100644 --- a/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js +++ b/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js @@ -1,4 +1,4 @@ -(self.TURBOPACK = self.TURBOPACK || []).push(["output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js", { +(self.TURBOPACK = self.TURBOPACK || []).push(["output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js", { "[project]/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/input/index.js (ecmascript)": (({ r: __turbopack_require__, x: __turbopack_external_require__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, c: __turbopack_cache__, l: __turbopack_load__, j: __turbopack_cjs__, p: process, g: global, __dirname }) => (() => { @@ -13,7 +13,7 @@ console.log(Foo, [].includes("foo")); })()), }, ({ loadedChunks, instantiateRuntimeModule }) => { - if(!(true && loadedChunks.has("output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_eede4f.js") && loadedChunks.has("output/63a02_@swc_helpers_src__class_call_check.mjs._.js"))) return true; + if(!(true && loadedChunks.has("output/63a02_@swc_helpers_src__class_call_check.mjs._.js") && loadedChunks.has("output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_eede4f.js"))) return true; instantiateRuntimeModule("[project]/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/input/index.js (ecmascript)"); }]); (() => { @@ -1063,4 +1063,4 @@ globalThis.TURBOPACK = { })(); -//# sourceMappingURL=79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js.map \ No newline at end of file +//# sourceMappingURL=79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js.map \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js.map b/crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js.map similarity index 100% rename from crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_3894a9.js.map rename to crates/turbopack-tests/tests/snapshot/swc_transforms/preset_env/output/79fb1_turbopack-tests_tests_snapshot_swc_transforms_preset_env_input_index_311eca.js.map