Skip to content

Commit 78459a2

Browse files
authored
Turbopack: support pattern into exports field (#82757)
Support patterns into `exports` field mappings - for exact mappings (such as `"./foo": "./src/foo/index.js"`), all patterns work - for wildcard mappings (such as `"./*": "./src/*/index.js"`), only basic patterns of `prefix<dynamic>suffix` are supported.
1 parent 3596c03 commit 78459a2

File tree

20 files changed

+586
-242
lines changed

20 files changed

+586
-242
lines changed

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

Lines changed: 255 additions & 139 deletions
Large diffs are not rendered by default.

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

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ use crate::{
4242
raw_module::RawModule,
4343
reference_type::ReferenceType,
4444
resolve::{
45+
alias_map::AliasKey,
4546
node::{node_cjs_resolve_options, node_esm_resolve_options},
4647
parse::stringify_data_uri,
4748
pattern::{PatternMatch, read_matches},
4849
plugin::AfterResolvePlugin,
50+
remap::ReplacedSubpathValueResult,
4951
},
5052
source::{OptionSource, Source, Sources},
5153
};
@@ -790,7 +792,7 @@ impl ResolveResult {
790792
}
791793
}
792794

793-
pub fn add_conditions<'a>(&mut self, conditions: impl IntoIterator<Item = (&'a str, bool)>) {
795+
pub fn add_conditions(&mut self, conditions: impl IntoIterator<Item = (RcStr, bool)>) {
794796
let mut primary = std::mem::take(&mut self.primary);
795797
for (k, v) in conditions {
796798
for (key, _) in primary.iter_mut() {
@@ -2694,6 +2696,8 @@ async fn resolve_into_package(
26942696
let is_root_match = path.is_match("") || path.is_match("/");
26952697
let could_match_others = path.could_match_others("");
26962698

2699+
let mut export_path_request = path.clone();
2700+
export_path_request.push_front(rcstr!(".").into());
26972701
for resolve_into_package in options_value.into_package.iter() {
26982702
match resolve_into_package {
26992703
// handled by the `resolve_into_folder` call below
@@ -2709,23 +2713,13 @@ async fn resolve_into_package(
27092713
continue;
27102714
};
27112715

2712-
let Some(path) = path.as_constant_string() else {
2713-
bail!("pattern into an exports field is not implemented yet");
2714-
};
2715-
2716-
let path = if path == "/" {
2717-
rcstr!(".")
2718-
} else {
2719-
format!(".{path}").into()
2720-
};
2721-
27222716
results.push(
27232717
handle_exports_imports_field(
27242718
package_path.clone(),
27252719
package_json_path,
27262720
*options,
27272721
exports_field,
2728-
&path,
2722+
export_path_request.clone(),
27292723
conditions,
27302724
unspecified_conditions,
27312725
query,
@@ -2931,55 +2925,94 @@ async fn handle_exports_imports_field(
29312925
package_json_path: FileSystemPath,
29322926
options: Vc<ResolveOptions>,
29332927
exports_imports_field: &AliasMap<SubpathValue>,
2934-
path: &str,
2928+
mut path: Pattern,
29352929
conditions: &BTreeMap<RcStr, ConditionValue>,
29362930
unspecified_conditions: &ConditionValue,
29372931
query: RcStr,
29382932
) -> Result<Vc<ResolveResult>> {
29392933
let mut results = Vec::new();
29402934
let mut conditions_state = FxHashMap::default();
29412935

2942-
let req = Pattern::Constant(format!("{path}{query}").into());
2943-
2944-
let values = exports_imports_field
2945-
.lookup(&req)
2946-
.map(AliasMatch::try_into_self)
2947-
.collect::<Result<Vec<_>>>()?;
2936+
if !query.is_empty() {
2937+
path.push(query.into());
2938+
}
2939+
let req = path;
29482940

2949-
for value in values.iter() {
2950-
if value.add_results(
2941+
let values = exports_imports_field.lookup(&req);
2942+
for value in values {
2943+
let value = value?;
2944+
if value.output.add_results(
2945+
value.prefix,
2946+
value.key,
29512947
conditions,
29522948
unspecified_conditions,
29532949
&mut conditions_state,
29542950
&mut results,
29552951
) {
2952+
// Match found, stop (leveraging the lazy `lookup` iterator).
29562953
break;
29572954
}
29582955
}
29592956

29602957
let mut resolved_results = Vec::new();
2961-
for (result_path, conditions) in results {
2958+
for ReplacedSubpathValueResult {
2959+
result_path,
2960+
conditions,
2961+
map_prefix,
2962+
map_key,
2963+
} in results
2964+
{
29622965
if let Some(result_path) = result_path.with_normalized_path() {
29632966
let request = Request::parse(Pattern::Concatenation(vec![
29642967
Pattern::Constant(rcstr!("./")),
2965-
result_path,
2968+
result_path.clone(),
29662969
]))
2967-
.to_resolved()
2970+
.resolve()
29682971
.await?;
29692972

29702973
let resolve_result = Box::pin(resolve_internal_inline(
29712974
package_path.clone(),
2972-
*request,
2975+
request,
29732976
options,
29742977
))
29752978
.await?;
2976-
if conditions.is_empty() {
2977-
resolved_results.push(resolve_result.with_request(path.into()));
2979+
2980+
let resolve_result = if let Some(req) = req.as_constant_string() {
2981+
resolve_result.with_request(req.clone())
29782982
} else {
2979-
let mut resolve_result = resolve_result.await?.with_request_ref(path.into());
2983+
match map_key {
2984+
AliasKey::Exact => resolve_result.with_request(map_prefix.clone().into()),
2985+
AliasKey::Wildcard { .. } => {
2986+
// - `req` is the user's request (key of the export map)
2987+
// - `result_path` is the final request (value of the export map), so
2988+
// effectively `'{foo}*{bar}'`
2989+
2990+
// Because of the assertion in AliasMapLookupIterator, `req` is of the
2991+
// form:
2992+
// - "prefix...<dynamic>" or
2993+
// - "prefix...<dynamic>...suffix"
2994+
2995+
let mut old_request_key = result_path;
2996+
// Remove the Pattern::Constant(rcstr!("./")), from above again
2997+
old_request_key.push_front(rcstr!("./").into());
2998+
let new_request_key = req.clone();
2999+
3000+
resolve_result.with_replaced_request_key_pattern(
3001+
Pattern::new(old_request_key),
3002+
Pattern::new(new_request_key),
3003+
)
3004+
}
3005+
}
3006+
};
3007+
3008+
let resolve_result = if !conditions.is_empty() {
3009+
let mut resolve_result = resolve_result.owned().await?;
29803010
resolve_result.add_conditions(conditions);
2981-
resolved_results.push(resolve_result.cell());
2982-
}
3011+
resolve_result.cell()
3012+
} else {
3013+
resolve_result
3014+
};
3015+
resolved_results.push(resolve_result);
29833016
}
29843017
}
29853018

@@ -3034,7 +3067,7 @@ async fn resolve_package_internal_with_imports_field(
30343067
package_json_path.clone(),
30353068
resolve_options,
30363069
imports,
3037-
specifier,
3070+
Pattern::Constant(specifier.clone()),
30383071
conditions,
30393072
unspecified_conditions,
30403073
RcStr::default(),

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,10 @@ async fn import_mapping_to_result(
418418
} else if let Some(request) = request.await?.request() {
419419
request
420420
} else {
421-
bail!("Cannot resolve external reference without request")
421+
bail!(
422+
"Cannot resolve external reference with dynamic request {:?}",
423+
request.request_pattern().await?.describe_as_string()
424+
)
422425
},
423426
*ty,
424427
*traced,
@@ -434,7 +437,10 @@ async fn import_mapping_to_result(
434437
} else if let Some(request) = request.await?.request() {
435438
request
436439
} else {
437-
bail!("Cannot resolve external reference without request")
440+
bail!(
441+
"Cannot resolve external reference with dynamic request {:?}",
442+
request.request_pattern().await?.describe_as_string()
443+
)
438444
},
439445
ty: *ty,
440446
traced: *traced,
@@ -517,6 +523,12 @@ impl ImportMap {
517523
// relative requests must not match global wildcard aliases.
518524

519525
let request_pattern = request.request_pattern().await?;
526+
if matches!(*request_pattern, Pattern::Dynamic | Pattern::DynamicNoSlash) {
527+
// You could probably conceive of cases where this isn't correct. But the dynamic will
528+
// just match every single entry in the import map, which is not what we want.
529+
return Ok(ImportMapResult::NoEntry);
530+
}
531+
520532
let (req_rel, rest) = request_pattern.split_could_match("./");
521533
let (req_rel_parent, req_rest) =
522534
rest.map(|r| r.split_could_match("../")).unwrap_or_default();
@@ -540,12 +552,7 @@ impl ImportMap {
540552
.chain(lookup_rel_parent.into_iter())
541553
.chain(lookup.into_iter())
542554
.map(async |result| {
543-
import_mapping_to_result(
544-
*result.try_join_into_self().await?,
545-
lookup_path.clone(),
546-
request,
547-
)
548-
.await
555+
import_mapping_to_result(*result?.output.await?, lookup_path.clone(), request).await
549556
})
550557
.try_join()
551558
.await?;

0 commit comments

Comments
 (0)