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
50 changes: 1 addition & 49 deletions turbopack/crates/turbopack-core/src/chunk/chunking_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,67 +50,19 @@ pub enum MangleType {
Deterministic,
}

#[derive(
Debug,
TaskInput,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Deserialize,
TraceRawVcs,
DeterministicHash,
NonLocalValue,
Encode,
Decode,
)]
#[serde(rename_all = "kebab-case")]
pub struct CompressOptions {
pub passes: Option<u8>,
pub sequences: Option<u8>,
pub keep_classnames: Option<bool>,
pub keep_fnames: Option<bool>,
}

#[derive(
Debug,
TaskInput,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Deserialize,
TraceRawVcs,
DeterministicHash,
NonLocalValue,
Encode,
Decode,
)]
#[serde(rename_all = "kebab-case")]
pub enum CompressType {
Default,
Options(CompressOptions),
}

#[turbo_tasks::value(shared)]
#[derive(Debug, TaskInput, Clone, Copy, Hash, DeterministicHash, Deserialize)]
pub enum MinifyType {
// TODO instead of adding a new property here,
// refactor that to Minify(MinifyOptions) to allow defaults on MinifyOptions
Minify {
mangle: Option<MangleType>,
compress: Option<CompressType>,
},
Minify { mangle: Option<MangleType> },
NoMinify,
}

