|
1 | 1 | use anyhow::{bail, Context, Result};
|
| 2 | +use serde::{Deserialize, Serialize}; |
2 | 3 | use tracing::Instrument;
|
3 | 4 | use turbo_rcstr::RcStr;
|
4 |
| -use turbo_tasks::{ResolvedVc, TryJoinIterExt, Upcast, Value, ValueToString, Vc}; |
| 5 | +use turbo_tasks::{ |
| 6 | + trace::TraceRawVcs, NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Upcast, Value, |
| 7 | + ValueToString, Vc, |
| 8 | +}; |
5 | 9 | use turbo_tasks_fs::FileSystemPath;
|
| 10 | +use turbo_tasks_hash::{hash_xxh3_hash64, DeterministicHash}; |
6 | 11 | use turbopack_core::{
|
7 |
| - asset::Asset, |
| 12 | + asset::{Asset, AssetContent}, |
8 | 13 | chunk::{
|
9 | 14 | availability_info::AvailabilityInfo,
|
10 | 15 | chunk_group::{make_chunk_group, MakeChunkGroupResult},
|
@@ -39,6 +44,31 @@ pub enum CurrentChunkMethod {
|
39 | 44 | DocumentCurrentScript,
|
40 | 45 | }
|
41 | 46 |
|
| 47 | +#[derive( |
| 48 | + Debug, |
| 49 | + TaskInput, |
| 50 | + Clone, |
| 51 | + Copy, |
| 52 | + PartialEq, |
| 53 | + Eq, |
| 54 | + Hash, |
| 55 | + Serialize, |
| 56 | + Deserialize, |
| 57 | + TraceRawVcs, |
| 58 | + DeterministicHash, |
| 59 | + NonLocalValue, |
| 60 | +)] |
| 61 | +pub enum ContentHashing { |
| 62 | + /// Direct content hashing: Embeds the chunk content hash directly into the referencing chunk. |
| 63 | + /// Benefit: No hash manifest needed. |
| 64 | + /// Downside: Causes cascading hash invalidation. |
| 65 | + Direct { |
| 66 | + /// The length of the content hash in hex chars. Anything lower than 8 is not recommended |
| 67 | + /// due to the high risk of collisions. |
| 68 | + length: u8, |
| 69 | + }, |
| 70 | +} |
| 71 | + |
42 | 72 | pub struct BrowserChunkingContextBuilder {
|
43 | 73 | chunking_context: BrowserChunkingContext,
|
44 | 74 | }
|
@@ -122,6 +152,11 @@ impl BrowserChunkingContextBuilder {
|
122 | 152 | self
|
123 | 153 | }
|
124 | 154 |
|
| 155 | + pub fn use_content_hashing(mut self, content_hashing: ContentHashing) -> Self { |
| 156 | + self.chunking_context.content_hashing = Some(content_hashing); |
| 157 | + self |
| 158 | + } |
| 159 | + |
125 | 160 | pub fn build(self) -> Vc<BrowserChunkingContext> {
|
126 | 161 | BrowserChunkingContext::new(Value::new(self.chunking_context))
|
127 | 162 | }
|
@@ -170,6 +205,8 @@ pub struct BrowserChunkingContext {
|
170 | 205 | runtime_type: RuntimeType,
|
171 | 206 | /// Whether to minify resulting chunks
|
172 | 207 | minify_type: MinifyType,
|
| 208 | + /// Whether content hashing is enabled. |
| 209 | + content_hashing: Option<ContentHashing>, |
173 | 210 | /// Whether to generate source maps
|
174 | 211 | source_maps_type: SourceMapsType,
|
175 | 212 | /// Method to use when figuring out the current chunk src
|
@@ -211,6 +248,7 @@ impl BrowserChunkingContext {
|
211 | 248 | environment,
|
212 | 249 | runtime_type,
|
213 | 250 | minify_type: MinifyType::NoMinify,
|
| 251 | + content_hashing: None, |
214 | 252 | source_maps_type: SourceMapsType::Full,
|
215 | 253 | current_chunk_method: CurrentChunkMethod::StringLiteral,
|
216 | 254 | manifest_chunks: false,
|
@@ -358,15 +396,35 @@ impl ChunkingContext for BrowserChunkingContext {
|
358 | 396 | #[turbo_tasks::function]
|
359 | 397 | async fn chunk_path(
|
360 | 398 | &self,
|
361 |
| - _asset: Option<Vc<Box<dyn Asset>>>, |
| 399 | + asset: Option<Vc<Box<dyn Asset>>>, |
362 | 400 | ident: Vc<AssetIdent>,
|
363 | 401 | extension: RcStr,
|
364 | 402 | ) -> Result<Vc<FileSystemPath>> {
|
365 | 403 | let root_path = self.chunk_root_path;
|
366 |
| - let name = ident |
367 |
| - .output_name(*self.root_path, extension) |
368 |
| - .owned() |
369 |
| - .await?; |
| 404 | + let name = match self.content_hashing { |
| 405 | + None => { |
| 406 | + ident |
| 407 | + .output_name(*self.root_path, extension) |
| 408 | + .owned() |
| 409 | + .await? |
| 410 | + } |
| 411 | + Some(ContentHashing::Direct { length }) => { |
| 412 | + let Some(asset) = asset else { |
| 413 | + bail!("chunk_path requires an asset when content hashing is enabled"); |
| 414 | + }; |
| 415 | + let content = asset.content().await?; |
| 416 | + if let AssetContent::File(file) = &*content { |
| 417 | + let hash = hash_xxh3_hash64(&file.await?); |
| 418 | + let length = length as usize; |
| 419 | + format!("{hash:0length$x}{extension}").into() |
| 420 | + } else { |
| 421 | + bail!( |
| 422 | + "chunk_path requires an asset with file content when content hashing is \ |
| 423 | + enabled" |
| 424 | + ); |
| 425 | + } |
| 426 | + } |
| 427 | + }; |
370 | 428 | Ok(root_path.join(name))
|
371 | 429 | }
|
372 | 430 |
|
|
0 commit comments