Skip to content

Commit 4fe867f

Browse files
committed
review comments
1 parent da52c23 commit 4fe867f

File tree

2 files changed

+132
-84
lines changed
  • turbopack/crates
    • turbopack-core/src/resolve
    • turbopack-ecmascript/src/references/esm

2 files changed

+132
-84
lines changed

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

Lines changed: 114 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,23 +2258,25 @@ async fn resolve_relative_request(
22582258

22592259
if options_value.enable_typescript_with_output_extension {
22602260
let replaced = new_path.replace_final_constants(&mut |c: &RcStr| -> Option<Pattern> {
2261-
let (base, replacement) = match c.rsplit_once(".") {
2262-
Some((base, "js")) => (base, vec![rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
2263-
Some((base, "mjs")) => (base, vec![rcstr!(".mts"), rcstr!(".mjs")]),
2264-
Some((base, "cjs")) => (base, vec![rcstr!(".cts"), rcstr!(".cjs")]),
2261+
let (base, replacement): (&str, &[RcStr]) = match c.rsplit_once(".") {
2262+
Some((base, "js")) => (base, &[rcstr!(".ts"), rcstr!(".tsx"), rcstr!(".js")]),
2263+
Some((base, "mjs")) => (base, &[rcstr!(".mts"), rcstr!(".mjs")]),
2264+
Some((base, "cjs")) => (base, &[rcstr!(".cts"), rcstr!(".cjs")]),
22652265
_ => {
22662266
return None;
22672267
}
22682268
};
22692269
added_extension_alternatives.extend(replacement.iter().cloned());
22702270
if base.is_empty() {
22712271
Some(Pattern::Alternatives(
2272-
replacement.into_iter().map(Pattern::Constant).collect(),
2272+
replacement.iter().cloned().map(Pattern::Constant).collect(),
22732273
))
22742274
} else {
22752275
Some(Pattern::Concatenation(vec![
22762276
Pattern::Constant(base.into()),
2277-
Pattern::Alternatives(replacement.into_iter().map(Pattern::Constant).collect()),
2277+
Pattern::Alternatives(
2278+
replacement.iter().cloned().map(Pattern::Constant).collect(),
2279+
),
22782280
]))
22792281
}
22802282
});
@@ -2321,6 +2323,10 @@ async fn resolve_relative_request(
23212323
continue;
23222324
};
23232325

2326+
// Check if we've already seen this filename without the extension. If so, skip
2327+
// this match since we already processed a higher-priority extension.
2328+
// Only do this check if the pattern doesn't contain dynamic parts - for dynamic
2329+
// imports, we need multiple keys for runtime resolution.
23242330
if !seen_base_patterns.insert(matched_pattern) {
23252331
continue 'matches; // Skip this entire file
23262332
}
@@ -3347,88 +3353,88 @@ mod tests {
33473353

33483354
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33493355
async fn test_explicit_js_resolves_to_ts() {
3350-
resolve_relative_request_test(
3351-
vec!["foo.js", "foo.ts"],
3352-
rcstr!("./foo.js").into(),
3353-
true,
3354-
false,
3355-
vec![("./foo.ts", "foo.ts")],
3356-
)
3356+
resolve_relative_request_test(ResolveTestParams {
3357+
files: vec!["foo.js", "foo.ts"],
3358+
pattern: rcstr!("./foo.js").into(),
3359+
enable_typescript_with_output_extension: true,
3360+
fully_specified: false,
3361+
expected: vec![("./foo.ts", "foo.ts")],
3362+
})
33573363
.await;
33583364
}
33593365

33603366
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33613367
async fn test_implicit_request_ts_priority() {
3362-
resolve_relative_request_test(
3363-
vec!["foo.js", "foo.ts"],
3364-
rcstr!("./foo").into(),
3365-
true,
3366-
false,
3367-
vec![("./foo", "foo.ts")], // Implicit resolution uses extensionless key
3368-
)
3368+
resolve_relative_request_test(ResolveTestParams {
3369+
files: vec!["foo.js", "foo.ts"],
3370+
pattern: rcstr!("./foo").into(),
3371+
enable_typescript_with_output_extension: true,
3372+
fully_specified: false,
3373+
expected: vec![("./foo", "foo.ts")], // Implicit resolution uses extensionless key
3374+
})
33693375
.await;
33703376
}
33713377

33723378
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33733379
async fn test_ts_priority_over_json() {
3374-
resolve_relative_request_test(
3375-
vec!["posts.json", "posts.ts"],
3376-
rcstr!("./posts").into(),
3377-
true,
3378-
false,
3379-
vec![("./posts", "posts.ts")], // Implicit resolution uses extensionless key
3380-
)
3380+
resolve_relative_request_test(ResolveTestParams {
3381+
files: vec!["posts.json", "posts.ts"],
3382+
pattern: rcstr!("./posts").into(),
3383+
enable_typescript_with_output_extension: true,
3384+
fully_specified: false,
3385+
expected: vec![("./posts", "posts.ts")], // Implicit resolution uses extensionless key
3386+
})
33813387
.await;
33823388
}
33833389

33843390
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33853391
async fn test_only_js_file_no_ts() {
3386-
resolve_relative_request_test(
3387-
vec!["bar.js"],
3388-
rcstr!("./bar.js").into(),
3389-
true,
3390-
false,
3391-
vec![("./bar.js", "bar.js")],
3392-
)
3392+
resolve_relative_request_test(ResolveTestParams {
3393+
files: vec!["bar.js"],
3394+
pattern: rcstr!("./bar.js").into(),
3395+
enable_typescript_with_output_extension: true,
3396+
fully_specified: false,
3397+
expected: vec![("./bar.js", "bar.js")],
3398+
})
33933399
.await;
33943400
}
33953401

33963402
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
33973403
async fn test_explicit_ts_request() {
3398-
resolve_relative_request_test(
3399-
vec!["foo.js", "foo.ts"],
3400-
rcstr!("./foo.ts").into(),
3401-
true,
3402-
false,
3403-
vec![("./foo.ts", "foo.ts")],
3404-
)
3404+
resolve_relative_request_test(ResolveTestParams {
3405+
files: vec!["foo.js", "foo.ts"],
3406+
pattern: rcstr!("./foo.ts").into(),
3407+
enable_typescript_with_output_extension: true,
3408+
fully_specified: false,
3409+
expected: vec![("./foo.ts", "foo.ts")],
3410+
})
34053411
.await;
34063412
}
34073413

34083414
// Fragment handling tests
34093415
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
34103416
async fn test_fragment_as_part_of_filename() {
34113417
// When a file literally contains '#' in its name, it should be preserved
3412-
resolve_relative_request_test(
3413-
vec!["client#component.js", "client#component.ts"],
3414-
rcstr!("./client#component.js").into(),
3415-
true,
3416-
false,
3417-
vec![("./client#component.ts", "client#component.ts")],
3418-
)
3418+
resolve_relative_request_test(ResolveTestParams {
3419+
files: vec!["client#component.js", "client#component.ts"],
3420+
pattern: rcstr!("./client#component.js").into(),
3421+
enable_typescript_with_output_extension: true,
3422+
fully_specified: false,
3423+
expected: vec![("./client#component.ts", "client#component.ts")],
3424+
})
34193425
.await;
34203426
}
34213427

34223428
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
34233429
async fn test_fragment_with_ts_priority() {
34243430
// Fragment handling with extension priority
3425-
resolve_relative_request_test(
3426-
vec!["page#section.js", "page#section.ts"],
3427-
rcstr!("./page#section").into(),
3428-
true,
3429-
false,
3430-
vec![("./page#section", "page#section.ts")],
3431-
)
3431+
resolve_relative_request_test(ResolveTestParams {
3432+
files: vec!["page#section.js", "page#section.ts"],
3433+
pattern: rcstr!("./page#section").into(),
3434+
enable_typescript_with_output_extension: true,
3435+
fully_specified: false,
3436+
expected: vec![("./page#section", "page#section.ts")],
3437+
})
34323438
.await;
34333439
}
34343440

@@ -3437,21 +3443,21 @@ mod tests {
34373443
async fn test_dynamic_pattern_with_js_extension() {
34383444
// Pattern: ./src/*.js should generate multiple keys with .ts priority
34393445
// When both foo.js and foo.ts exist, dynamic patterns need both keys for runtime resolution
3440-
// Results are sorted alphabetically by key
3441-
resolve_relative_request_test(
3442-
vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3443-
Pattern::Concatenation(vec![
3446+
// Results maintain priority order: .ts files before .js files
3447+
resolve_relative_request_test(ResolveTestParams {
3448+
files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3449+
pattern: Pattern::Concatenation(vec![
34443450
Pattern::Constant(rcstr!("./src/")),
34453451
Pattern::Dynamic,
34463452
Pattern::Constant(rcstr!(".js")),
34473453
]),
3448-
true,
3449-
false,
3450-
vec![
3454+
enable_typescript_with_output_extension: true,
3455+
fully_specified: false,
3456+
expected: vec![
34513457
("./src/foo.ts", "src/foo.ts"),
34523458
("./src/bar.js", "src/bar.js"),
34533459
],
3454-
)
3460+
})
34553461
.await;
34563462
}
34573463

@@ -3460,29 +3466,62 @@ mod tests {
34603466
// Pattern: ./src/* (no extension) with TypeScript priority
34613467
// Dynamic patterns generate keys for all matched files, including extension alternatives
34623468
// Results are sorted alphabetically by key
3463-
resolve_relative_request_test(
3464-
vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3465-
Pattern::Concatenation(vec![Pattern::Constant(rcstr!("./src/")), Pattern::Dynamic]),
3466-
true,
3467-
false,
3468-
vec![
3469+
resolve_relative_request_test(ResolveTestParams {
3470+
files: vec!["src/foo.js", "src/foo.ts", "src/bar.js"],
3471+
pattern: Pattern::Concatenation(vec![
3472+
Pattern::Constant(rcstr!("./src/")),
3473+
Pattern::Dynamic,
3474+
]),
3475+
enable_typescript_with_output_extension: true,
3476+
fully_specified: false,
3477+
expected: vec![
34693478
("./src/bar", "src/bar.js"),
34703479
("./src/bar.js", "src/bar.js"),
34713480
("./src/foo", "src/foo.js"),
34723481
("./src/foo.js", "src/foo.js"),
34733482
],
3474-
)
3483+
})
34753484
.await;
34763485
}
34773486

3478-
/// Helper function to run a single extension priority test case
3479-
async fn resolve_relative_request_test(
3480-
files: Vec<&str>,
3487+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
3488+
async fn test_dts_shouldnt_be_selected() {
3489+
// Pattern: ./src/* (no extension) with TypeScript priority
3490+
// Dynamic patterns generate keys for all matched files, including extension alternatives
3491+
// Results are sorted alphabetically by key
3492+
resolve_relative_request_test(ResolveTestParams {
3493+
files: vec!["foo.js", "foo.ts", "foo.d.ts"],
3494+
pattern: rcstr!("foo.js").into(),
3495+
enable_typescript_with_output_extension: true,
3496+
fully_specified: false,
3497+
expected: vec![("foo.ts", "foo.ts")],
3498+
})
3499+
.await;
3500+
}
3501+
3502+
/// Test parameters for resolve_relative_request_test
3503+
struct ResolveTestParams<'a> {
3504+
/// Files to create in the test filesystem
3505+
files: Vec<&'a str>,
3506+
/// Pattern to resolve
34813507
pattern: Pattern,
3508+
/// Whether to enable TypeScript output extension transformation
34823509
enable_typescript_with_output_extension: bool,
3510+
/// Whether to require fully specified imports
34833511
fully_specified: bool,
3484-
expected: Vec<(&str, &str)>,
3485-
) {
3512+
/// Expected (request_key, resolved_path) pairs
3513+
expected: Vec<(&'a str, &'a str)>,
3514+
}
3515+
3516+
/// Helper function to run a single extension priority test case
3517+
async fn resolve_relative_request_test(params: ResolveTestParams<'_>) {
3518+
let ResolveTestParams {
3519+
files,
3520+
pattern,
3521+
enable_typescript_with_output_extension,
3522+
fully_specified,
3523+
expected,
3524+
} = params;
34863525
let scratch = tempfile::tempdir().unwrap();
34873526
{
34883527
let path = scratch.path();

turbopack/crates/turbopack-ecmascript/src/references/esm/base.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use swc_core::{
1010
quote,
1111
};
1212
use turbo_rcstr::{RcStr, rcstr};
13-
use turbo_tasks::{ResolvedVc, ValueToString, Vc};
13+
use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
1414
use turbo_tasks_fs::FileSystemPath;
1515
use turbopack_core::{
1616
chunk::{
@@ -459,16 +459,25 @@ impl ModuleReference for EsmAssetReference {
459459
Some(self.issue_source),
460460
)
461461
.await?;
462-
let modules = result.primary_modules().await?;
463-
debug_assert!(
464-
modules.len() <= 1,
465-
"EsmAssetReference request {request} resolved to {num} results",
466-
request = &self.request,
467-
num = modules.len()
468-
);
462+
if cfg!(debug_assertions) {
463+
let modules = result.primary_modules().await?;
464+
if modules.len() > 1 {
465+
panic!(
466+
"EsmAssetReference request '{request}' resolved to {num} \
467+
results:\n{detailed:?}",
468+
request = &self.request,
469+
num = modules.len(),
470+
detailed = modules
471+
.iter()
472+
.map(|m| m.ident().to_string())
473+
.try_join()
474+
.await?
475+
);
476+
}
477+
}
469478

470479
if let Some(ModulePart::Export(export_name)) = &self.export_name {
471-
for &module in modules {
480+
for &module in result.primary_modules().await? {
472481
if let Some(module) = ResolvedVc::try_downcast(module)
473482
&& *is_export_missing(*module, export_name.clone()).await?
474483
{

0 commit comments

Comments
 (0)