Skip to content

Commit bb74999

Browse files
xusd320fireairforce
andcommitted
feat(turbopack): apply utoo patches to canary (#27)
* feat(turbopack): apply utoo patches to canary * chore: update external code --------- Co-authored-by: zoomdong <[email protected]>
1 parent 10e70b6 commit bb74999

File tree

13 files changed

+200
-46
lines changed

13 files changed

+200
-46
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 & 25 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};
@@ -183,6 +187,16 @@ impl BrowserChunkingContextBuilder {
183187
self
184188
}
185189

190+
pub fn filename(mut self, filename: RcStr) -> Self {
191+
self.chunking_context.filename = Some(filename);
192+
self
193+
}
194+
195+
pub fn chunk_filename(mut self, chunk_filename: RcStr) -> Self {
196+
self.chunking_context.chunk_filename = Some(chunk_filename);
197+
self
198+
}
199+
186200
pub fn build(self) -> Vc<BrowserChunkingContext> {
187201
BrowserChunkingContext::cell(self.chunking_context)
188202
}
@@ -249,6 +263,10 @@ pub struct BrowserChunkingContext {
249263
export_usage: Option<ResolvedVc<ExportUsageInfo>>,
250264
/// The chunking configs
251265
chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
266+
/// Evaluate chunk filename template
267+
filename: Option<RcStr>,
268+
/// Non evaluate chunk filename template
269+
chunk_filename: Option<RcStr>,
252270
}
253271

