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