impl Default for MinifyType {
fn default() -> Self {
Self::Minify {
mangle: Some(MangleType::OptimalSize),
compress: Some(CompressType::Default),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions turbopack/crates/turbopack-core/src/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub use crate::chunk::{
},
chunking_context::{
AssetSuffix, ChunkGroupResult, ChunkGroupType, ChunkingConfig, ChunkingConfigs,
ChunkingContext, ChunkingContextExt, CompressOptions, CompressType, EntryChunkGroupResult,
MangleType, MinifyType, SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
ChunkingContext, ChunkingContextExt, EntryChunkGroupResult, MangleType, MinifyType,
SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
},
data::{ChunkData, ChunkDataOption, ChunksData},
evaluate::{EvaluatableAsset, EvaluatableAssetExt, EvaluatableAssets},
Expand Down
104 changes: 75 additions & 29 deletions turbopack/crates/turbopack-core/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@ impl EdgeWorkerEnvironment {
#[turbo_tasks::value(transparent, serialization = "skip")]
pub struct RuntimeVersions(#[turbo_tasks(trace_ignore)] pub Versions);

/// Checks if a browser version field is either absent or at least the given version.
/// Supports major-only, major.minor, and major.minor.patch comparisons.
macro_rules! version_at_least {
($data:expr, $field:ident, $major:expr) => {
$data.$field.is_none_or(|v| v.major >= $major)
};
($data:expr, $field:ident, $major:expr, $minor:expr) => {
$data
.$field
.is_none_or(|v| v.major > $major || (v.major == $major && v.minor >= $minor))
};
($data:expr, $field:ident, $major:expr, $minor:expr, $patch:expr) => {
$data.$field.is_none_or(|v| {
v.major > $major
|| (v.major == $major && v.minor > $minor)
|| (v.major == $major && v.minor == $minor && v.patch >= $patch)
})
};
}

#[turbo_tasks::value_impl]
impl RuntimeVersions {
/// Whether the environment supports arrow functions.
Expand All @@ -376,22 +396,18 @@ impl RuntimeVersions {
// "opera_mobile": "34",
// "electron": "0.36"
let data = &self.0;
let supported = data.chrome.is_none_or(|v| v.major >= 47)
&& data.opera.is_none_or(|v| v.major >= 34)
&& data.edge.is_none_or(|v| v.major >= 13)
&& data.firefox.is_none_or(|v| v.major >= 43)
&& data.safari.is_none_or(|v| v.major >= 10)
&& data.node.is_none_or(|v| v.major >= 6)
&& data.deno.is_none_or(|v| v.major >= 1)
&& data.ios.is_none_or(|v| v.major >= 10)
&& data.samsung.is_none_or(|v| v.major >= 5)
&& data.rhino.is_none_or(|v| {
v.major > 1
|| (v.major == 1 && v.minor > 7)
|| (v.major == 1 && v.minor == 7 && v.patch >= 13)
})
&& data.opera_mobile.is_none_or(|v| v.major >= 34)
&& data.electron.is_none_or(|v| v.major > 0 || v.minor >= 36);
let supported = version_at_least!(data, chrome, 47)
&& version_at_least!(data, opera, 34)
&& version_at_least!(data, edge, 13)
&& version_at_least!(data, firefox, 43)
&& version_at_least!(data, safari, 10)
&& version_at_least!(data, node, 6)
&& version_at_least!(data, deno, 1)
&& version_at_least!(data, ios, 10)
&& version_at_least!(data, samsung, 5)
&& version_at_least!(data, rhino, 1, 7, 13)
&& version_at_least!(data, opera_mobile, 34)
&& version_at_least!(data, electron, 0, 36);

Vc::cell(supported)
}
Expand All @@ -412,19 +428,49 @@ impl RuntimeVersions {
// "opera_mobile": "37",
// "electron": "1.1"
let data = &self.0;
let supported = data.chrome.is_none_or(|v| v.major >= 50)
&& data.opera.is_none_or(|v| v.major >= 37)
&& data.edge.is_none_or(|v| v.major >= 14)
&& data.firefox.is_none_or(|v| v.major >= 53)
&& data.safari.is_none_or(|v| v.major >= 11)
&& data.node.is_none_or(|v| v.major >= 6)
&& data.deno.is_none_or(|v| v.major >= 1)
&& data.ios.is_none_or(|v| v.major >= 11)
&& data.samsung.is_none_or(|v| v.major >= 5)
&& data.opera_mobile.is_none_or(|v| v.major >= 37)
&& data
.electron
.is_none_or(|v| v.major > 1 || (v.major == 1 && v.minor >= 1));
let supported = version_at_least!(data, chrome, 50)
&& version_at_least!(data, opera, 37)
&& version_at_least!(data, edge, 14)
&& version_at_least!(data, firefox, 53)
&& version_at_least!(data, safari, 11)
&& version_at_least!(data, node, 6)
&& version_at_least!(data, deno, 1)
&& version_at_least!(data, ios, 11)
&& version_at_least!(data, samsung, 5)
&& version_at_least!(data, opera_mobile, 37)
&& version_at_least!(data, electron, 1, 1);

Vc::cell(supported)
}

/// Whether the environment supports async/await syntax.
#[turbo_tasks::function]
pub fn supports_async_await(&self) -> Vc<bool> {
// https://github.com/babel/babel/blob/b0e3517dc566880e76b5f1f4dcf7fcecba58337d/packages/babel-compat-data/data/plugins.json#L295-L307
// "chrome": "55",
// "opera": "42",
// "edge": "15",
// "firefox": "52",
// "safari": "11",
// "node": "7.6",
// "deno": "1",
// "ios": "11",
// "samsung": "6",
// "opera_mobile": "42",
// "electron": "1.6"
let data = &self.0;

let supported = version_at_least!(data, chrome, 55)
&& version_at_least!(data, opera, 42)
&& version_at_least!(data, edge, 15)
&& version_at_least!(data, firefox, 52)
&& version_at_least!(data, safari, 11)
&& version_at_least!(data, node, 7, 6)
&& version_at_least!(data, deno, 1)
&& version_at_least!(data, ios, 11)
&& version_at_least!(data, samsung, 6)
&& version_at_least!(data, opera_mobile, 42)
&& version_at_least!(data, electron, 1, 6);

Vc::cell(supported)
}
Expand Down
45 changes: 13 additions & 32 deletions turbopack/crates/turbopack-core/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use turbo_tasks::{
trace::TraceRawVcs, turbofmt,
};
use turbo_tasks_fs::FileSystemPath;
use turbo_tasks_hash::{DeterministicHash, Xxh3Hash64Hasher, encode_hex, hash_xxh3_hash64};
use turbo_tasks_hash::{DeterministicHash, Xxh3Hash64Hasher, encode_base38, hash_xxh3_hash64};

use crate::resolve::ModulePart;

Expand Down Expand Up @@ -269,27 +269,6 @@ impl AssetIdent {
fragment.deterministic_hash(&mut hasher);
has_hash = true;
}
if !assets.is_empty() {
// Use XOR to combine asset hashes in an order-independent way
// This ensures chunks with the same modules but different order get the same hash
let mut asset_hashes = Vec::with_capacity(assets.len());
for (key, ident) in assets.iter() {
let mut asset_hasher = Xxh3Hash64Hasher::new();
key.deterministic_hash(&mut asset_hasher);
ident
.to_string()
.await?
.deterministic_hash(&mut asset_hasher);
asset_hashes.push(asset_hasher.finish());
}
asset_hashes.sort_unstable();

2_u8.deterministic_hash(&mut hasher);
for h in asset_hashes {
h.deterministic_hash(&mut hasher);
}
has_hash = true;
}
for (key, ident) in assets.iter() {
2_u8.deterministic_hash(&mut hasher);
key.deterministic_hash(&mut hasher);
Expand Down Expand Up @@ -357,8 +336,9 @@ impl AssetIdent {
}

if has_hash {
let hash = encode_hex(hasher.finish());
let truncated_hash = &hash[..8];
let hash = encode_base38(hasher.finish());
// 7 base38 chars ≈ 36 bits of collision resistance
let truncated_hash = &hash[..7];
write!(name, "_{truncated_hash}")?;
}

Expand All @@ -379,17 +359,18 @@ impl AssetIdent {
}
}
if i > 0 {
let hash = encode_hex(hash_xxh3_hash64(&name.as_bytes()[..i]));
let truncated_hash = &hash[..5];
let hash = encode_base38(hash_xxh3_hash64(&name.as_bytes()[..i]));
// 4 base38 chars ≈ 21 bits — just a short disambiguator prefix
let truncated_hash = &hash[..4];
name = format!("{}_{}", truncated_hash, &name[i..]);
}
// 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 Down Expand Up @@ -455,8 +436,8 @@ impl ValueToString for AssetIdent {
}
}

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

Expand Down
72 changes: 21 additions & 51 deletions turbopack/crates/turbopack-core/src/module_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,56 +698,21 @@ pub struct ModuleGraph {

#[turbo_tasks::value_impl]
impl ModuleGraph {
/// Analyze the module graph and potentially remove unused references (by determining the used
/// exports and removing unused imports).
#[turbo_tasks::function(operation)]
pub async fn from_single_graph(graph: OperationVc<SingleModuleGraph>) -> Result<Vc<Self>> {
let graph = Self::create(vec![graph], None)
.read_strongly_consistent()
.await?;
Ok(ReadRef::cell(graph))
}

#[turbo_tasks::function(operation)]
pub async fn from_graphs(graphs: Vec<OperationVc<SingleModuleGraph>>) -> Result<Vc<Self>> {
let graph = Self::create(graphs, None)
.read_strongly_consistent()
.await?;
Ok(ReadRef::cell(graph))
}

/// Analyze the module graph and remove unused references (by determining the used exports and
/// removing unused imports).
///
/// In particular, this removes ModuleReference-s that list only unused exports in the
/// `import_usage()`
#[turbo_tasks::function(operation)]
pub async fn from_single_graph_without_unused_references(
graph: OperationVc<SingleModuleGraph>,
binding_usage: OperationVc<BindingUsageInfo>,
) -> Result<Vc<Self>> {
let graph = Self::create(vec![graph], Some(binding_usage))
.read_strongly_consistent()
.await?;
Ok(ReadRef::cell(graph))
}

/// Analyze the module graph and remove unused references (by determining the used exports and
/// removing unused imports).
///
/// In particular, this removes ModuleReference-s that list only unused exports in the
/// `import_usage()`
#[turbo_tasks::function(operation)]
pub async fn from_graphs_without_unused_references(
pub async fn from_graphs(
graphs: Vec<OperationVc<SingleModuleGraph>>,
binding_usage: OperationVc<BindingUsageInfo>,
binding_usage: Option<OperationVc<BindingUsageInfo>>,
) -> Result<Vc<Self>> {
let graph = Self::create(graphs, Some(binding_usage))
let graph = Self::from_graphs_inner(graphs, binding_usage)
.read_strongly_consistent()
.await?;
Ok(ReadRef::cell(graph))
}

#[turbo_tasks::function(operation)]
async fn create(
async fn from_graphs_inner(
graphs: Vec<OperationVc<SingleModuleGraph>>,
binding_usage: Option<OperationVc<BindingUsageInfo>>,
) -> Result<Vc<ModuleGraph>> {
Expand Down Expand Up @@ -2103,15 +2068,18 @@ pub mod tests {
false,
);

let module_graph = ModuleGraph::from_graphs(vec![
parent_graph,
SingleModuleGraph::new_with_entries_visited(
ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
VisitedModules::from_graph(parent_graph),
false,
false,
),
])
let module_graph = ModuleGraph::from_graphs(
vec![
parent_graph,
SingleModuleGraph::new_with_entries_visited(
ResolvedVc::cell(vec![ChunkGroupEntry::Entry(vec![a_module])]),
VisitedModules::from_graph(parent_graph),
false,
false,
),
],
None,
)
.connect();
let child_graph = module_graph
.iter_graphs()
Expand Down Expand Up @@ -2366,7 +2334,9 @@ pub mod tests {
.await?
.into_iter()
.collect();
let module_graph = ModuleGraph::from_single_graph(graph).connect().await?;
let module_graph = ModuleGraph::from_graphs(vec![graph], None)
.connect()
.await?;

Ok(SetupGraph {
module_graph,
Expand Down
Loading
Loading