254272
impl BrowserChunkingContext {
@@ -289,6 +307,8 @@ impl BrowserChunkingContext {
289307
module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
290308
export_usage: None,
291309
chunking_configs: Default::default(),
310+
filename: Default::default(),
311+
chunk_filename: Default::default(),
292312
},
293313
}
294314
}
@@ -438,36 +458,76 @@ impl ChunkingContext for BrowserChunkingContext {
438458
extension.starts_with("."),
439459
"`extension` should include the leading '.', got '{extension}'"
440460
);
441-
let root_path = self.chunk_root_path.clone();
442-
let name = match self.content_hashing {
443-
None => {
444-
ident
445-
.output_name(self.root_path.clone(), prefix, extension)
446-
.owned()
447-
.await?
448-
}
449-
Some(ContentHashing::Direct { length }) => {
450-
let Some(asset) = asset else {
451-
bail!("chunk_path requires an asset when content hashing is enabled");
452-
};
453-
let content = asset.content().await?;
454-
if let AssetContent::File(file) = &*content {
455-
let hash = hash_xxh3_hash64(&file.await?);
456-
let length = length as usize;
457-
if let Some(prefix) = prefix {
458-
format!("{prefix}-{hash:0length$x}{extension}").into()
459-
} else {
460-
format!("{hash:0length$x}{extension}").into()
461+
462+
let output_name = ident
463+
.output_name(self.root_path.clone(), prefix, extension.clone())
464+
.owned()
465+
.await?;
466+
467+
let mut filename = match asset {
468+
Some(asset) => {
469+
let ident = ident.await?;
470+
471+
let mut evaluate = false;
472+
let mut dev_chunk_list = false;
473+
ident.modifiers.iter().for_each(|m| {
474+
if m.contains("evaluate") {
475+
evaluate = true;
461476
}
477+
if m.contains("dev chunk list") {
478+
dev_chunk_list = true;
479+
}
480+
});
481+
let query = QString::from(ident.query.as_str());
482+
let name = if dev_chunk_list {
483+
output_name.as_str()
462484
} else {
463-
bail!(
464-
"chunk_path requires an asset with file content when content hashing is \
465-
enabled"
466-
);
485+
query.get("name").unwrap_or(output_name.as_str())
486+
};
487+
488+
let filename_template = if evaluate {
489+
&self.filename
490+
} else {
491+
&self.chunk_filename
492+
};
493+
494+
match filename_template {
495+
Some(filename) => {
496+
let mut filename = filename.to_string();
497+
498+
if match_name_placeholder(&filename) {
499+
filename = replace_name_placeholder(&filename, name);
500+
}
501+
502+
if match_content_hash_placeholder(&filename) {
503+
let content = asset.content().await?;
504+
if let AssetContent::File(file) = &*content {
505+
let content_hash = hash_xxh3_hash64(&file.await?);
506+
filename = replace_content_hash_placeholder(
507+
&filename,
508+
&format!("{content_hash:016x}"),
509+
);
510+
} else {
511+
bail!(
512+
"chunk_path requires an asset with file content when content \
513+
hashing is enabled"
514+
);
515+
}
516+
};
517+
518+
filename
519+
}
520+
None => name.to_string(),
467521
}
468522
}
523+
None => output_name.to_string(),
469524
};
470-
Ok(root_path.join(&name)?.cell())
525+
526+
if !filename.ends_with(extension.as_str()) {
527+
filename.push_str(&extension);
528+
}
529+
530+
self.chunk_root_path.join(&filename).map(|p| p.cell())
471531
}
472532

473533
#[turbo_tasks::function]
@@ -766,3 +826,40 @@ impl ChunkingContext for BrowserChunkingContext {
766826
}
767827
}
768828
}
829+
830+
pub fn clean_separators(s: &str) -> String {
831+
static SEPARATOR_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r".*[/#?]").unwrap());
832+
SEPARATOR_REGEX.replace_all(s, "").to_string()
833+
}
834+
835+
static NAME_PLACEHOLDER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[name\]").unwrap());
836+
837+
pub fn match_name_placeholder(s: &str) -> bool {
838+
NAME_PLACEHOLDER_REGEX.is_match(s)
839+
}
840+
841+
pub fn replace_name_placeholder(s: &str, name: &str) -> String {
842+
NAME_PLACEHOLDER_REGEX.replace_all(s, name).to_string()
843+
}
844+
845+
static CONTENT_HASH_PLACEHOLDER_REGEX: LazyLock<Regex> =
846+
LazyLock::new(|| Regex::new(r"\[contenthash(?::(?P<len>\d+))?\]").unwrap());
847+
848+
pub fn match_content_hash_placeholder(s: &str) -> bool {
849+
CONTENT_HASH_PLACEHOLDER_REGEX.is_match(s)
850+
}
851+
852+
pub fn replace_content_hash_placeholder(s: &str, hash: &str) -> String {
853+
CONTENT_HASH_PLACEHOLDER_REGEX
854+
.replace_all(s, |caps: &regex::Captures| {
855+
let len = caps.name("len").map(|m| m.as_str()).unwrap_or("");
856+
let len = if len.is_empty() {
857+
hash.len()
858+
} else {
859+
len.parse().unwrap_or(hash.len())
860+
};
861+
let len = min(len, hash.len());
862+
hash[..len].to_string()
863+
})
864+
.to_string()
865+
}

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-css/src/chunk/mod.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ pub mod source_map;
44
use std::fmt::Write;
55

66
use anyhow::{Result, bail};
7-
use swc_core::common::pass::Either;
87
use turbo_rcstr::{RcStr, rcstr};
9-
use turbo_tasks::{
10-
FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueDefault, ValueToString, Vc,
11-
};
8+
use turbo_tasks::{FxIndexSet, ResolvedVc, TryJoinIterExt, ValueDefault, ValueToString, Vc};
129
use turbo_tasks_fs::{
1310
File, FileSystem, FileSystemPath,
1411
rope::{Rope, RopeBuilder},
@@ -35,7 +32,7 @@ use turbopack_core::{
3532
source_map::{GenerateSourceMap, OptionStringifiedSourceMap, utils::fileify_source_map},
3633
};
3734

38-
use self::{single_item_chunk::chunk::SingleItemCssChunk, source_map::CssChunkSourceMapAsset};
35+
use self::source_map::CssChunkSourceMapAsset;
3936
use crate::{ImportAssetReference, util::stringify_js};
4037

4138
#[turbo_tasks::value]
@@ -316,7 +313,7 @@ impl OutputChunk for CssChunk {
316313
};
317314
Ok(OutputChunkRuntimeInfo {
318315
included_ids: Some(ResolvedVc::cell(included_ids)),
319-
module_chunks: Some(ResolvedVc::cell(module_chunks)),
316+
module_chunks: None,
320317
..Default::default()
321318
}
322319
.cell())

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)