turbopack_resolve/
resolve.rs

1use anyhow::Result;
2use next_taskless::{BUN_EXTERNALS, EDGE_NODE_EXTERNALS, NODE_EXTERNALS};
3use turbo_rcstr::rcstr;
4use turbo_tasks::{ResolvedVc, Vc};
5use turbo_tasks_fs::{FileSystem, FileSystemPath};
6use turbopack_core::resolve::{
7    AliasMap, AliasPattern, ExternalTraced, ExternalType, FindContextFileResult, find_context_file,
8    options::{
9        ConditionValue, ImportMap, ImportMapping, ResolutionConditions, ResolveInPackage,
10        ResolveIntoPackage, ResolveModules, ResolveOptions,
11    },
12};
13
14use crate::{
15    resolve_options_context::ResolveOptionsContext,
16    typescript::{apply_tsconfig_resolve_options, tsconfig, tsconfig_resolve_options},
17};
18
19#[turbo_tasks::function]
20async fn base_resolve_options(
21    fs: ResolvedVc<Box<dyn FileSystem>>,
22    options_context: Vc<ResolveOptionsContext>,
23) -> Result<Vc<ResolveOptions>> {
24    let opt = options_context.await?;
25    let emulating = opt.emulate_environment;
26    let root = fs.root().owned().await?;
27    let mut direct_mappings = AliasMap::new();
28    let node_externals = if let Some(environment) = emulating {
29        environment.node_externals().owned().await?
30    } else {
31        opt.enable_node_externals
32    };
33    let untraced_external_cell =
34        ImportMapping::External(None, ExternalType::CommonJs, ExternalTraced::Untraced)
35            .resolved_cell();
36
37    for req in BUN_EXTERNALS {
38        direct_mappings.insert(AliasPattern::exact(req), untraced_external_cell);
39    }
40
41    if node_externals || opt.enable_edge_node_externals {
42        if node_externals {
43            for req in NODE_EXTERNALS {
44                direct_mappings.insert(AliasPattern::exact(req), untraced_external_cell);
45                direct_mappings.insert(
46                    AliasPattern::exact(format!("node:{req}")),
47                    untraced_external_cell,
48                );
49            }
50        }
51
52        if opt.enable_edge_node_externals {
53            for req in EDGE_NODE_EXTERNALS {
54                direct_mappings.insert(
55                    AliasPattern::exact(req),
56                    ImportMapping::External(
57                        Some(format!("node:{req}").into()),
58                        ExternalType::CommonJs,
59                        ExternalTraced::Untraced,
60                    )
61                    .resolved_cell(),
62                );
63                direct_mappings.insert(
64                    AliasPattern::exact(format!("node:{req}")),
65                    untraced_external_cell,
66                );
67            }
68        }
69    }
70
71    let mut import_map = ImportMap::new(direct_mappings);
72    if let Some(additional_import_map) = opt.import_map {
73        let additional_import_map = additional_import_map.await?;
74        import_map.extend_ref(&additional_import_map);
75    }
76    let import_map = import_map.resolved_cell();
77
78    let conditions = {
79        let mut conditions: ResolutionConditions = [
80            (rcstr!("import"), ConditionValue::Unknown),
81            (rcstr!("require"), ConditionValue::Unknown),
82        ]
83        .into_iter()
84        .collect();
85        if opt.browser {
86            conditions.insert(rcstr!("browser"), ConditionValue::Set);
87        }
88        if opt.module {
89            conditions.insert(rcstr!("module"), ConditionValue::Set);
90        }
91        if let Some(environment) = emulating {
92            for condition in environment.resolve_conditions().await?.iter() {
93                conditions.insert(condition.clone(), ConditionValue::Set);
94            }
95        }
96        for condition in opt.custom_conditions.iter() {
97            conditions.insert(condition.clone(), ConditionValue::Set);
98        }
99        // Infer some well-known conditions
100        let dev = conditions.get("development").cloned();
101        let prod = conditions.get("production").cloned();
102        if prod.is_none() {
103            conditions.insert(
104                rcstr!("production"),
105                if matches!(dev, Some(ConditionValue::Set)) {
106                    ConditionValue::Unset
107                } else {
108                    ConditionValue::Unknown
109                },
110            );
111        }
112        if dev.is_none() {
113            conditions.insert(
114                rcstr!("development"),
115                if matches!(prod, Some(ConditionValue::Set)) {
116                    ConditionValue::Unset
117                } else {
118                    ConditionValue::Unknown
119                },
120            );
121        }
122        conditions
123    };
124
125    let extensions = if let Some(custom_extension) = &opt.custom_extensions {
126        custom_extension.clone()
127    } else if let Some(environment) = emulating {
128        environment.resolve_extensions().owned().await?
129    } else {
130        let mut ext = Vec::new();
131        if opt.enable_typescript && opt.enable_react {
132            ext.push(rcstr!(".tsx"));
133        }
134        if opt.enable_typescript {
135            ext.push(rcstr!(".ts"));
136        }
137        if opt.enable_react {
138            ext.push(rcstr!(".jsx"));
139        }
140        ext.push(rcstr!(".js"));
141        if opt.enable_mjs_extension {
142            ext.push(rcstr!(".mjs"));
143        }
144        if opt.enable_node_native_modules {
145            ext.push(rcstr!(".node"));
146        }
147        ext.push(rcstr!(".json"));
148        ext
149    };
150    Ok(ResolveOptions {
151        extensions,
152        modules: if let Some(environment) = emulating {
153            if *environment.resolve_node_modules().await? {
154                vec![ResolveModules::Nested(
155                    root.clone(),
156                    vec![rcstr!("node_modules")],
157                )]
158            } else {
159                Vec::new()
160            }
161        } else {
162            let mut mods = Vec::new();
163            if let Some(dir) = &opt.enable_node_modules {
164                mods.push(ResolveModules::Nested(
165                    dir.clone(),
166                    vec![rcstr!("node_modules")],
167                ));
168            }
169            mods
170        },
171        into_package: {
172            let mut resolve_into = vec![ResolveIntoPackage::ExportsField {
173                conditions: conditions.clone(),
174                unspecified_conditions: ConditionValue::Unset,
175            }];
176            if opt.browser {
177                resolve_into.push(ResolveIntoPackage::MainField {
178                    field: rcstr!("browser"),
179                });
180            }
181            if opt.module {
182                resolve_into.push(ResolveIntoPackage::MainField {
183                    field: rcstr!("module"),
184                });
185            }
186            resolve_into.push(ResolveIntoPackage::MainField {
187                field: rcstr!("main"),
188            });
189            resolve_into
190        },
191        in_package: {
192            let mut resolve_in = vec![ResolveInPackage::ImportsField {
193                conditions,
194                unspecified_conditions: ConditionValue::Unset,
195            }];
196            if opt.browser {
197                resolve_in.push(ResolveInPackage::AliasField(rcstr!("browser")));
198            }
199            resolve_in
200        },
201        default_files: vec![rcstr!("index")],
202        import_map: Some(import_map),
203        resolved_map: opt.resolved_map,
204        after_resolve_plugins: opt.after_resolve_plugins.clone(),
205        before_resolve_plugins: opt.before_resolve_plugins.clone(),
206        loose_errors: opt.loose_errors,
207        collect_affecting_sources: opt.collect_affecting_sources,
208        ..Default::default()
209    }
210    .cell())
211}
212
213#[turbo_tasks::function]
214pub async fn resolve_options(
215    resolve_path: FileSystemPath,
216    options_context: Vc<ResolveOptionsContext>,
217) -> Result<Vc<ResolveOptions>> {
218    let options_context_value = options_context.await?;
219    if !options_context_value.rules.is_empty() {
220        for (condition, new_options_context) in options_context_value.rules.iter() {
221            if condition.matches(&resolve_path) {
222                return Ok(resolve_options(resolve_path, **new_options_context));
223            }
224        }
225    }
226
227    let resolve_options = base_resolve_options(*resolve_path.fs, options_context);
228
229    let resolve_options = if options_context_value.enable_typescript {
230        let find_tsconfig = async || {
231            // Otherwise, attempt to find a tsconfig up the file tree
232            let tsconfig = find_context_file(
233                resolve_path.clone(),
234                tsconfig(),
235                options_context_value.collect_affecting_sources,
236            )
237            .await?;
238            anyhow::Ok::<Vc<ResolveOptions>>(match &*tsconfig {
239                FindContextFileResult::Found(path, _) => apply_tsconfig_resolve_options(
240                    resolve_options,
241                    tsconfig_resolve_options(path.clone()),
242                ),
243                FindContextFileResult::NotFound(_) => resolve_options,
244            })
245        };
246
247        // Use a specified tsconfig path if provided. In Next.js, this is always provided by the
248        // default config, at the very least.
249        if let Some(tsconfig_path) = &options_context_value.tsconfig_path {
250            let meta = tsconfig_path.metadata().await;
251            if meta.is_ok() {
252                // If the file exists, use it.
253                apply_tsconfig_resolve_options(
254                    resolve_options,
255                    tsconfig_resolve_options(tsconfig_path.clone()),
256                )
257            } else {
258                // Otherwise, try and find one.
259                // TODO: If the user provides a tsconfig.json explicitly, this should fail
260                // explicitly. Currently implemented this way for parity with webpack.
261                find_tsconfig().await?
262            }
263        } else {
264            find_tsconfig().await?
265        }
266    } else {
267        resolve_options
268    };
269
270    // Make sure to always apply `options_context.import_map` last, so it properly
271    // overwrites any other mappings.
272    let resolve_options = options_context_value
273        .import_map
274        .map(|import_map| resolve_options.with_extended_import_map(*import_map))
275        .unwrap_or(resolve_options);
276    // And the same for the fallback_import_map
277    let resolve_options = options_context_value
278        .fallback_import_map
279        .map(|fallback_import_map| {
280            resolve_options.with_extended_fallback_import_map(*fallback_import_map)
281        })
282        .unwrap_or(resolve_options);
283
284    Ok(resolve_options)
285}