1pub mod availability_info;
2pub mod available_modules;
3pub mod chunk_group;
4pub mod chunk_id_strategy;
5pub(crate) mod chunk_item_batch;
6pub mod chunking;
7pub(crate) mod chunking_context;
8pub(crate) mod data;
9pub(crate) mod evaluate;
10
11use std::fmt::Display;
12
13use anyhow::{Result, bail};
14use auto_hash_map::AutoSet;
15use bincode::{Decode, Encode};
16use serde::{Deserialize, Serialize};
17use turbo_rcstr::RcStr;
18use turbo_tasks::{
19 FxIndexSet, NonLocalValue, ResolvedVc, TaskInput, Upcast, ValueToString, Vc,
20 debug::ValueDebugFormat, trace::TraceRawVcs,
21};
22use turbo_tasks_hash::DeterministicHash;
23
24pub use crate::chunk::{
25 chunk_item_batch::{
26 ChunkItemBatchGroup, ChunkItemBatchWithAsyncModuleInfo,
27 ChunkItemOrBatchWithAsyncModuleInfo, batch_info,
28 },
29 chunking_context::{
30 AssetSuffix, ChunkGroupResult, ChunkGroupType, ChunkingConfig, ChunkingConfigs,
31 ChunkingContext, ChunkingContextExt, EntryChunkGroupResult, MangleType, MinifyType,
32 SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
33 },
34 data::{ChunkData, ChunkDataOption, ChunksData},
35 evaluate::{EvaluatableAsset, EvaluatableAssetExt, EvaluatableAssets},
36};
37use crate::{
38 asset::Asset,
39 chunk::availability_info::AvailabilityInfo,
40 ident::AssetIdent,
41 module::Module,
42 module_graph::{
43 ModuleGraph,
44 module_batch::{ChunkableModuleOrBatch, ModuleBatchGroup},
45 },
46 output::{OutputAssets, OutputAssetsReference},
47};
48
49#[derive(
50 Debug,
51 TaskInput,
52 Clone,
53 Copy,
54 PartialEq,
55 Eq,
56 Hash,
57 TraceRawVcs,
58 DeterministicHash,
59 NonLocalValue,
60 Encode,
61 Decode,
62)]
63pub enum ContentHashing {
64 Direct {
68 length: u8,
71 },
72}
73
74#[turbo_tasks::value(shared)]
75#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize, TaskInput)]
76#[serde(rename_all = "kebab-case")]
77pub enum CrossOrigin {
78 #[default]
79 None,
80 Anonymous,
81 UseCredentials,
82}
83
84impl CrossOrigin {
85 pub fn as_str(self) -> Option<&'static str> {
86 match self {
87 Self::None => None,
88 Self::Anonymous => Some("anonymous"),
89 Self::UseCredentials => Some("use-credentials"),
90 }
91 }
92}
93
94impl TryFrom<Option<&str>> for CrossOrigin {
95 type Error = anyhow::Error;
96
97 fn try_from(value: Option<&str>) -> Result<Self> {
98 match value {
99 None => Ok(Self::None),
100 Some("anonymous") => Ok(Self::Anonymous),
101 Some("use-credentials") => Ok(Self::UseCredentials),
102 Some(value) => bail!(
103 "invalid crossOrigin value `{value}`; supported values are `anonymous` and \
104 `use-credentials`"
105 ),
106 }
107 }
108}
109
110#[turbo_tasks::value(shared, operation)]
112#[derive(Debug, Clone, Hash, Ord, PartialOrd, DeterministicHash, Serialize, ValueToString)]
113#[serde(untagged)]
114pub enum ModuleId {
115 Number(u64),
116 String(RcStr),
117}
118
119impl Display for ModuleId {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 ModuleId::Number(i) => write!(f, "{i}"),
123 ModuleId::String(s) => write!(f, "{s}"),
124 }
125 }
126}
127
128impl ModuleId {
129 pub fn parse(id: &str) -> Result<ModuleId> {
130 Ok(match id.parse::<u64>() {
131 Ok(i) => ModuleId::Number(i),
132 Err(_) => ModuleId::String(id.into()),
133 })
134 }
135}
136
137#[turbo_tasks::value(transparent, shared)]
139pub struct ModuleIds(Vec<ModuleId>);
140
141#[turbo_tasks::value_trait]
143pub trait ChunkableModule: Module {
144 #[turbo_tasks::function]
145 fn as_chunk_item(
146 self: Vc<Self>,
147 module_graph: Vc<ModuleGraph>,
148 chunking_context: Vc<Box<dyn ChunkingContext>>,
149 ) -> Vc<Box<dyn ChunkItem>>;
150}
151
152#[turbo_tasks::value_trait]
157pub trait MergeableModule: Module {
158 #[turbo_tasks::function]
161 fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
162 Vc::cell(true)
163 }
164
165 #[turbo_tasks::function]
171 fn merge(
172 self: Vc<Self>,
173 modules: Vc<MergeableModulesExposed>,
174 entry_points: Vc<MergeableModules>,
175 ) -> Vc<Box<dyn ChunkableModule>>;
176}
177#[turbo_tasks::value(transparent)]
178pub struct MergeableModules(Vec<ResolvedVc<Box<dyn MergeableModule>>>);
179
180#[turbo_tasks::value_impl]
181impl MergeableModules {
182 #[turbo_tasks::function]
183 pub fn interned(modules: Vec<ResolvedVc<Box<dyn MergeableModule>>>) -> Vc<Self> {
184 Vc::cell(modules)
185 }
186}
187
188#[derive(
190 Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, TaskInput, Hash, Encode, Decode,
191)]
192pub enum MergeableModuleExposure {
193 None,
196 Internal,
199 External,
202}
203
204#[turbo_tasks::value(transparent)]
205pub struct MergeableModulesExposed(
206 Vec<(
207 ResolvedVc<Box<dyn MergeableModule>>,
208 MergeableModuleExposure,
209 )>,
210);
211
212#[turbo_tasks::value_impl]
213impl MergeableModulesExposed {
214 #[turbo_tasks::function]
215 pub fn interned(
216 modules: Vec<(
217 ResolvedVc<Box<dyn MergeableModule>>,
218 MergeableModuleExposure,
219 )>,
220 ) -> Vc<Self> {
221 Vc::cell(modules)
222 }
223}
224
225#[turbo_tasks::value(transparent)]
226pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
227
228#[turbo_tasks::value_impl]
229impl Chunks {
230 #[turbo_tasks::function]
231 pub fn empty() -> Vc<Self> {
232 Vc::cell(vec![])
233 }
234}
235
236#[turbo_tasks::value_trait]
243pub trait Chunk: OutputAssetsReference {
244 #[turbo_tasks::function]
245 fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
246
247 #[turbo_tasks::function]
248 fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
249
250 #[turbo_tasks::function]
251 fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
252 ChunkItems(vec![]).cell()
253 }
254}
255
256#[turbo_tasks::value(shared)]
259#[derive(Default)]
260pub struct OutputChunkRuntimeInfo {
261 pub included_ids: Option<ResolvedVc<ModuleIds>>,
262 pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
263 pub module_chunks: Option<ResolvedVc<OutputAssets>>,
267 pub placeholder_for_future_extensions: (),
268}
269
270#[turbo_tasks::value_impl]
271impl OutputChunkRuntimeInfo {
272 #[turbo_tasks::function]
273 pub fn empty() -> Vc<Self> {
274 Self::default().cell()
275 }
276}
277
278#[turbo_tasks::value_trait]
279pub trait OutputChunk: Asset {
280 #[turbo_tasks::function]
281 fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
282}
283
284#[derive(
287 Debug,
288 Clone,
289 Hash,
290 TraceRawVcs,
291 Serialize,
292 Deserialize,
293 Eq,
294 PartialEq,
295 ValueDebugFormat,
296 NonLocalValue,
297 Encode,
298 Decode,
299)]
300pub enum ChunkingType {
301 Parallel {
303 inherit_async: bool,
306 hoisted: bool,
309 },
310 Async,
313 Isolated {
317 _ty: ChunkGroupType,
318 merge_tag: Option<RcStr>,
319 },
320 Shared {
324 inherit_async: bool,
325 merge_tag: Option<RcStr>,
326 },
327 Traced,
329}
330
331impl Display for ChunkingType {
332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333 match self {
334 ChunkingType::Parallel {
335 inherit_async,
336 hoisted,
337 } => {
338 write!(
339 f,
340 "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
341 )
342 }
343 ChunkingType::Async => write!(f, "Async"),
344 ChunkingType::Isolated {
345 _ty,
346 merge_tag: Some(merge_tag),
347 } => {
348 write!(f, "Isolated(merge_tag: {merge_tag})")
349 }
350 ChunkingType::Isolated {
351 _ty,
352 merge_tag: None,
353 } => {
354 write!(f, "Isolated")
355 }
356 ChunkingType::Shared {
357 inherit_async,
358 merge_tag: Some(merge_tag),
359 } => {
360 write!(
361 f,
362 "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
363 )
364 }
365 ChunkingType::Shared {
366 inherit_async,
367 merge_tag: None,
368 } => {
369 write!(f, "Shared(inherit_async: {inherit_async})")
370 }
371 ChunkingType::Traced => write!(f, "Traced"),
372 }
373 }
374}
375
376impl ChunkingType {
377 pub fn is_inherit_async(&self) -> bool {
378 matches!(
379 self,
380 ChunkingType::Parallel {
381 inherit_async: true,
382 ..
383 } | ChunkingType::Shared {
384 inherit_async: true,
385 ..
386 }
387 )
388 }
389
390 pub fn is_parallel(&self) -> bool {
391 matches!(self, ChunkingType::Parallel { .. })
392 }
393
394 pub fn is_merged(&self) -> bool {
395 matches!(
396 self,
397 ChunkingType::Isolated {
398 merge_tag: Some(_),
399 ..
400 } | ChunkingType::Shared {
401 merge_tag: Some(_),
402 ..
403 }
404 )
405 }
406
407 pub fn without_inherit_async(&self) -> Self {
408 match self {
409 ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
410 hoisted: *hoisted,
411 inherit_async: false,
412 },
413 ChunkingType::Async => ChunkingType::Async,
414 ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
415 _ty: *_ty,
416 merge_tag: merge_tag.clone(),
417 },
418 ChunkingType::Shared {
419 inherit_async: _,
420 merge_tag,
421 } => ChunkingType::Shared {
422 inherit_async: false,
423 merge_tag: merge_tag.clone(),
424 },
425 ChunkingType::Traced => ChunkingType::Traced,
426 }
427 }
428}
429
430pub struct ChunkGroupContent {
431 pub chunkable_items: Vec<ChunkableModuleOrBatch>,
432 pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
433 pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
434 pub traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
435 pub availability_info: AvailabilityInfo,
436}
437
438#[turbo_tasks::value_trait]
439pub trait ChunkItem: OutputAssetsReference {
440 #[turbo_tasks::function]
444 fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
445
446 #[turbo_tasks::function]
451 fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
452 self.asset_ident()
453 }
454
455 #[turbo_tasks::function]
457 fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>>;
458
459 #[turbo_tasks::function]
462 fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
463
464 #[turbo_tasks::function]
465 fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
466}
467
468#[turbo_tasks::value_trait]
469pub trait ChunkType: ValueToString {
470 #[turbo_tasks::function]
472 fn is_style(self: Vc<Self>) -> Vc<bool>;
473
474 #[turbo_tasks::function]
476 fn chunk(
477 &self,
478 chunking_context: Vc<Box<dyn ChunkingContext>>,
479 chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
480 batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
481 ) -> Vc<Box<dyn Chunk>>;
482
483 #[turbo_tasks::function]
484 fn chunk_item_size(
485 &self,
486 chunking_context: Vc<Box<dyn ChunkingContext>>,
487 chunk_item: Vc<Box<dyn ChunkItem>>,
488 async_module_info: Option<Vc<AsyncModuleInfo>>,
489 ) -> Vc<usize>;
490}
491
492pub fn round_chunk_item_size(size: usize) -> usize {
493 let a = size.next_power_of_two();
494 size & (a | (a >> 1) | (a >> 2))
495}
496
497#[turbo_tasks::value(transparent)]
498pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
499
500#[turbo_tasks::value]
501pub struct AsyncModuleInfo {
502 pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
503}
504
505#[turbo_tasks::value_impl]
506impl AsyncModuleInfo {
507 #[turbo_tasks::function]
508 pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
509 Ok(Self {
510 referenced_async_modules: referenced_async_modules.into_iter().collect(),
511 }
512 .cell())
513 }
514}
515
516#[derive(
517 Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
518)]
519pub struct ChunkItemWithAsyncModuleInfo {
520 pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
521 pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
522 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
523}
524
525pub trait ChunkItemExt {
526 fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
528}
529
530impl<T> ChunkItemExt for T
531where
532 T: Upcast<Box<dyn ChunkItem>> + Send,
533{
534 async fn id(self: Vc<Self>) -> Result<ModuleId> {
536 let chunk_item = Vc::upcast_non_strict(self);
537 chunk_item
538 .chunking_context()
539 .chunk_item_id_strategy()
540 .await?
541 .get_id(chunk_item)
542 .await
543 }
544}
545
546pub trait ModuleChunkItemIdExt {
547 fn chunk_item_id(
549 self: Vc<Self>,
550 chunking_context: Vc<Box<dyn ChunkingContext>>,
551 ) -> impl Future<Output = Result<ModuleId>> + Send;
552}
553impl<T> ModuleChunkItemIdExt for T
554where
555 T: Upcast<Box<dyn Module>> + Send,
556{
557 async fn chunk_item_id(
558 self: Vc<Self>,
559 chunking_context: Vc<Box<dyn ChunkingContext>>,
560 ) -> Result<ModuleId> {
561 chunking_context
562 .chunk_item_id_strategy()
563 .await?
564 .get_id_from_module(Vc::upcast_non_strict(self))
565 .await
566 }
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572
573 #[test]
574 fn test_round_chunk_item_size() {
575 assert_eq!(round_chunk_item_size(0), 0);
576 assert_eq!(round_chunk_item_size(1), 1);
577 assert_eq!(round_chunk_item_size(2), 2);
578 assert_eq!(round_chunk_item_size(3), 3);
579 assert_eq!(round_chunk_item_size(4), 4);
580 assert_eq!(round_chunk_item_size(5), 4);
581 assert_eq!(round_chunk_item_size(6), 6);
582 assert_eq!(round_chunk_item_size(7), 6);
583 assert_eq!(round_chunk_item_size(8), 8);
584 assert_eq!(round_chunk_item_size(49000), 32_768);
585 assert_eq!(round_chunk_item_size(50000), 49_152);
586
587 assert_eq!(changes_in_range(0..1000), 19);
588 assert_eq!(changes_in_range(1000..2000), 2);
589 assert_eq!(changes_in_range(2000..3000), 1);
590
591 assert_eq!(changes_in_range(3000..10000), 4);
592
593 fn changes_in_range(range: std::ops::Range<usize>) -> usize {
594 let len = range.len();
595 let mut count = 0;
596 for i in range {
597 let a = round_chunk_item_size(i);
598 assert!(a >= i * 2 / 3);
599 assert!(a <= i);
600 let b = round_chunk_item_size(i + 1);
601
602 if a == b {
603 count += 1;
604 }
605 }
606 len - count
607 }
608 }
609}