Skip to content

Commit cfacd83

Browse files
committed
enable content hashing in production
1 parent b664ffd commit cfacd83

File tree

4 files changed

+71
-10
lines changed

4 files changed

+71
-10
lines changed

crates/next-core/src/next_client/context.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use turbopack::{
1414
resolve_options_context::ResolveOptionsContext,
1515
};
1616
use turbopack_browser::{
17-
react_refresh::assert_can_resolve_react_refresh, BrowserChunkingContext, CurrentChunkMethod,
17+
react_refresh::assert_can_resolve_react_refresh, BrowserChunkingContext, ContentHashing,
18+
CurrentChunkMethod,
1819
};
1920
use turbopack_core::{
2021
chunk::{
@@ -486,6 +487,7 @@ pub async fn get_client_chunking_context(
486487
..Default::default()
487488
},
488489
);
490+
builder = builder.use_content_hashing(ContentHashing::Direct { length: 16 })
489491
}
490492

491493
Ok(Vc::upcast(builder.build()))

turbopack/crates/turbopack-browser/src/chunking_context.rs

+65-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use anyhow::{bail, Context, Result};
2+
use serde::{Deserialize, Serialize};
23
use tracing::Instrument;
34
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+
};
59
use turbo_tasks_fs::FileSystemPath;
10+
use turbo_tasks_hash::{hash_xxh3_hash64, DeterministicHash};
611
use turbopack_core::{
7-
asset::Asset,
12+
asset::{Asset, AssetContent},
813
chunk::{
914
availability_info::AvailabilityInfo,
1015
chunk_group::{make_chunk_group, MakeChunkGroupResult},
@@ -39,6 +44,31 @@ pub enum CurrentChunkMethod {
3944
DocumentCurrentScript,
4045
}
4146

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+
4272
pub struct BrowserChunkingContextBuilder {
4373
chunking_context: BrowserChunkingContext,
4474
}
@@ -122,6 +152,11 @@ impl BrowserChunkingContextBuilder {
122152
self
123153
}
124154

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+
125160
pub fn build(self) -> Vc<BrowserChunkingContext> {
126161
BrowserChunkingContext::new(Value::new(self.chunking_context))
127162
}
@@ -170,6 +205,8 @@ pub struct BrowserChunkingContext {
170205
runtime_type: RuntimeType,
171206
/// Whether to minify resulting chunks
172207
minify_type: MinifyType,
208+
/// Whether content hashing is enabled.
209+
content_hashing: Option<ContentHashing>,
173210
/// Whether to generate source maps
174211
source_maps_type: SourceMapsType,
175212
/// Method to use when figuring out the current chunk src
@@ -211,6 +248,7 @@ impl BrowserChunkingContext {
211248
environment,
212249
runtime_type,
213250
minify_type: MinifyType::NoMinify,
251+
content_hashing: None,
214252
source_maps_type: SourceMapsType::Full,
215253
current_chunk_method: CurrentChunkMethod::StringLiteral,
216254
manifest_chunks: false,
@@ -358,15 +396,35 @@ impl ChunkingContext for BrowserChunkingContext {
358396
#[turbo_tasks::function]
359397
async fn chunk_path(
360398
&self,
361-
_asset: Option<Vc<Box<dyn Asset>>>,
399+
asset: Option<Vc<Box<dyn Asset>>>,
362400
ident: Vc<AssetIdent>,
363401
extension: RcStr,
364402
) -> Result<Vc<FileSystemPath>> {
365403
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+
};
370428
Ok(root_path.join(name))
371429
}
372430

turbopack/crates/turbopack-browser/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub mod ecmascript;
88
pub mod react_refresh;
99

1010
pub use chunking_context::{
11-
BrowserChunkingContext, BrowserChunkingContextBuilder, CurrentChunkMethod,
11+
BrowserChunkingContext, BrowserChunkingContextBuilder, ContentHashing, CurrentChunkMethod,
1212
};
1313

1414
pub fn register() {

turbopack/crates/turbopack-cli/src/build/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use turbopack::{
2121
css::chunk::CssChunkType, ecmascript::chunk::EcmascriptChunkType,
2222
global_module_ids::get_global_module_id_strategy,
2323
};
24-
use turbopack_browser::BrowserChunkingContext;
24+
use turbopack_browser::{BrowserChunkingContext, ContentHashing};
2525
use turbopack_cli_utils::issue::{ConsoleUi, LogOptions};
2626
use turbopack_core::{
2727
asset::Asset,
@@ -354,6 +354,7 @@ async fn build_internal(
354354
..Default::default()
355355
},
356356
);
357+
builder = builder.use_content_hashing(ContentHashing::Direct { length: 16 })
357358
}
358359
}
359360

0 commit comments

Comments
 (0)