Skip to content

Commit

Permalink
Add support for CSS client references
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkirsz committed Jun 27, 2023
1 parent 12903ee commit 81d7fbf
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 319 deletions.
33 changes: 20 additions & 13 deletions crates/turbopack-core/src/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -283,6 +285,9 @@ where

#[derive(Eq, PartialEq, Clone, Hash)]
enum ChunkContentGraphNode<I> {
// 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
Expand Down Expand Up @@ -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,
_ => {
Expand Down Expand Up @@ -504,24 +514,20 @@ where
}

fn edges(&mut self, node: &ChunkContentGraphNode<I>) -> 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::<I>(context, *reference))
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 6 additions & 0 deletions crates/turbopack-core/src/chunk/passthrough_asset.rs
Original file line number Diff line number Diff line change
@@ -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 {}
1 change: 0 additions & 1 deletion crates/turbopack-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions crates/turbopack-core/src/reference_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
61 changes: 27 additions & 34 deletions crates/turbopack-css/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -48,54 +50,45 @@ 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<ParseResultVc> {
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<AssetIdentVc> {
Ok(self.await?.source.ident())
}
}

#[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]
Expand All @@ -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,
))
}
Expand All @@ -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,
})
Expand All @@ -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()
Expand All @@ -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<CssChunkItemContentVc> {
let references = &*self.module.references().await?;
Expand Down Expand Up @@ -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,
..
Expand Down Expand Up @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions crates/turbopack-css/src/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use self::{
};
use crate::{
embed::{CssEmbed, CssEmbeddable, CssEmbeddableVc},
parse::ParseResultSourceMapVc,
parse::ParseCssResultSourceMapVc,
util::stringify_js,
ImportAssetReferenceVc,
};
Expand Down Expand Up @@ -485,7 +485,7 @@ pub enum CssImport {
pub struct CssChunkItemContent {
pub inner_code: Rope,
pub imports: Vec<CssImport>,
pub source_map: Option<ParseResultSourceMapVc>,
pub source_map: Option<ParseCssResultSourceMapVc>,
}

#[turbo_tasks::value_trait]
Expand Down
65 changes: 65 additions & 0 deletions crates/turbopack-css/src/global_asset.rs
Original file line number Diff line number Diff line change
@@ -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<AssetContentVc> {
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 {}
29 changes: 25 additions & 4 deletions crates/turbopack-css/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
}

Expand Down
Loading

0 comments on commit 81d7fbf

Please sign in to comment.