diff --git a/crates/turbopack-core/src/chunk/mod.rs b/crates/turbopack-core/src/chunk/mod.rs index 4b1fd67df36c32..120d98fb2286c4 100644 --- a/crates/turbopack-core/src/chunk/mod.rs +++ b/crates/turbopack-core/src/chunk/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod containment_tree; pub(crate) mod data; pub(crate) mod evaluate; pub mod optimize; +pub(crate) mod passthrough_asset; use std::{ collections::HashSet, @@ -32,6 +33,7 @@ pub use self::{ chunking_context::{ChunkingContext, ChunkingContextVc}, data::{ChunkData, ChunkDataOption, ChunkDataOptionVc, ChunkDataVc, ChunksData, ChunksDataVc}, evaluate::{EvaluatableAsset, EvaluatableAssetVc, EvaluatableAssets, EvaluatableAssetsVc}, + passthrough_asset::{PassthroughAsset, PassthroughAssetVc}, }; use crate::{ asset::{Asset, AssetVc, AssetsVc}, @@ -283,6 +285,9 @@ where #[derive(Eq, PartialEq, Clone, Hash)] enum ChunkContentGraphNode { + // An asset not placed in the current chunk, but whose references we will + // follow to find more graph nodes. + PassthroughAsset { asset: AssetVc }, // Chunk items that are placed into the current chunk ChunkItem { item: I, ident: StringReadRef }, // Asset that is already available and doesn't need to be included @@ -342,6 +347,11 @@ where } } + if PassthroughAssetVc::resolve_from(asset).await?.is_some() { + graph_nodes.push((None, ChunkContentGraphNode::PassthroughAsset { asset })); + continue; + } + let chunkable_asset = match ChunkableAssetVc::resolve_from(asset).await? { Some(chunkable_asset) => chunkable_asset, _ => { @@ -504,24 +514,20 @@ where } fn edges(&mut self, node: &ChunkContentGraphNode) -> Self::EdgesFuture { - let chunk_item = if let ChunkContentGraphNode::ChunkItem { - item: chunk_item, .. - } = node - { - Some(chunk_item.clone()) - } else { - None - }; + let node = node.clone(); let context = self.context; async move { - let Some(chunk_item) = chunk_item else { - return Ok(vec![].into_iter().flatten()); + let references = match node { + ChunkContentGraphNode::PassthroughAsset { asset } => asset.references(), + ChunkContentGraphNode::ChunkItem { item, .. } => item.references(), + _ => { + return Ok(vec![].into_iter().flatten()); + } }; - Ok(chunk_item - .references() + Ok(references .await? .into_iter() .map(|reference| reference_to_graph_nodes::(context, *reference)) @@ -599,7 +605,8 @@ where for graph_node in graph_nodes { match graph_node { - ChunkContentGraphNode::AvailableAsset(_asset) => {} + ChunkContentGraphNode::AvailableAsset(_) + | ChunkContentGraphNode::PassthroughAsset { .. } => {} ChunkContentGraphNode::ChunkItem { item, .. } => { chunk_items.push(item); } diff --git a/crates/turbopack-core/src/chunk/passthrough_asset.rs b/crates/turbopack-core/src/chunk/passthrough_asset.rs new file mode 100644 index 00000000000000..0cbb81f5bc2aae --- /dev/null +++ b/crates/turbopack-core/src/chunk/passthrough_asset.rs @@ -0,0 +1,6 @@ +use crate::asset::{Asset, AssetVc}; + +/// An [Asset] that should never be placed into a chunk, but whose references +/// should still be followed. +#[turbo_tasks::value_trait] +pub trait PassthroughAsset: Asset {} diff --git a/crates/turbopack-core/src/lib.rs b/crates/turbopack-core/src/lib.rs index 8bdf03d26ae0ae..604573fb586f43 100644 --- a/crates/turbopack-core/src/lib.rs +++ b/crates/turbopack-core/src/lib.rs @@ -17,7 +17,6 @@ pub mod ident; pub mod introspect; pub mod issue; pub mod package_json; -pub mod plugin; pub mod proxied_asset; pub mod reference; pub mod reference_type; diff --git a/crates/turbopack-core/src/reference_type.rs b/crates/turbopack-core/src/reference_type.rs index 7b3d39bd3cb769..4db243e549514b 100644 --- a/crates/turbopack-core/src/reference_type.rs +++ b/crates/turbopack-core/src/reference_type.rs @@ -45,6 +45,12 @@ pub enum EcmaScriptModulesReferenceSubType { pub enum CssReferenceSubType { AtImport, Compose, + /// Reference from any asset to a CSS-parseable asset. + /// + /// This marks the boundary between non-CSS and CSS assets. The Next.js App + /// Router implementation uses this to inject client references in-between + /// Global/Module CSS assets and the underlying CSS assets. + Internal, Custom(u8), Undefined, } diff --git a/crates/turbopack-css/src/asset.rs b/crates/turbopack-css/src/asset.rs index fc986785a21466..8a2e243c698b79 100644 --- a/crates/turbopack-css/src/asset.rs +++ b/crates/turbopack-css/src/asset.rs @@ -30,7 +30,9 @@ use crate::{ CssChunkPlaceable, CssChunkPlaceableVc, CssChunkVc, CssImport, }, code_gen::{CodeGenerateable, CodeGenerateableVc}, - parse::{parse, ParseResult, ParseResultSourceMap, ParseResultVc}, + parse::{ + parse_css, ParseCss, ParseCssResult, ParseCssResultSourceMap, ParseCssResultVc, ParseCssVc, + }, path_visitor::ApplyVisitors, references::{ analyze_css_stylesheet, compose::CssModuleComposeReferenceVc, @@ -48,47 +50,30 @@ fn modifier() -> StringVc { #[turbo_tasks::value] #[derive(Clone)] pub struct CssModuleAsset { - pub source: AssetVc, - pub context: AssetContextVc, - pub transforms: CssInputTransformsVc, - pub ty: CssModuleAssetType, + source: AssetVc, + context: AssetContextVc, + transforms: CssInputTransformsVc, + ty: CssModuleAssetType, } #[turbo_tasks::value_impl] impl CssModuleAssetVc { - /// Creates a new CSS asset. The CSS is treated as global CSS. - #[turbo_tasks::function] - pub fn new(source: AssetVc, context: AssetContextVc, transforms: CssInputTransformsVc) -> Self { - Self::cell(CssModuleAsset { - source, - context, - transforms, - ty: CssModuleAssetType::Global, - }) - } - - /// Creates a new CSS asset. The CSS is treated as CSS module. + /// Creates a new CSS asset. #[turbo_tasks::function] - pub fn new_module( + pub fn new( source: AssetVc, context: AssetContextVc, transforms: CssInputTransformsVc, + ty: CssModuleAssetType, ) -> Self { Self::cell(CssModuleAsset { source, context, transforms, - ty: CssModuleAssetType::Module, + ty, }) } - /// Returns the parsed css. - #[turbo_tasks::function] - pub(crate) async fn parse(self) -> Result { - let this = self.await?; - Ok(parse(this.source, Value::new(this.ty), this.transforms)) - } - /// Retrns the asset ident of the source without the "css" modifier #[turbo_tasks::function] pub async fn source_ident(self) -> Result { @@ -96,6 +81,14 @@ impl CssModuleAssetVc { } } +#[turbo_tasks::value_impl] +impl ParseCss for CssModuleAsset { + #[turbo_tasks::function] + fn parse_css(&self) -> ParseCssResultVc { + parse_css(self.source, self.ty, self.transforms) + } +} + #[turbo_tasks::value_impl] impl Asset for CssModuleAsset { #[turbo_tasks::function] @@ -115,7 +108,7 @@ impl Asset for CssModuleAsset { Ok(analyze_css_stylesheet( this.source, self_vc.as_resolve_origin(), - Value::new(this.ty), + this.ty, this.transforms, )) } @@ -137,7 +130,7 @@ impl ChunkableAsset for CssModuleAsset { impl CssChunkPlaceable for CssModuleAsset { #[turbo_tasks::function] fn as_chunk_item(self_vc: CssModuleAssetVc, context: ChunkingContextVc) -> CssChunkItemVc { - ModuleChunkItemVc::cell(ModuleChunkItem { + CssModuleChunkItemVc::cell(CssModuleChunkItem { module: self_vc, context, }) @@ -159,13 +152,13 @@ impl ResolveOrigin for CssModuleAsset { } #[turbo_tasks::value] -struct ModuleChunkItem { +struct CssModuleChunkItem { module: CssModuleAssetVc, context: ChunkingContextVc, } #[turbo_tasks::value_impl] -impl ChunkItem for ModuleChunkItem { +impl ChunkItem for CssModuleChunkItem { #[turbo_tasks::function] fn asset_ident(&self) -> AssetIdentVc { self.module.ident() @@ -178,7 +171,7 @@ impl ChunkItem for ModuleChunkItem { } #[turbo_tasks::value_impl] -impl CssChunkItem for ModuleChunkItem { +impl CssChunkItem for CssModuleChunkItem { #[turbo_tasks::function] async fn content(&self) -> Result { let references = &*self.module.references().await?; @@ -236,9 +229,9 @@ impl CssChunkItem for ModuleChunkItem { } } - let parsed = self.module.parse().await?; + let parsed = self.module.parse_css().await?; - if let ParseResult::Ok { + if let ParseCssResult::Ok { stylesheet, source_map, .. @@ -280,7 +273,7 @@ impl CssChunkItem for ModuleChunkItem { code_gen.emit(&stylesheet)?; - let srcmap = ParseResultSourceMap::new(source_map.clone(), srcmap).cell(); + let srcmap = ParseCssResultSourceMap::new(source_map.clone(), srcmap).cell(); Ok(CssChunkItemContent { inner_code: code_string.into(), diff --git a/crates/turbopack-css/src/chunk/mod.rs b/crates/turbopack-css/src/chunk/mod.rs index 3a2292472c0553..483f767461e0d8 100644 --- a/crates/turbopack-css/src/chunk/mod.rs +++ b/crates/turbopack-css/src/chunk/mod.rs @@ -35,7 +35,7 @@ use self::{ }; use crate::{ embed::{CssEmbed, CssEmbeddable, CssEmbeddableVc}, - parse::ParseResultSourceMapVc, + parse::ParseCssResultSourceMapVc, util::stringify_js, ImportAssetReferenceVc, }; @@ -485,7 +485,7 @@ pub enum CssImport { pub struct CssChunkItemContent { pub inner_code: Rope, pub imports: Vec, - pub source_map: Option, + pub source_map: Option, } #[turbo_tasks::value_trait] diff --git a/crates/turbopack-css/src/global_asset.rs b/crates/turbopack-css/src/global_asset.rs new file mode 100644 index 00000000000000..f14e4fb89b4e14 --- /dev/null +++ b/crates/turbopack-css/src/global_asset.rs @@ -0,0 +1,65 @@ +use anyhow::{bail, Result}; +use turbo_tasks::{primitives::StringVc, Value}; +use turbopack_core::{ + asset::{Asset, AssetContentVc, AssetVc}, + chunk::{PassthroughAsset, PassthroughAssetVc}, + context::{AssetContext, AssetContextVc}, + ident::AssetIdentVc, + reference::AssetReferencesVc, + reference_type::{CssReferenceSubType, ReferenceType}, +}; + +use crate::references::internal::InternalCssAssetReferenceVc; + +#[turbo_tasks::value] +#[derive(Clone)] +pub struct GlobalCssAsset { + source: AssetVc, + inner: AssetVc, +} + +#[turbo_tasks::value_impl] +impl GlobalCssAssetVc { + /// Creates a new CSS asset. The CSS is treated as global CSS. + #[turbo_tasks::function] + pub fn new(source: AssetVc, context: AssetContextVc) -> Self { + Self::cell(GlobalCssAsset { + source, + // The underlying CSS is processed through an internal CSS reference. + // This can then be picked up by other rules to treat CSS assets in + // a special way. For instance, in the Next App Router implementation, + // RSC CSS assets will be added to the client references manifest. + inner: context.process( + source, + Value::new(ReferenceType::Css(CssReferenceSubType::Internal)), + ), + }) + } +} + +#[turbo_tasks::value_impl] +impl Asset for GlobalCssAsset { + #[turbo_tasks::function] + fn ident(&self) -> AssetIdentVc { + self.source.ident().with_modifier(modifier()) + } + + #[turbo_tasks::function] + fn content(&self) -> Result { + bail!("CSS global asset has no contents") + } + + #[turbo_tasks::function] + fn references(&self) -> AssetReferencesVc { + AssetReferencesVc::cell(vec![InternalCssAssetReferenceVc::new(self.inner).into()]) + } +} + +#[turbo_tasks::function] +fn modifier() -> StringVc { + StringVc::cell("global css".to_string()) +} + +/// A GlobalAsset is a transparent wrapper around an actual CSS asset. +#[turbo_tasks::value_impl] +impl PassthroughAsset for GlobalCssAsset {} diff --git a/crates/turbopack-css/src/lib.rs b/crates/turbopack-css/src/lib.rs index 7c18bc012cbb6a..b6e067c3b18c3c 100644 --- a/crates/turbopack-css/src/lib.rs +++ b/crates/turbopack-css/src/lib.rs @@ -7,6 +7,7 @@ mod asset; pub mod chunk; mod code_gen; pub mod embed; +mod global_asset; mod module_asset; pub(crate) mod parse; mod path_visitor; @@ -16,15 +17,35 @@ pub(crate) mod util; use anyhow::Result; pub use asset::CssModuleAssetVc; -pub use module_asset::ModuleCssModuleAssetVc; +pub use global_asset::GlobalCssAssetVc; +pub use module_asset::ModuleCssAssetVc; +pub use parse::{ParseCss, ParseCssResult, ParseCssResultVc, ParseCssVc}; +use serde::{Deserialize, Serialize}; pub use transform::{CssInputTransform, CssInputTransformsVc}; +use turbo_tasks::{trace::TraceRawVcs, TaskInput}; use crate::references::import::ImportAssetReferenceVc; -#[turbo_tasks::value(serialization = "auto_for_input")] -#[derive(PartialOrd, Ord, Hash, Debug, Copy, Clone)] +#[derive( + PartialOrd, + Ord, + Eq, + PartialEq, + Hash, + Debug, + Copy, + Clone, + Default, + Serialize, + Deserialize, + TaskInput, + TraceRawVcs, +)] pub enum CssModuleAssetType { - Global, + /// Default parsing mode. + #[default] + Default, + /// The CSS is parsed as CSS modules. Module, } diff --git a/crates/turbopack-css/src/module_asset.rs b/crates/turbopack-css/src/module_asset.rs index 93bc66b34fa494..2a08da164c86df 100644 --- a/crates/turbopack-css/src/module_asset.rs +++ b/crates/turbopack-css/src/module_asset.rs @@ -1,29 +1,28 @@ -use std::{fmt::Write, sync::Arc}; +use std::{fmt::Write, iter::once, sync::Arc}; -use anyhow::Result; +use anyhow::{bail, Result}; use indexmap::IndexMap; use indoc::formatdoc; use swc_core::{ common::{BytePos, FileName, LineCol, SourceMap}, css::modules::CssClassName, }; -use turbo_tasks::{primitives::StringVc, Value, ValueToString, ValueToStringVc}; +use turbo_tasks::{primitives::StringVc, Value, ValueToString}; use turbo_tasks_fs::FileSystemPathVc; use turbopack_core::{ asset::{Asset, AssetContentVc, AssetVc}, chunk::{ availability_info::AvailabilityInfo, ChunkItem, ChunkItemVc, ChunkVc, ChunkableAsset, - ChunkableAssetReference, ChunkableAssetReferenceVc, ChunkableAssetVc, ChunkingContextVc, - ChunkingType, ChunkingTypeOptionVc, + ChunkableAssetVc, ChunkingContextVc, }, - context::AssetContextVc, + context::{AssetContext, AssetContextVc}, ident::AssetIdentVc, issue::{Issue, IssueSeverity, IssueSeverityVc, IssueVc}, - reference::{AssetReference, AssetReferenceVc, AssetReferencesVc}, + reference::{AssetReference, AssetReferencesVc}, + reference_type::{CssReferenceSubType, ReferenceType}, resolve::{ origin::{ResolveOrigin, ResolveOriginVc}, parse::RequestVc, - ResolveResult, ResolveResultVc, }, }; use turbopack_ecmascript::{ @@ -37,14 +36,8 @@ use turbopack_ecmascript::{ }; use crate::{ - chunk::{ - CssChunkItem, CssChunkItemContentVc, CssChunkItemVc, CssChunkPlaceable, - CssChunkPlaceableVc, CssChunkVc, - }, - parse::ParseResult, - references::compose::CssModuleComposeReferenceVc, - transform::CssInputTransformsVc, - CssModuleAssetVc, + parse::{ParseCss, ParseCssResult, ParseCssVc}, + references::{compose::CssModuleComposeReferenceVc, internal::InternalCssAssetReferenceVc}, }; #[turbo_tasks::function] @@ -54,41 +47,47 @@ fn modifier() -> StringVc { #[turbo_tasks::value] #[derive(Clone)] -pub struct ModuleCssModuleAsset { - pub inner: CssModuleAssetVc, +pub struct ModuleCssAsset { + pub inner: AssetVc, + pub context: AssetContextVc, } #[turbo_tasks::value_impl] -impl ModuleCssModuleAssetVc { +impl ModuleCssAssetVc { #[turbo_tasks::function] - pub fn new(source: AssetVc, context: AssetContextVc, transforms: CssInputTransformsVc) -> Self { - Self::cell(ModuleCssModuleAsset { - inner: CssModuleAssetVc::new_module(source, context, transforms), - }) + pub async fn new(source: AssetVc, context: AssetContextVc) -> Result { + let inner = context.process( + source, + Value::new(ReferenceType::Css(CssReferenceSubType::Internal)), + ); + + Ok(Self::cell(ModuleCssAsset { inner, context })) } } #[turbo_tasks::value_impl] -impl Asset for ModuleCssModuleAsset { +impl Asset for ModuleCssAsset { #[turbo_tasks::function] fn ident(&self) -> AssetIdentVc { - self.inner.source_ident().with_modifier(modifier()) + self.inner.ident().with_modifier(modifier()) } #[turbo_tasks::function] - fn content(&self) -> AssetContentVc { - self.inner.content() + fn content(&self) -> Result { + bail!("CSS module asset has no contents") } #[turbo_tasks::function] - 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()) + async fn references(self_vc: ModuleCssAssetVc) -> Result { + let this = self_vc.await?; + + // The inner 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 references = once(InternalCssAssetReferenceVc::new(this.inner).into()) + .chain(self_vc.module_references().await?.iter().copied()) .collect(); Ok(AssetReferencesVc::cell(references)) @@ -140,15 +139,20 @@ enum ModuleCssClass { struct ModuleCssClasses(IndexMap>); #[turbo_tasks::value_impl] -impl ModuleCssModuleAssetVc { +impl ModuleCssAssetVc { #[turbo_tasks::function] async fn classes(self) -> Result { let inner = self.await?.inner; - let parse_result = inner.parse().await?; + + let Some(inner) = ParseCssVc::resolve_from(inner).await? else { + bail!("inner asset should be CSS parseable"); + }; + + let parse_result = inner.parse_css().await?; let mut classes = IndexMap::default(); // TODO(alexkirsz) Should we report an error on parse error here? - if let ParseResult::Ok { exports, .. } = &*parse_result { + if let ParseCssResult::Ok { exports, .. } = &*parse_result { for (class_name, export_class_names) in exports { let mut export = Vec::default(); @@ -197,10 +201,10 @@ impl ModuleCssModuleAssetVc { } #[turbo_tasks::value_impl] -impl ChunkableAsset for ModuleCssModuleAsset { +impl ChunkableAsset for ModuleCssAsset { #[turbo_tasks::function] fn as_chunk( - self_vc: ModuleCssModuleAssetVc, + self_vc: ModuleCssAssetVc, context: ChunkingContextVc, availability_info: Value, ) -> ChunkVc { @@ -209,10 +213,10 @@ impl ChunkableAsset for ModuleCssModuleAsset { } #[turbo_tasks::value_impl] -impl EcmascriptChunkPlaceable for ModuleCssModuleAsset { +impl EcmascriptChunkPlaceable for ModuleCssAsset { #[turbo_tasks::function] fn as_chunk_item( - self_vc: ModuleCssModuleAssetVc, + self_vc: ModuleCssAssetVc, context: EcmascriptChunkingContextVc, ) -> EcmascriptChunkItemVc { ModuleChunkItem { @@ -230,7 +234,7 @@ impl EcmascriptChunkPlaceable for ModuleCssModuleAsset { } #[turbo_tasks::value_impl] -impl ResolveOrigin for ModuleCssModuleAsset { +impl ResolveOrigin for ModuleCssAsset { #[turbo_tasks::function] fn origin_path(&self) -> FileSystemPathVc { self.inner.ident().path() @@ -238,13 +242,13 @@ impl ResolveOrigin for ModuleCssModuleAsset { #[turbo_tasks::function] fn context(&self) -> AssetContextVc { - self.inner.context() + self.context } } #[turbo_tasks::value] struct ModuleChunkItem { - module: ModuleCssModuleAssetVc, + module: ModuleCssAssetVc, context: EcmascriptChunkingContextVc, } @@ -256,21 +260,8 @@ impl ChunkItem for ModuleChunkItem { } #[turbo_tasks::function] - 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, - } - .cell() - .into()]; - - references.extend(self.module.references().await?.iter().copied()); - - Ok(AssetReferencesVc::cell(references)) + fn references(&self) -> AssetReferencesVc { + self.module.references() } } @@ -311,7 +302,7 @@ impl EcmascriptChunkItem for ModuleChunkItem { continue; }; - let Some(css_module) = ModuleCssModuleAssetVc::resolve_from(resolved_module).await? else { + let Some(css_module) = ModuleCssAssetVc::resolve_from(resolved_module).await? else { CssModuleComposesIssue { severity: IssueSeverity::Error.cell(), source: self.module.ident(), @@ -328,9 +319,7 @@ impl EcmascriptChunkItem for ModuleChunkItem { // 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 placeable = css_module.as_ecmascript_chunk_placeable(); let module_id = placeable.as_chunk_item(self.context).id().await?; let module_id = StringifyJs(&*module_id); @@ -368,157 +357,6 @@ impl EcmascriptChunkItem for ModuleChunkItem { } } -#[turbo_tasks::value] -struct CssProxyToCssAssetReference { - module: ModuleCssModuleAssetVc, -} - -#[turbo_tasks::value_impl] -impl ValueToString for CssProxyToCssAssetReference { - #[turbo_tasks::function] - async fn to_string(&self) -> Result { - Ok(StringVc::cell(format!( - "proxy(css) {}", - self.module.ident().to_string().await?, - ))) - } -} - -#[turbo_tasks::value_impl] -impl AssetReference for CssProxyToCssAssetReference { - #[turbo_tasks::function] - fn resolve_reference(&self) -> ResolveResultVc { - ResolveResult::asset( - CssProxyModuleAsset { - module: self.module, - } - .cell() - .into(), - ) - .cell() - } -} - -#[turbo_tasks::value_impl] -impl ChunkableAssetReference for CssProxyToCssAssetReference { - #[turbo_tasks::function] - fn chunking_type(&self) -> ChunkingTypeOptionVc { - ChunkingTypeOptionVc::cell(Some(ChunkingType::Parallel)) - } -} - -/// 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 Asset for CssProxyModuleAsset { - #[turbo_tasks::function] - async fn ident(&self) -> Result { - Ok(self.module.await?.inner.ident().with_modifier(modifier())) - } - - #[turbo_tasks::function] - 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, - availability_info: Value, - ) -> ChunkVc { - CssChunkVc::new(context, self_vc.into(), availability_info).into() - } -} - -#[turbo_tasks::value_impl] -impl CssChunkPlaceable for CssProxyModuleAsset { - #[turbo_tasks::function] - fn as_chunk_item(self_vc: CssProxyModuleAssetVc, context: ChunkingContextVc) -> CssChunkItemVc { - CssProxyModuleChunkItemVc::cell(CssProxyModuleChunkItem { - inner: self_vc, - context, - }) - .into() - } -} - -#[turbo_tasks::value_impl] -impl ResolveOrigin for CssProxyModuleAsset { - #[turbo_tasks::function] - fn origin_path(&self) -> FileSystemPathVc { - self.module.ident().path() - } - - #[turbo_tasks::function] - fn context(&self) -> AssetContextVc { - self.module.context() - } -} - -#[turbo_tasks::value] -struct CssProxyModuleChunkItem { - inner: CssProxyModuleAssetVc, - context: ChunkingContextVc, -} - -#[turbo_tasks::value_impl] -impl ChunkItem for CssProxyModuleChunkItem { - #[turbo_tasks::function] - fn asset_ident(&self) -> AssetIdentVc { - self.inner.ident() - } - - #[turbo_tasks::function] - fn references(&self) -> AssetReferencesVc { - self.inner.references() - } -} - -#[turbo_tasks::value_impl] -impl CssChunkItem for CssProxyModuleChunkItem { - #[turbo_tasks::function] - async fn content(&self) -> Result { - Ok(self - .inner - .await? - .module - .await? - .inner - .as_chunk_item(self.context) - .content()) - } - - #[turbo_tasks::function] - fn chunking_context(&self) -> ChunkingContextVc { - self.context - } -} - fn generate_minimal_source_map(filename: String, source: String) -> ParseResultSourceMapVc { let mut mappings = vec![]; // Start from 1 because 0 is reserved for dummy spans in SWC. diff --git a/crates/turbopack-css/src/parse.rs b/crates/turbopack-css/src/parse.rs index e50fae880a1ec2..65e25682a08c6c 100644 --- a/crates/turbopack-css/src/parse.rs +++ b/crates/turbopack-css/src/parse.rs @@ -15,7 +15,7 @@ use swc_core::{ }, ecma::atoms::JsWord, }; -use turbo_tasks::{Value, ValueToString}; +use turbo_tasks::ValueToString; use turbo_tasks_fs::{FileContent, FileSystemPath}; use turbopack_core::{ asset::{Asset, AssetContent, AssetVc}, @@ -33,7 +33,7 @@ use crate::{ static BASENAME_RE: Lazy = Lazy::new(|| Regex::new(r"^[^.]*").unwrap()); #[turbo_tasks::value(shared, serialization = "none", eq = "manual")] -pub enum ParseResult { +pub enum ParseCssResult { Ok { #[turbo_tasks(trace_ignore)] stylesheet: Stylesheet, @@ -48,7 +48,7 @@ pub enum ParseResult { NotFound, } -impl PartialEq for ParseResult { +impl PartialEq for ParseCssResult { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Ok { .. }, Self::Ok { .. }) => false, @@ -58,7 +58,7 @@ impl PartialEq for ParseResult { } #[turbo_tasks::value(shared, serialization = "none", eq = "manual")] -pub struct ParseResultSourceMap { +pub struct ParseCssResultSourceMap { #[turbo_tasks(debug_ignore, trace_ignore)] source_map: Arc, @@ -68,15 +68,15 @@ pub struct ParseResultSourceMap { mappings: Vec<(BytePos, LineCol)>, } -impl PartialEq for ParseResultSourceMap { +impl PartialEq for ParseCssResultSourceMap { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.source_map, &other.source_map) && self.mappings == other.mappings } } -impl ParseResultSourceMap { +impl ParseCssResultSourceMap { pub fn new(source_map: Arc, mappings: Vec<(BytePos, LineCol)>) -> Self { - ParseResultSourceMap { + ParseCssResultSourceMap { source_map, mappings, } @@ -84,7 +84,7 @@ impl ParseResultSourceMap { } #[turbo_tasks::value_impl] -impl GenerateSourceMap for ParseResultSourceMap { +impl GenerateSourceMap for ParseCssResultSourceMap { #[turbo_tasks::function] fn generate_source_map(&self) -> OptionSourceMapVc { let map = self.source_map.build_source_map_with_config( @@ -117,21 +117,20 @@ impl SourceMapGenConfig for InlineSourcesContentConfig { } #[turbo_tasks::function] -pub async fn parse( +pub async fn parse_css( source: AssetVc, - ty: Value, + ty: CssModuleAssetType, transforms: CssInputTransformsVc, -) -> Result { +) -> Result { let content = source.content(); let fs_path = &*source.ident().path().await?; let ident_str = &*source.ident().to_string().await?; - let ty = ty.into_value(); Ok(match &*content.await? { - AssetContent::Redirect { .. } => ParseResult::Unparseable.cell(), + AssetContent::Redirect { .. } => ParseCssResult::Unparseable.cell(), AssetContent::File(file) => match &*file.await? { - FileContent::NotFound => ParseResult::NotFound.cell(), + FileContent::NotFound => ParseCssResult::NotFound.cell(), FileContent::Content(file) => match file.content().to_str() { - Err(_err) => ParseResult::Unparseable.cell(), + Err(_err) => ParseCssResult::Unparseable.cell(), Ok(string) => { let transforms = &*transforms.await?; parse_content( @@ -156,7 +155,7 @@ async fn parse_content( source: AssetVc, ty: CssModuleAssetType, transforms: &[CssInputTransform], -) -> Result { +) -> Result { let source_map: Arc = Default::default(); let handler = Handler::with_emitter( true, @@ -183,7 +182,7 @@ async fn parse_content( Err(e) => { // TODO report in in a stream e.to_diagnostics(&handler).emit(); - return Ok(ParseResult::Unparseable.into()); + return Ok(ParseCssResult::Unparseable.into()); } }; @@ -194,7 +193,7 @@ async fn parse_content( } if has_errors { - return Ok(ParseResult::Unparseable.into()); + return Ok(ParseCssResult::Unparseable.into()); } let context = TransformContext { @@ -205,7 +204,7 @@ async fn parse_content( } let (imports, exports) = match ty { - CssModuleAssetType::Global => Default::default(), + CssModuleAssetType::Default => Default::default(), CssModuleAssetType::Module => { let imports = swc_core::css::modules::imports::analyze_imports(&parsed_stylesheet); let basename = BASENAME_RE @@ -232,7 +231,7 @@ async fn parse_content( } }; - Ok(ParseResult::Ok { + Ok(ParseCssResult::Ok { stylesheet: parsed_stylesheet, source_map, imports, @@ -250,3 +249,10 @@ impl TransformConfig for ModuleTransformConfig { format!("{}{}", *local, self.suffix).into() } } + +/// Trait to be implemented by assets which can be parsed as CSS. +#[turbo_tasks::value_trait] +pub trait ParseCss { + /// Returns the parsed css. + fn parse_css(&self) -> ParseCssResultVc; +} diff --git a/crates/turbopack-css/src/references/internal.rs b/crates/turbopack-css/src/references/internal.rs new file mode 100644 index 00000000000000..c2ab171e45830b --- /dev/null +++ b/crates/turbopack-css/src/references/internal.rs @@ -0,0 +1,46 @@ +use anyhow::Result; +use turbo_tasks::{primitives::StringVc, ValueToString, ValueToStringVc}; +use turbopack_core::{ + asset::{Asset, AssetVc}, + chunk::{ChunkableAssetReference, ChunkableAssetReferenceVc}, + reference::{AssetReference, AssetReferenceVc}, + resolve::{ResolveResult, ResolveResultVc}, +}; + +/// A reference to an internal CSS asset. +#[turbo_tasks::value] +#[derive(Hash, Debug)] +pub struct InternalCssAssetReference { + asset: AssetVc, +} + +#[turbo_tasks::value_impl] +impl InternalCssAssetReferenceVc { + /// Creates a new [`InternalCssAssetReferenceVc`]. + #[turbo_tasks::function] + pub fn new(asset: AssetVc) -> Self { + Self::cell(InternalCssAssetReference { asset }) + } +} + +#[turbo_tasks::value_impl] +impl AssetReference for InternalCssAssetReference { + #[turbo_tasks::function] + fn resolve_reference(&self) -> ResolveResultVc { + ResolveResult::asset(self.asset).cell() + } +} + +#[turbo_tasks::value_impl] +impl ValueToString for InternalCssAssetReference { + #[turbo_tasks::function] + async fn to_string(&self) -> Result { + Ok(StringVc::cell(format!( + "internal css {}", + self.asset.ident().to_string().await? + ))) + } +} + +#[turbo_tasks::value_impl] +impl ChunkableAssetReference for InternalCssAssetReference {} diff --git a/crates/turbopack-css/src/references/mod.rs b/crates/turbopack-css/src/references/mod.rs index 110817577619bc..542f32508fc3ef 100644 --- a/crates/turbopack-css/src/references/mod.rs +++ b/crates/turbopack-css/src/references/mod.rs @@ -26,7 +26,7 @@ use turbopack_core::{ use turbopack_swc_utils::emitter::IssueEmitter; use crate::{ - parse::{parse, ParseResult}, + parse::{parse_css, ParseCssResult}, references::{ import::{ImportAssetReferenceVc, ImportAttributes}, url::UrlAssetReferenceVc, @@ -36,20 +36,21 @@ use crate::{ pub(crate) mod compose; pub(crate) mod import; +pub(crate) mod internal; pub(crate) mod url; #[turbo_tasks::function] pub async fn analyze_css_stylesheet( source: AssetVc, origin: ResolveOriginVc, - ty: Value, + ty: CssModuleAssetType, transforms: CssInputTransformsVc, ) -> Result { let mut references = Vec::new(); - let parsed = parse(source, ty, transforms).await?; + let parsed = parse_css(source, ty, transforms).await?; - if let ParseResult::Ok { + if let ParseCssResult::Ok { stylesheet, source_map, .. diff --git a/crates/turbopack/src/lib.rs b/crates/turbopack/src/lib.rs index 57db670a0940e3..0077ab6d890699 100644 --- a/crates/turbopack/src/lib.rs +++ b/crates/turbopack/src/lib.rs @@ -13,7 +13,7 @@ use std::{ }; use anyhow::Result; -use css::{CssModuleAssetVc, ModuleCssModuleAssetVc}; +use css::{CssModuleAssetVc, GlobalCssAssetVc, ModuleCssAssetVc}; use ecmascript::{ typescript::resolve::TypescriptTypesAssetReferenceVc, EcmascriptModuleAssetType, EcmascriptModuleAssetVc, @@ -34,7 +34,6 @@ use turbopack_core::{ context::{AssetContext, AssetContextVc}, ident::AssetIdentVc, issue::{Issue, IssueVc}, - plugin::CustomModuleType, reference::all_referenced_assets, reference_type::{EcmaScriptModulesReferenceSubType, InnerAssetsVc, ReferenceType}, resolve::{ @@ -62,6 +61,7 @@ use turbopack_mdx::MdxModuleAssetVc; use turbopack_static::StaticModuleAssetVc; use self::{ + module_options::CustomModuleType, resolve_options_context::ResolveOptionsContextVc, transition::{TransitionVc, TransitionsByNameVc}, }; @@ -164,14 +164,12 @@ async fn apply_module_type( builder.build() } - ModuleType::Json => JsonModuleAssetVc::new(source).into(), ModuleType::Raw => source, - ModuleType::Css(transforms) => { - CssModuleAssetVc::new(source, context.into(), *transforms).into() - } - ModuleType::CssModule(transforms) => { - ModuleCssModuleAssetVc::new(source, context.into(), *transforms).into() + ModuleType::CssGlobal => GlobalCssAssetVc::new(source, context.into()).into(), + ModuleType::CssModule => ModuleCssAssetVc::new(source, context.into()).into(), + ModuleType::Css { ty, transforms } => { + CssModuleAssetVc::new(source, context.into(), *transforms, *ty).into() } ModuleType::Static => StaticModuleAssetVc::new(source, context.into()).into(), ModuleType::Mdx { diff --git a/crates/turbopack-core/src/plugin.rs b/crates/turbopack/src/module_options/custom_module_type.rs similarity index 56% rename from crates/turbopack-core/src/plugin.rs rename to crates/turbopack/src/module_options/custom_module_type.rs index 352fd58bd6fbb8..cdd67907d5d491 100644 --- a/crates/turbopack-core/src/plugin.rs +++ b/crates/turbopack/src/module_options/custom_module_type.rs @@ -1,11 +1,13 @@ -use crate::{asset::AssetVc, context::AssetContextVc, resolve::ModulePartVc}; +use turbopack_core::{asset::AssetVc, resolve::ModulePartVc}; + +use crate::ModuleAssetContextVc; #[turbo_tasks::value_trait] pub trait CustomModuleType { fn create_module( &self, source: AssetVc, - context: AssetContextVc, + context: ModuleAssetContextVc, part: Option, ) -> AssetVc; } diff --git a/crates/turbopack/src/module_options/mod.rs b/crates/turbopack/src/module_options/mod.rs index 5ac0d264cd3d34..b3811b7208ec68 100644 --- a/crates/turbopack/src/module_options/mod.rs +++ b/crates/turbopack/src/module_options/mod.rs @@ -1,19 +1,21 @@ +pub(crate) mod custom_module_type; pub mod module_options_context; pub mod module_rule; pub mod rule_condition; use anyhow::{Context, Result}; +pub use custom_module_type::{CustomModuleType, CustomModuleTypeVc}; pub use module_options_context::*; pub use module_rule::*; pub use rule_condition::*; use turbo_tasks::primitives::OptionStringVc; use turbo_tasks_fs::{glob::GlobVc, FileSystemPathVc}; use turbopack_core::{ - reference_type::{ReferenceType, UrlReferenceSubType}, + reference_type::{CssReferenceSubType, ReferenceType, UrlReferenceSubType}, resolve::options::{ImportMap, ImportMapVc, ImportMapping, ImportMappingVc}, source_transform::SourceTransformsVc, }; -use turbopack_css::{CssInputTransform, CssInputTransformsVc}; +use turbopack_css::{CssInputTransform, CssInputTransformsVc, CssModuleAssetType}; use turbopack_ecmascript::{ EcmascriptInputTransform, EcmascriptInputTransformsVc, EcmascriptOptions, SpecifiedModuleType, }; @@ -223,7 +225,12 @@ impl ModuleOptionsVc { vec![ModuleRuleEffect::ModuleType(ModuleType::Json)], ), ModuleRule::new( - ModuleRuleCondition::ResourcePathEndsWith(".css".to_string()), + ModuleRuleCondition::all(vec![ + ModuleRuleCondition::ResourcePathEndsWith(".css".to_string()), + ModuleRuleCondition::not(ModuleRuleCondition::ReferenceType( + ReferenceType::Css(CssReferenceSubType::Internal), + )), + ]), [ if let Some(options) = enable_postcss_transform { let execution_context = execution_context @@ -249,19 +256,44 @@ impl ModuleOptionsVc { } else { None }, - Some(ModuleRuleEffect::ModuleType(ModuleType::Css( - css_transforms, - ))), + Some(ModuleRuleEffect::ModuleType(ModuleType::CssGlobal)), ] .into_iter() .flatten() .collect(), ), ModuleRule::new( - ModuleRuleCondition::ResourcePathEndsWith(".module.css".to_string()), - vec![ModuleRuleEffect::ModuleType(ModuleType::CssModule( - css_transforms, - ))], + ModuleRuleCondition::all(vec![ + ModuleRuleCondition::ResourcePathEndsWith(".module.css".to_string()), + ModuleRuleCondition::not(ModuleRuleCondition::ReferenceType( + ReferenceType::Css(CssReferenceSubType::Internal), + )), + ]), + vec![ModuleRuleEffect::ModuleType(ModuleType::CssModule)], + ), + ModuleRule::new( + ModuleRuleCondition::all(vec![ + ModuleRuleCondition::ResourcePathEndsWith(".css".to_string()), + ModuleRuleCondition::ReferenceType(ReferenceType::Css( + CssReferenceSubType::Internal, + )), + ]), + vec![ModuleRuleEffect::ModuleType(ModuleType::Css { + ty: CssModuleAssetType::Default, + transforms: css_transforms, + })], + ), + ModuleRule::new( + ModuleRuleCondition::all(vec![ + ModuleRuleCondition::ResourcePathEndsWith(".module.css".to_string()), + ModuleRuleCondition::ReferenceType(ReferenceType::Css( + CssReferenceSubType::Internal, + )), + ]), + vec![ModuleRuleEffect::ModuleType(ModuleType::Css { + ty: CssModuleAssetType::Module, + transforms: css_transforms, + })], ), ModuleRule::new( ModuleRuleCondition::any(vec![ diff --git a/crates/turbopack/src/module_options/module_rule.rs b/crates/turbopack/src/module_options/module_rule.rs index fc7223dd0ed5cb..2b97d8ff0e9dcf 100644 --- a/crates/turbopack/src/module_options/module_rule.rs +++ b/crates/turbopack/src/module_options/module_rule.rs @@ -3,14 +3,13 @@ use serde::{Deserialize, Serialize}; use turbo_tasks::trace::TraceRawVcs; use turbo_tasks_fs::FileSystemPath; use turbopack_core::{ - asset::AssetVc, plugin::CustomModuleTypeVc, reference_type::ReferenceType, - source_transform::SourceTransformsVc, + asset::AssetVc, reference_type::ReferenceType, source_transform::SourceTransformsVc, }; -use turbopack_css::CssInputTransformsVc; +use turbopack_css::{CssInputTransformsVc, CssModuleAssetType}; use turbopack_ecmascript::{EcmascriptInputTransformsVc, EcmascriptOptions}; use turbopack_mdx::MdxTransformOptionsVc; -use super::ModuleRuleCondition; +use super::{CustomModuleTypeVc, ModuleRuleCondition}; #[derive(Debug, Clone, Serialize, Deserialize, TraceRawVcs, PartialEq, Eq)] pub struct ModuleRule { @@ -74,8 +73,12 @@ pub enum ModuleType { transforms: EcmascriptInputTransformsVc, options: MdxTransformOptionsVc, }, - Css(CssInputTransformsVc), - CssModule(CssInputTransformsVc), + CssGlobal, + CssModule, + Css { + ty: CssModuleAssetType, + transforms: CssInputTransformsVc, + }, Static, Custom(CustomModuleTypeVc), }