Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/next/src/build/swc/generated-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function lightningCssTransformStyleAttribute(

/* auto-generated by NAPI-RS */

export declare class ExternalObject<T> {
export class ExternalObject<T> {
readonly '': {
readonly '': unique symbol
[K: symbol]: T
Expand Down
3 changes: 2 additions & 1 deletion turbopack/crates/turbopack-browser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ serde_json = { workspace = true }
serde_qs = { workspace = true }
tracing = { workspace = true }
urlencoding = { workspace = true }

regex = { workspace = true }
qstring = { workspace = true }
turbo-rcstr = { workspace = true }
turbo-tasks = { workspace = true }
turbo-tasks-fs = { workspace = true }
Expand Down
143 changes: 122 additions & 21 deletions turbopack/crates/turbopack-browser/src/chunking_context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::{cmp::min, sync::LazyLock};

use anyhow::{Context, Result, bail};
use qstring::QString;
use regex::Regex;
use serde::{Deserialize, Serialize};
use tracing::Instrument;
use turbo_rcstr::{RcStr, rcstr};
Expand Down Expand Up @@ -165,6 +169,16 @@ impl BrowserChunkingContextBuilder {
self
}

pub fn filename(mut self, filename: RcStr) -> Self {
self.chunking_context.filename = Some(filename);
self
}

pub fn chunk_filename(mut self, chunk_filename: RcStr) -> Self {
self.chunking_context.chunk_filename = Some(chunk_filename);
self
}

pub fn build(self) -> Vc<BrowserChunkingContext> {
BrowserChunkingContext::cell(self.chunking_context)
}
Expand Down Expand Up @@ -227,6 +241,10 @@ pub struct BrowserChunkingContext {
module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
/// The chunking configs
chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
/// Evaluate chunk filename template
filename: Option<RcStr>,
/// Non evaluate chunk filename template
chunk_filename: Option<RcStr>,
}

impl BrowserChunkingContext {
Expand Down Expand Up @@ -265,6 +283,8 @@ impl BrowserChunkingContext {
manifest_chunks: false,
module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
chunking_configs: Default::default(),
filename: Default::default(),
chunk_filename: Default::default(),
},
}
}
Expand Down Expand Up @@ -410,32 +430,76 @@ impl ChunkingContext for BrowserChunkingContext {
extension.starts_with("."),
"`extension` should include the leading '.', got '{extension}'"
);
let root_path = self.chunk_root_path.clone();
let name = match self.content_hashing {
None => {
ident
.output_name(self.root_path.clone(), extension)
.owned()
.await?
}
Some(ContentHashing::Direct { length }) => {
let Some(asset) = asset else {
bail!("chunk_path requires an asset when content hashing is enabled");

let output_name = ident
.output_name(self.root_path.clone(), extension.clone())
.owned()
.await?;

let mut filename = match asset {
Some(asset) => {
let ident = ident.await?;

let mut evaluate = false;
let mut dev_chunk_list = false;
ident.modifiers.iter().for_each(|m| {
if m.contains("evaluate") {
evaluate = true;
}
if m.contains("dev chunk list") {
dev_chunk_list = true;
}
});
let query = QString::from(ident.query.as_str());
let name = if dev_chunk_list {
output_name.as_str()
} else {
query.get("name").unwrap_or(output_name.as_str())
};
let content = asset.content().await?;
if let AssetContent::File(file) = &*content {
let hash = hash_xxh3_hash64(&file.await?);
let length = length as usize;
format!("{hash:0length$x}{extension}").into()

let filename_template = if evaluate {
&self.filename
} else {
bail!(
"chunk_path requires an asset with file content when content hashing is \
enabled"
);
&self.chunk_filename
};

match filename_template {
Some(filename) => {
let mut filename = filename.to_string();

if match_name_placeholder(&filename) {
filename = replace_name_placeholder(&filename, name);
}

if match_content_hash_placeholder(&filename) {
let content = asset.content().await?;
if let AssetContent::File(file) = &*content {
let content_hash = hash_xxh3_hash64(&file.await?);
filename = replace_content_hash_placeholder(
&filename,
&format!("{content_hash:016x}"),
);
} else {
bail!(
"chunk_path requires an asset with file content when content \
hashing is enabled"
);
}
};

filename
}
None => name.to_string(),
}
}
None => output_name.to_string(),
};
Ok(root_path.join(&name)?.cell())

if !filename.ends_with(extension.as_str()) {
filename.push_str(&extension);
}

self.chunk_root_path.join(&filename).map(|p| p.cell())
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -715,3 +779,40 @@ impl ChunkingContext for BrowserChunkingContext {
})
}
}

pub fn clean_separators(s: &str) -> String {
static SEPARATOR_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r".*[/#?]").unwrap());
SEPARATOR_REGEX.replace_all(s, "").to_string()
}

static NAME_PLACEHOLDER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[name\]").unwrap());

pub fn match_name_placeholder(s: &str) -> bool {
NAME_PLACEHOLDER_REGEX.is_match(s)
}

pub fn replace_name_placeholder(s: &str, name: &str) -> String {
NAME_PLACEHOLDER_REGEX.replace_all(s, name).to_string()
}

static CONTENT_HASH_PLACEHOLDER_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[contenthash(?::(?P<len>\d+))?\]").unwrap());

pub fn match_content_hash_placeholder(s: &str) -> bool {
CONTENT_HASH_PLACEHOLDER_REGEX.is_match(s)
}

pub fn replace_content_hash_placeholder(s: &str, hash: &str) -> String {
CONTENT_HASH_PLACEHOLDER_REGEX
.replace_all(s, |caps: &regex::Captures| {
let len = caps.name("len").map(|m| m.as_str()).unwrap_or("");
let len = if len.is_empty() {
hash.len()
} else {
len.parse().unwrap_or(hash.len())
};
let len = min(len, hash.len());
hash[..len].to_string()
})
.to_string()
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
/// * Contains the Turbopack browser runtime code; and
/// * Evaluates a list of runtime entries.
#[turbo_tasks::value(shared)]
pub(crate) struct EcmascriptBrowserEvaluateChunk {
pub struct EcmascriptBrowserEvaluateChunk {
chunking_context: ResolvedVc<BrowserChunkingContext>,
ident: ResolvedVc<AssetIdent>,
other_chunks: ResolvedVc<OutputAssets>,
Expand Down Expand Up @@ -68,13 +68,33 @@ impl EcmascriptBrowserEvaluateChunk {
}

#[turbo_tasks::function]
async fn chunks_data(&self) -> Result<Vc<ChunksData>> {
pub async fn chunks_data(&self) -> Result<Vc<ChunksData>> {
Ok(ChunkData::from_assets(
self.chunking_context.output_root().await?.clone_value(),
*self.other_chunks,
))
}

#[turbo_tasks::function]
pub fn ident(&self) -> Vc<AssetIdent> {
*self.ident
}

#[turbo_tasks::function]
pub fn evaluatable_assets(&self) -> Vc<EvaluatableAssets> {
*self.evaluatable_assets
}

#[turbo_tasks::function]
pub fn module_graph(&self) -> Vc<ModuleGraph> {
*self.module_graph
}

#[turbo_tasks::function]
pub fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
Vc::upcast(*self.chunking_context)
}

#[turbo_tasks::function]
async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
let this = self.await?;
Expand Down
1 change: 1 addition & 0 deletions turbopack/crates/turbopack-browser/src/ecmascript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pub(crate) mod version;

pub use chunk::EcmascriptBrowserChunk;
pub use content::EcmascriptBrowserChunkContent;
pub use evaluate::chunk::EcmascriptBrowserEvaluateChunk;
2 changes: 1 addition & 1 deletion turbopack/crates/turbopack-browser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![feature(arbitrary_self_types)]
#![feature(arbitrary_self_types_pointers)]

pub(crate) mod chunking_context;
pub mod chunking_context;
pub mod ecmascript;
pub mod react_refresh;

Expand Down
10 changes: 5 additions & 5 deletions turbopack/crates/turbopack-core/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,10 @@ impl AssetIdent {
// We need to make sure that `.json` and `.json.js` doesn't end up with the same
// name. So when we add an extra extension when want to mark that with a "._"
// suffix.
if !removed_extension {
name += "._";
}
name += &expected_extension;
// if !removed_extension {
// name += "._";
// }
// name += &expected_extension;
Ok(Vc::cell(name.into()))
}
}
Expand All @@ -429,5 +429,5 @@ fn clean_separators(s: &str) -> String {
}

fn clean_additional_extensions(s: &str) -> String {
s.replace('.', "_")
s.replace('.', "_").replace("[root-of-the-server]", "")
}
Loading
Loading