Skip to content
Draft
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.

11 changes: 4 additions & 7 deletions crates/next-core/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::future::Future;
use std::{future::Future, str::FromStr};

use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
Expand Down Expand Up @@ -60,13 +60,10 @@ pub fn defines(define_env: &FxIndexMap<RcStr, Option<RcStr>>) -> CompileTimeDefi
)
.or_insert_with(|| {
if let Some(v) = v {
let val = serde_json::from_str(v);
let val = serde_json::Value::from_str(v);
match val {
Ok(serde_json::Value::Bool(v)) => CompileTimeDefineValue::Bool(v),
Ok(serde_json::Value::String(v)) => {
CompileTimeDefineValue::String(v.into())
}
_ => CompileTimeDefineValue::JSON(v.clone()),
Ok(v) => v.into(),
_ => CompileTimeDefineValue::Evaluate(v.clone()),
}
} else {
CompileTimeDefineValue::Undefined
Expand Down
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
6 changes: 6 additions & 0 deletions turbopack/crates/turbo-rcstr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ impl AsRef<[u8]> for RcStr {
}
}

impl From<RcStr> for BytesStr {
fn from(value: RcStr) -> Self {
Self::from_str_slice(value.as_str())
}
}

impl PartialEq<str> for RcStr {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
Expand Down
9 changes: 7 additions & 2 deletions turbopack/crates/turbo-tasks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ futures = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
mopa = "0.2.0"
once_cell = { workspace = true }
parking_lot = { workspace = true, features = ["serde"]}
parking_lot = { workspace = true, features = ["serde"] }
pin-project-lite = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true, features = ["rc", "derive"] }
serde_json = { workspace = true }
serde_regex = "1.1.0"
shrink-to-fit = { workspace=true,features = ["indexmap", "serde_json", "smallvec", "nightly"] }
shrink-to-fit = { workspace = true, features = [
"indexmap",
"serde_json",
"smallvec",
"nightly",
] }
smallvec = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
Expand Down
2 changes: 1 addition & 1 deletion turbopack/crates/turbo-tasks/src/task/task_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ where
async fn resolve_input(&self) -> Result<Self> {
let mut resolved = Vec::with_capacity(self.len());
for value in self {
resolved.push(value.resolve_input().await?);
resolved.push(Box::pin(value.resolve_input()).await?);
}
Ok(resolved)
}
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 @@ -183,6 +187,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 @@ -249,6 +263,10 @@ pub struct BrowserChunkingContext {
export_usage: Option<ResolvedVc<ExportUsageInfo>>,
/// 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 @@ -289,6 +307,8 @@ impl BrowserChunkingContext {
module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
export_usage: None,
chunking_configs: Default::default(),
filename: Default::default(),
chunk_filename: Default::default(),
},
}
}
Expand Down Expand Up @@ -437,32 +457,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 @@ -759,3 +823,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()
}
17 changes: 15 additions & 2 deletions turbopack/crates/turbopack-browser/src/ecmascript/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
source_map::{GenerateSourceMap, OptionStringifiedSourceMap, SourceMapAsset},
version::{MergeableVersionedContent, Version, VersionedContent, VersionedContentMerger},
};
use turbopack_ecmascript::{chunk::EcmascriptChunkContent, minify::minify, utils::StringifyJs};

Check warning on line 17 in turbopack/crates/turbopack-browser/src/ecmascript/content.rs

View workflow job for this annotation

GitHub Actions / Benchmark Rust Crates (tiny)

unused import: `minify::minify`

use super::{
chunk::EcmascriptBrowserChunk, content_entry::EcmascriptBrowserChunkContentEntries,
Expand Down Expand Up @@ -127,8 +127,21 @@

let mut code = code.build();

if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
code = minify(code, source_maps, mangle)?;
if let MinifyType::Minify { mangle, extract_comments } = *this.chunking_context.minify_type().await? {
let result = turbopack_ecmascript::minify::minify_with_options(
code,
source_maps,
mangle,
extract_comments
)?;

code = result.code;

// TODO: Handle extracted comments by creating a license file
// For now, we'll just ignore the extracted comments
if let Some(_extracted_comments) = result.extracted_comments {
// Future implementation: create a LicenseAsset and add it to output
}
}

Ok(code.cell())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
};
use turbopack_ecmascript::{
chunk::{EcmascriptChunkData, EcmascriptChunkPlaceable},
minify::minify,

Check warning on line 25 in turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs

View workflow job for this annotation

GitHub Actions / Benchmark Rust Crates (tiny)

unused import: `minify::minify`
utils::StringifyJs,
};
use turbopack_ecmascript_runtime::RuntimeType;
Expand All @@ -36,7 +36,7 @@
/// * 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 @@
}

#[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().owned().await?,
*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 Expand Up @@ -189,8 +209,21 @@

let mut code = code.build();

if let MinifyType::Minify { mangle } = *this.chunking_context.minify_type().await? {
code = minify(code, source_maps, mangle)?;
if let MinifyType::Minify { mangle, extract_comments } = *this.chunking_context.minify_type().await? {
let result = turbopack_ecmascript::minify::minify_with_options(
code,
source_maps,
mangle,
extract_comments
)?;

code = result.code;

// TODO: Handle extracted comments by creating a license file
// For now, we'll just ignore the extracted comments
if let Some(_extracted_comments) = result.extracted_comments {
// Future implementation: create a LicenseAsset and add it to output
}
}

Ok(code.cell())
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
Loading
Loading