Skip to main content

turbopack_core/module_graph/
style_groups.rs

1//! Algorithm-neutral output types for the style-chunking pipeline.
2//!
3//! [`StyleGroups`] is the cell type both algorithms — the default ("loose") one in
4//! [`super::style_groups_loose`] and the graph-based one in [`super::style_groups_graph`] —
5//! produce. Living here means neither algorithm has to import from the other.
6
7use bincode::{Decode, Encode};
8use turbo_tasks::{
9    FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, trace::TraceRawVcs,
10};
11
12use crate::chunk::{ChunkItemBatchWithAsyncModuleInfo, ChunkItemWithAsyncModuleInfo};
13
14/// Wrapper around an `f32` that implements [`TaskInput`] (and the other derives the
15/// [`StyleGroupsAlgorithm`] enum needs) by going through the IEEE-754 bit pattern. Use
16/// [`F32TaskInput::get`] / [`F32TaskInput::from`] at the boundary; do not match on the inner
17/// `u32` directly.
18#[derive(
19    TaskInput,
20    Debug,
21    Clone,
22    Copy,
23    PartialEq,
24    Eq,
25    Hash,
26    NonLocalValue,
27    OperationValue,
28    TraceRawVcs,
29    Encode,
30    Decode,
31)]
32pub struct F32TaskInput(u32);
33
34impl F32TaskInput {
35    pub const fn from(value: f32) -> Self {
36        Self(value.to_bits())
37    }
38    pub const fn get(self) -> f32 {
39        f32::from_bits(self.0)
40    }
41}
42
43/// Selects the algorithm used to compute [`StyleGroups`].
44#[turbo_tasks::value(shared, operation)]
45#[derive(Clone, Debug, Default, Hash, TaskInput)]
46pub enum StyleGroupsAlgorithm {
47    /// Default ("loose") algorithm, see
48    /// [`crate::module_graph::style_groups_loose::compute_style_groups`].
49    #[default]
50    Default,
51    /// Graph-analysis based algorithm, see
52    /// [`crate::module_graph::style_groups_graph::compute_style_groups_graph`].
53    Graph {
54        /// See `experimental.cssChunking.requestCost` in Next.js.
55        request_cost: F32TaskInput,
56        /// See `experimental.cssChunking.moduleFactorCost` in Next.js.
57        module_factor_cost: F32TaskInput,
58    },
59}
60
61impl StyleGroupsAlgorithm {
62    /// Build a [`StyleGroupsAlgorithm::Graph`] variant from real `f32` cost parameters.
63    pub fn graph(request_cost: f32, module_factor_cost: f32) -> Self {
64        Self::Graph {
65            request_cost: F32TaskInput::from(request_cost),
66            module_factor_cost: F32TaskInput::from(module_factor_cost),
67        }
68    }
69}
70
71#[derive(
72    TaskInput, Debug, Clone, PartialEq, Eq, Hash, NonLocalValue, TraceRawVcs, Encode, Decode,
73)]
74pub struct StyleGroupsConfig {
75    pub max_chunk_size: usize,
76    pub algorithm: StyleGroupsAlgorithm,
77}
78
79/// Per-item metadata produced by the style chunking algorithms.
80#[derive(
81    Debug, Clone, PartialEq, Eq, Hash, NonLocalValue, TraceRawVcs, Encode, Decode, TaskInput,
82)]
83pub struct StyleItemInfo {
84    /// Stable sort key applied by the production-chunking pass when ordering chunks within a chunk
85    /// group. The loose algorithm produces all `None` orders and relies on input order; the graph
86    /// algorithm produces all `Some(_)` orders (including for singletons). Mixing `Some` and
87    /// `None` within a single [`StyleGroups`] result is not produced in practice — the
88    /// production-chunking sort treats `None` as a sort key that is less than any `Some(_)`, but
89    /// this branch is only exercised in the all-`None` (loose) case.
90    pub order: Option<u32>,
91    /// `Some(batch)` when this chunk item shares its emitted chunk with other items. `None` for
92    /// items that end up alone in their own chunk under the graph algorithm.
93    pub batch: Option<ResolvedVc<ChunkItemBatchWithAsyncModuleInfo>>,
94}
95
96/// Styling must not be duplicated in the application. The simplest way to achieve this is to put
97/// every styling chunk item into a separate chunk. That works, but isn't efficient since it would
98/// cause a lot of requests. Instead, multiple chunk items are grouped together and placed in a
99/// single shared chunk. `StyleGroups` specifies how chunk items are grouped together.
100#[turbo_tasks::value(shared)]
101pub struct StyleGroups {
102    /// Per-item info keyed by chunk item.
103    ///
104    /// The loose algorithm only inserts items it actively grouped into shared chunks (everything
105    /// else is implicitly emitted as a singleton chunk preserving input order). The graph
106    /// algorithm inserts every input item — including singletons — so its result fully determines
107    /// the final per-chunk-group ordering through `StyleItemInfo::order`.
108    #[bincode(with = "turbo_bincode::indexmap")]
109    pub shared_chunk_items: FxIndexMap<ChunkItemWithAsyncModuleInfo, StyleItemInfo>,
110}
111
112/// Constructor for [`StyleGroups`] that's accessible from both algorithm modules without
113/// forcing the cell visibility wider.
114pub(super) fn make_style_groups(
115    shared_chunk_items: FxIndexMap<ChunkItemWithAsyncModuleInfo, StyleItemInfo>,
116) -> Vc<StyleGroups> {
117    StyleGroups { shared_chunk_items }.cell()
118}