Skip to content

Commit 2c9291f

Browse files
xusd320fireairforce
authored andcommitted
feat(turbopack): apply utoo patches to canary
1 parent 321c6af commit 2c9291f

File tree

14 files changed

+239
-54
lines changed

14 files changed

+239
-54
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/next/src/build/swc/generated-native.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function lightningCssTransformStyleAttribute(
2828

2929
/* auto-generated by NAPI-RS */
3030

31-
export declare class ExternalObject<T> {
31+
export class ExternalObject<T> {
3232
readonly '': {
3333
readonly '': unique symbol
3434
[K: symbol]: T

turbopack/crates/turbopack-browser/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ serde_json = { workspace = true }
2727
serde_qs = { workspace = true }
2828
tracing = { workspace = true }
2929
urlencoding = { workspace = true }
30-
30+
regex = { workspace = true }
31+
qstring = { workspace = true }
3132
turbo-rcstr = { workspace = true }
3233
turbo-tasks = { workspace = true }
3334
turbo-tasks-fs = { workspace = true }

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

Lines changed: 122 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
use std::{cmp::min, sync::LazyLock};
2+
13
use anyhow::{Context, Result, bail};
4+
use qstring::QString;
5+
use regex::Regex;
26
use serde::{Deserialize, Serialize};
37
use tracing::Instrument;
48
use turbo_rcstr::{RcStr, rcstr};
@@ -188,6 +192,16 @@ impl BrowserChunkingContextBuilder {
188192
self
189193
}
190194

195+
pub fn filename(mut self, filename: RcStr) -> Self {
196+
self.chunking_context.filename = Some(filename);
197+
self
198+
}
199+
200+
pub fn chunk_filename(mut self, chunk_filename: RcStr) -> Self {
201+
self.chunking_context.chunk_filename = Some(chunk_filename);
202+
self
203+
}
204+
191205
pub fn build(self) -> Vc<BrowserChunkingContext> {
192206
BrowserChunkingContext::cell(self.chunking_context)
193207
}
@@ -256,6 +270,10 @@ pub struct BrowserChunkingContext {
256270
export_usage: Option<ResolvedVc<ExportUsageInfo>>,
257271
/// The chunking configs
258272
chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
273+
/// Evaluate chunk filename template
274+
filename: Option<RcStr>,
275+
/// Non evaluate chunk filename template
276+
chunk_filename: Option<RcStr>,
259277
}
260278

261279
impl BrowserChunkingContext {
@@ -297,6 +315,8 @@ impl BrowserChunkingContext {
297315
module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
298316
export_usage: None,
299317
chunking_configs: Default::default(),
318+
filename: Default::default(),
319+
chunk_filename: Default::default(),
300320
},
301321
}
302322
}
@@ -460,40 +480,76 @@ impl ChunkingContext for BrowserChunkingContext {
460480
extension.starts_with("."),
461481
"`extension` should include the leading '.', got '{extension}'"
462482
);
463-
let ChunkPathInfo {
464-
chunk_root_path,
465-
content_hashing,
466-
root_path,
467-
} = &*self.chunk_path_info().await?;
468-
let name = match *content_hashing {
469-
None => {
470-
ident
471-
.output_name(root_path.clone(), prefix, extension)
472-
.owned()
473-
.await?
474-
}
475-
Some(ContentHashing::Direct { length }) => {
476-
let Some(asset) = asset else {
477-
bail!("chunk_path requires an asset when content hashing is enabled");
478-
};
479-
let content = asset.content().await?;
480-
if let AssetContent::File(file) = &*content {
481-
let hash = hash_xxh3_hash64(&file.await?);
482-
let length = length as usize;
483-
if let Some(prefix) = prefix {
484-
format!("{prefix}-{hash:0length$x}{extension}").into()
485-
} else {
486-
format!("{hash:0length$x}{extension}").into()
483+
484+
let output_name = ident
485+
.output_name(self.root_path.clone(), prefix, extension.clone())
486+
.owned()
487+
.await?;
488+
489+
let mut filename = match asset {
490+
Some(asset) => {
491+
let ident = ident.await?;
492+
493+
let mut evaluate = false;
494+
let mut dev_chunk_list = false;
495+
ident.modifiers.iter().for_each(|m| {
496+
if m.contains("evaluate") {
497+
evaluate = true;
487498
}
499+
if m.contains("dev chunk list") {
500+
dev_chunk_list = true;
501+
}
502+
});
503+
let query = QString::from(ident.query.as_str());
504+
let name = if dev_chunk_list {
505+
output_name.as_str()
488506
} else {
489-
bail!(
490-
"chunk_path requires an asset with file content when content hashing is \
491-
enabled"
492-
);
507+
query.get("name").unwrap_or(output_name.as_str())
508+
};
509+
510+
let filename_template = if evaluate {
511+
&self.filename
512+
} else {
513+
&self.chunk_filename
514+
};
515+
516+
match filename_template {
517+
Some(filename) => {
518+
let mut filename = filename.to_string();
519+
520+
if match_name_placeholder(&filename) {
521+
filename = replace_name_placeholder(&filename, name);
522+
}
523+
524+
if match_content_hash_placeholder(&filename) {
525+
let content = asset.content().await?;
526+
if let AssetContent::File(file) = &*content {
527+
let content_hash = hash_xxh3_hash64(&file.await?);
528+
filename = replace_content_hash_placeholder(
529+
&filename,
530+
&format!("{content_hash:016x}"),
531+
);
532+
} else {
533+
bail!(
534+
"chunk_path requires an asset with file content when content \
535+
hashing is enabled"
536+
);
537+
}
538+
};
539+
540+
filename
541+
}
542+
None => name.to_string(),
493543
}
494544
}
545+
None => output_name.to_string(),
495546
};
496-
Ok(chunk_root_path.join(&name)?.cell())
547+
548+
if !filename.ends_with(extension.as_str()) {
549+
filename.push_str(&extension);
550+
}
551+
552+
self.chunk_root_path.join(&filename).map(|p| p.cell())
497553
}
498554

499555
#[turbo_tasks::function]
@@ -820,3 +876,40 @@ struct ChunkPathInfo {
820876
chunk_root_path: FileSystemPath,
821877
content_hashing: Option<ContentHashing>,
822878
}
879+
880+
pub fn clean_separators(s: &str) -> String {
881+
static SEPARATOR_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r".*[/#?]").unwrap());
882+
SEPARATOR_REGEX.replace_all(s, "").to_string()
883+
}
884+
885+
static NAME_PLACEHOLDER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[name\]").unwrap());
886+
887+
pub fn match_name_placeholder(s: &str) -> bool {
888+
NAME_PLACEHOLDER_REGEX.is_match(s)
889+
}
890+
891+
pub fn replace_name_placeholder(s: &str, name: &str) -> String {
892+
NAME_PLACEHOLDER_REGEX.replace_all(s, name).to_string()
893+
}
894+
895+
static CONTENT_HASH_PLACEHOLDER_REGEX: LazyLock<Regex> =
896+
LazyLock::new(|| Regex::new(r"\[contenthash(?::(?P<len>\d+))?\]").unwrap());
897+
898+
pub fn match_content_hash_placeholder(s: &str) -> bool {
899+
CONTENT_HASH_PLACEHOLDER_REGEX.is_match(s)
900+
}
901+
902+
pub fn replace_content_hash_placeholder(s: &str, hash: &str) -> String {
903+
CONTENT_HASH_PLACEHOLDER_REGEX
904+
.replace_all(s, |caps: &regex::Captures| {
905+
let len = caps.name("len").map(|m| m.as_str()).unwrap_or("");
906+
let len = if len.is_empty() {
907+
hash.len()
908+
} else {
909+
len.parse().unwrap_or(hash.len())
910+
};
911+
let len = min(len, hash.len());
912+
hash[..len].to_string()
913+
})
914+
.to_string()
915+
}

turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use crate::{
3636
/// * Contains the Turbopack browser runtime code; and
3737
/// * Evaluates a list of runtime entries.
3838
#[turbo_tasks::value(shared)]
39-
pub(crate) struct EcmascriptBrowserEvaluateChunk {
39+
pub struct EcmascriptBrowserEvaluateChunk {
4040
chunking_context: ResolvedVc<BrowserChunkingContext>,
4141
ident: ResolvedVc<AssetIdent>,
4242
other_chunks: ResolvedVc<OutputAssets>,
@@ -68,13 +68,33 @@ impl EcmascriptBrowserEvaluateChunk {
6868
}
6969

7070
#[turbo_tasks::function]
71-
async fn chunks_data(&self) -> Result<Vc<ChunksData>> {
71+
pub async fn chunks_data(&self) -> Result<Vc<ChunksData>> {
7272
Ok(ChunkData::from_assets(
7373
self.chunking_context.output_root().owned().await?,
7474
*self.other_chunks,
7575
))
7676
}
7777

78+
#[turbo_tasks::function]
79+
pub fn ident(&self) -> Vc<AssetIdent> {
80+
*self.ident
81+
}
82+
83+
#[turbo_tasks::function]
84+
pub fn evaluatable_assets(&self) -> Vc<EvaluatableAssets> {
85+
*self.evaluatable_assets
86+
}
87+
88+
#[turbo_tasks::function]
89+
pub fn module_graph(&self) -> Vc<ModuleGraph> {
90+
*self.module_graph
91+
}
92+
93+
#[turbo_tasks::function]
94+
pub fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
95+
Vc::upcast(*self.chunking_context)
96+
}
97+
7898
#[turbo_tasks::function]
7999
async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
80100
let this = self.await?;

turbopack/crates/turbopack-browser/src/ecmascript/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ pub(crate) mod version;
99

1010
pub use chunk::EcmascriptBrowserChunk;
1111
pub use content::EcmascriptBrowserChunkContent;
12+
pub use evaluate::chunk::EcmascriptBrowserEvaluateChunk;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#![feature(arbitrary_self_types)]
44
#![feature(arbitrary_self_types_pointers)]
55

6-
pub(crate) mod chunking_context;
6+
pub mod chunking_context;
77
pub mod ecmascript;
88
pub mod react_refresh;
99

turbopack/crates/turbopack-core/src/ident.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,10 @@ impl AssetIdent {
363363
// We need to make sure that `.json` and `.json.js` doesn't end up with the same
364364
// name. So when we add an extra extension when want to mark that with a "._"
365365
// suffix.
366-
if !removed_extension {
367-
name += "._";
368-
}
369-
name += &expected_extension;
366+
// if !removed_extension {
367+
// name += "._";
368+
// }
369+
// name += &expected_extension;
370370
Ok(Vc::cell(name.into()))
371371
}
372372
}
@@ -439,5 +439,5 @@ fn clean_separators(s: &str) -> String {
439439
}
440440

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

turbopack/crates/turbopack-core/src/resolve/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ pub enum ExternalType {
391391
EcmaScriptModule,
392392
Global,
393393
Script,
394+
Umd,
394395
}
395396

396397
impl Display for ExternalType {
@@ -401,6 +402,7 @@ impl Display for ExternalType {
401402
ExternalType::Url => write!(f, "url"),
402403
ExternalType::Global => write!(f, "global"),
403404
ExternalType::Script => write!(f, "script"),
405+
ExternalType::Umd => write!(f, "umd"),
404406
}
405407
}
406408
}
@@ -2768,7 +2770,10 @@ async fn resolve_import_map_result(
27682770
ExternalType::EcmaScriptModule => {
27692771
node_esm_resolve_options(alias_lookup_path.root().owned().await?)
27702772
}
2771-
ExternalType::Script | ExternalType::Url | ExternalType::Global => options,
2773+
ExternalType::Script
2774+
| ExternalType::Url
2775+
| ExternalType::Global
2776+
| ExternalType::Umd => options,
27722777
},
27732778
)
27742779
.await?

turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ declare var TURBOPACK_NEXT_CHUNK_URLS: ChunkUrl[] | undefined
2121
declare var CHUNK_BASE_PATH: string
2222
declare var CHUNK_SUFFIX_PATH: string
2323

24+
function normalizeChunkPath(path: string) {
25+
if (path.startsWith('/')) {
26+
path = path.substring(1)
27+
} else if (path.startsWith('./')) {
28+
path = path.substring(2)
29+
}
30+
31+
if (path.endsWith('/')) {
32+
path = path.slice(0, -1)
33+
}
34+
return path
35+
}
36+
37+
const NORMALIZED_CHUNK_BASE_PATH = normalizeChunkPath(CHUNK_BASE_PATH)
38+
2439
interface TurbopackBrowserBaseContext<M> extends TurbopackBaseContext<M> {
2540
R: ResolvePathFromModule
2641
}
@@ -344,7 +359,7 @@ function instantiateRuntimeModule(
344359
* Returns the URL relative to the origin where a chunk can be fetched from.
345360
*/
346361
function getChunkRelativeUrl(chunkPath: ChunkPath | ChunkListPath): ChunkUrl {
347-
return `${CHUNK_BASE_PATH}${chunkPath
362+
return `${NORMALIZED_CHUNK_BASE_PATH}${chunkPath
348363
.split('/')
349364
.map((p) => encodeURIComponent(p))
350365
.join('/')}${CHUNK_SUFFIX_PATH}` as ChunkUrl
@@ -363,13 +378,18 @@ function getPathFromScript(
363378
if (typeof chunkScript === 'string') {
364379
return chunkScript as ChunkPath | ChunkListPath
365380
}
366-
const chunkUrl =
381+
let chunkUrl =
367382
typeof TURBOPACK_NEXT_CHUNK_URLS !== 'undefined'
368383
? TURBOPACK_NEXT_CHUNK_URLS.pop()!
369384
: chunkScript.getAttribute('src')!
385+
if (chunkUrl.startsWith('/')) {
386+
chunkUrl = chunkUrl.substring(1)
387+
} else if (chunkUrl.startsWith('./')) {
388+
chunkUrl = chunkUrl.substring(2)
389+
}
370390
const src = decodeURIComponent(chunkUrl.replace(/[?#].*$/, ''))
371-
const path = src.startsWith(CHUNK_BASE_PATH)
372-
? src.slice(CHUNK_BASE_PATH.length)
391+
const path = src.startsWith(NORMALIZED_CHUNK_BASE_PATH)
392+
? src.slice(NORMALIZED_CHUNK_BASE_PATH.length)
373393
: src
374394
return path as ChunkPath | ChunkListPath
375395
}

0 commit comments

Comments
 (0)