Skip to main content

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, TsConfigHandling},
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 opt.module_sync != ConditionValue::Unset {
92            conditions.insert(rcstr!("module-sync"), opt.module_sync);
93        }
94        if let Some(environment) = emulating {
95            for condition in environment.resolve_conditions().await?.iter() {
96                conditions.insert(condition.clone(), ConditionValue::Set);
97            }
98        }
99        for condition in opt.custom_conditions.iter() {
100            conditions.insert(condition.clone(), ConditionValue::Set);
101        }
102        // Infer some well-known conditions
103        let dev = conditions.get("development").cloned();
104        let prod = conditions.get("production").cloned();
105        if prod.is_none() {
106            conditions.insert(
107                rcstr!("production"),
108                if matches!(dev, Some(ConditionValue::Set)) {
109                    ConditionValue::Unset
110                } else {
111                    ConditionValue::Unknown
112                },
113            );
114        }
115        if dev.is_none() {
116            conditions.insert(
117                rcstr!("development"),
118                if matches!(prod, Some(ConditionValue::Set)) {
119                    ConditionValue::Unset
120                } else {
121                    ConditionValue::Unknown
122                },
123            );
124        }
125        conditions
126    };
127
128    let extensions = if let Some(custom_extension) = &opt.custom_extensions {
129        custom_extension.clone()
130    } else if let Some(environment) = emulating {
131        environment.resolve_extensions().owned().await?
132    } else {
133        let mut ext = Vec::new();
134        if opt.enable_typescript && opt.enable_react {
135            ext.push(rcstr!(".tsx"));
136        }
137        if opt.enable_typescript {
138            ext.push(rcstr!(".ts"));
139        }
140        if opt.enable_react {
141            ext.push(rcstr!(".jsx"));
142        }
143        ext.push(rcstr!(".js"));
144        if opt.enable_mjs_extension {
145            ext.push(rcstr!(".mjs"));
146        }
147        if opt.enable_node_native_modules {
148            ext.push(rcstr!(".node"));
149        }
150        ext.push(rcstr!(".json"));
151        ext
152    };
153    Ok(ResolveOptions {
154        extensions,
155        modules: if let Some(environment) = emulating {
156            if *environment.resolve_node_modules().await? {
157                vec![ResolveModules::Nested(
158                    root.clone(),
159                    vec![rcstr!("node_modules")],
160                )]
161            } else {
162                Vec::new()
163            }
164        } else {
165            let mut mods = Vec::new();
166            if let Some(dir) = &opt.enable_node_modules {
167                mods.push(ResolveModules::Nested(
168                    dir.clone(),
169                    vec![rcstr!("node_modules")],
170                ));
171            }
172            mods
173        },
174        into_package: {
175            let mut resolve_into = vec![ResolveIntoPackage::ExportsField {
176                conditions: conditions.clone(),
177                unspecified_conditions: ConditionValue::Unset,
178            }];
179            if opt.browser {
180                resolve_into.push(ResolveIntoPackage::MainField {
181                    field: rcstr!("browser"),
182                });
183            }
184            if opt.module {
185                resolve_into.push(ResolveIntoPackage::MainField {
186                    field: rcstr!("module"),
187                });
188            }
189            resolve_into.push(ResolveIntoPackage::MainField {
190                field: rcstr!("main"),
191            });
192            resolve_into
193        },
194        in_package: {
195            let mut resolve_in = vec![ResolveInPackage::ImportsField {
196                conditions,
197                unspecified_conditions: ConditionValue::Unset,
198            }];
199            if opt.browser {
200                resolve_in.push(ResolveInPackage::AliasField(rcstr!("browser")));
201            }
202            resolve_in
203        },
204        default_files: vec![rcstr!("index")],
205        import_map: Some(import_map),
206        resolved_map: opt.resolved_map,
207        after_resolve_plugins: opt.after_resolve_plugins.clone(),
208        before_resolve_plugins: opt.before_resolve_plugins.clone(),
209        loose_errors: opt.loose_errors,
210        collect_affecting_sources: opt.collect_affecting_sources,
211        ..Default::default()
212    }
213    .cell())
214}
215
216#[turbo_tasks::function]
217pub async fn resolve_options(
218    resolve_path: FileSystemPath,
219    options_context: Vc<ResolveOptionsContext>,
220) -> Result<Vc<ResolveOptions>> {
221    let options_context_value = options_context.await?;
222    if !options_context_value.rules.is_empty() {
223        for (condition, new_options_context) in options_context_value.rules.iter() {
224            if condition.matches(&resolve_path) {
225                return Ok(resolve_options(resolve_path, **new_options_context));
226            }
227        }
228    }
229
230    let resolve_options = base_resolve_options(*resolve_path.fs, options_context);
231
232    let resolve_options = if options_context_value.enable_typescript {
233        let find_tsconfig = async || {
234            // Otherwise, attempt to find a tsconfig up the file tree
235            let tsconfig = find_context_file(
236                resolve_path.clone(),
237                tsconfig(),
238                options_context_value.collect_affecting_sources,
239            )
240            .await?;
241            anyhow::Ok::<Vc<ResolveOptions>>(match &*tsconfig {
242                FindContextFileResult::Found(path, _) => apply_tsconfig_resolve_options(
243                    resolve_options,
244                    tsconfig_resolve_options(path.clone()),
245                ),
246                FindContextFileResult::NotFound(_) => resolve_options,
247            })
248        };
249
250        // Use a specified tsconfig path if provided. In Next.js, this is always provided by the
251        // default config, at the very least.
252        match &options_context_value.tsconfig_path {
253            TsConfigHandling::Disabled => resolve_options,
254            TsConfigHandling::ContextFile => find_tsconfig().await?,
255            TsConfigHandling::Fixed(tsconfig_path) => {
256                let meta = tsconfig_path.metadata().await;
257                if meta.is_ok() {
258                    // If the file exists, use it.
259                    apply_tsconfig_resolve_options(
260                        resolve_options,
261                        tsconfig_resolve_options(tsconfig_path.clone()),
262                    )
263                } else {
264                    // Otherwise, try and find one.
265                    // TODO: If the user provides a tsconfig.json explicitly, this should fail
266                    // explicitly. Currently implemented this way for parity with webpack.
267                    find_tsconfig().await?
268                }
269            }
270        }
271    } else {
272        resolve_options
273    };
274
275    // Make sure to always apply `options_context.import_map` last, so it properly
276    // overwrites any other mappings.
277    let resolve_options = options_context_value
278        .import_map
279        .map(|import_map| resolve_options.with_extended_import_map(*import_map))
280        .unwrap_or(resolve_options);
281    // And the same for the fallback_import_map
282    let resolve_options = options_context_value
283        .fallback_import_map
284        .map(|fallback_import_map| {
285            resolve_options.with_extended_fallback_import_map(*fallback_import_map)
286        })
287        .unwrap_or(resolve_options);
288
289    Ok(resolve_options)
290}