turbopack_resolve/
resolve.rs

1use anyhow::Result;
2use turbo_rcstr::rcstr;
3use turbo_tasks::{ResolvedVc, Vc};
4use turbo_tasks_fs::{FileSystem, FileSystemPath};
5use turbopack_core::resolve::{
6    AliasMap, AliasPattern, ExternalTraced, ExternalType, FindContextFileResult, find_context_file,
7    options::{
8        ConditionValue, ImportMap, ImportMapping, ResolutionConditions, ResolveInPackage,
9        ResolveIntoPackage, ResolveModules, ResolveOptions,
10    },
11};
12
13use crate::{
14    resolve_options_context::ResolveOptionsContext,
15    typescript::{apply_tsconfig_resolve_options, tsconfig, tsconfig_resolve_options},
16};
17
18const NODE_EXTERNALS: [&str; 64] = [
19    "assert",
20    "assert/strict",
21    "async_hooks",
22    "buffer",
23    "child_process",
24    "cluster",
25    "console",
26    "constants",
27    "crypto",
28    "dgram",
29    "diagnostics_channel",
30    "dns",
31    "dns/promises",
32    "domain",
33    "events",
34    "fs",
35    "fs/promises",
36    "http",
37    "http2",
38    "https",
39    "inspector",
40    "module",
41    "net",
42    "os",
43    "path",
44    "path/posix",
45    "path/win32",
46    "perf_hooks",
47    "process",
48    "punycode",
49    "querystring",
50    "readline",
51    "repl",
52    "stream",
53    "stream/promises",
54    "stream/web",
55    "string_decoder",
56    "sys",
57    "timers",
58    "timers/promises",
59    "tls",
60    "trace_events",
61    "tty",
62    "url",
63    "util",
64    "util/types",
65    "v8",
66    "vm",
67    "wasi",
68    "worker_threads",
69    "zlib",
70    "pnpapi",
71    "_http_agent",
72    "_http_client",
73    "_http_common",
74    "_http_incoming",
75    "_http_outgoing",
76    "_http_server",
77    "_stream_duplex",
78    "_stream_passthrough",
79    "_stream_readable",
80    "_stream_transform",
81    "_stream_wrap",
82    "_stream_writable",
83];
84
85const EDGE_NODE_EXTERNALS: [&str; 5] = ["buffer", "events", "assert", "util", "async_hooks"];
86
87const BUN_EXTERNALS: [&str; 6] = [
88    "bun:ffi",
89    "bun:jsc",
90    "bun:sqlite",
91    "bun:test",
92    "bun:wrap",
93    "bun",
94];
95
96#[turbo_tasks::function]
97async fn base_resolve_options(
98    fs: ResolvedVc<Box<dyn FileSystem>>,
99    options_context: Vc<ResolveOptionsContext>,
100) -> Result<Vc<ResolveOptions>> {
101    let opt = options_context.await?;
102    let emulating = opt.emulate_environment;
103    let root = fs.root().owned().await?;
104    let mut direct_mappings = AliasMap::new();
105    let node_externals = if let Some(environment) = emulating {
106        environment.node_externals().owned().await?
107    } else {
108        opt.enable_node_externals
109    };
110    let untraced_external_cell =
111        ImportMapping::External(None, ExternalType::CommonJs, ExternalTraced::Untraced)
112            .resolved_cell();
113
114    for req in BUN_EXTERNALS {
115        direct_mappings.insert(AliasPattern::exact(req), untraced_external_cell);
116    }
117
118    if node_externals || opt.enable_edge_node_externals {
119        if node_externals {
120            for req in NODE_EXTERNALS {
121                direct_mappings.insert(AliasPattern::exact(req), untraced_external_cell);
122                direct_mappings.insert(
123                    AliasPattern::exact(format!("node:{req}")),
124                    untraced_external_cell,
125                );
126            }
127        }
128
129        if opt.enable_edge_node_externals {
130            for req in EDGE_NODE_EXTERNALS {
131                direct_mappings.insert(
132                    AliasPattern::exact(req),
133                    ImportMapping::External(
134                        Some(format!("node:{req}").into()),
135                        ExternalType::CommonJs,
136                        ExternalTraced::Untraced,
137                    )
138                    .resolved_cell(),
139                );
140                direct_mappings.insert(
141                    AliasPattern::exact(format!("node:{req}")),
142                    untraced_external_cell,
143                );
144            }
145        }
146    }
147
148    let mut import_map = ImportMap::new(direct_mappings);
149    if let Some(additional_import_map) = opt.import_map {
150        let additional_import_map = additional_import_map.await?;
151        import_map.extend_ref(&additional_import_map);
152    }
153    let import_map = import_map.resolved_cell();
154
155    let conditions = {
156        let mut conditions: ResolutionConditions = [
157            (rcstr!("import"), ConditionValue::Unknown),
158            (rcstr!("require"), ConditionValue::Unknown),
159        ]
160        .into_iter()
161        .collect();
162        if opt.browser {
163            conditions.insert(rcstr!("browser"), ConditionValue::Set);
164        }
165        if opt.module {
166            conditions.insert(rcstr!("module"), ConditionValue::Set);
167        }
168        if let Some(environment) = emulating {
169            for condition in environment.resolve_conditions().await?.iter() {
170                conditions.insert(condition.clone(), ConditionValue::Set);
171            }
172        }
173        for condition in opt.custom_conditions.iter() {
174            conditions.insert(condition.clone(), ConditionValue::Set);
175        }
176        // Infer some well-known conditions
177        let dev = conditions.get("development").cloned();
178        let prod = conditions.get("production").cloned();
179        if prod.is_none() {
180            conditions.insert(
181                rcstr!("production"),
182                if matches!(dev, Some(ConditionValue::Set)) {
183                    ConditionValue::Unset
184                } else {
185                    ConditionValue::Unknown
186                },
187            );
188        }
189        if dev.is_none() {
190            conditions.insert(
191                rcstr!("development"),
192                if matches!(prod, Some(ConditionValue::Set)) {
193                    ConditionValue::Unset
194                } else {
195                    ConditionValue::Unknown
196                },
197            );
198        }
199        conditions
200    };
201
202    let extensions = if let Some(custom_extension) = &opt.custom_extensions {
203        custom_extension.clone()
204    } else if let Some(environment) = emulating {
205        environment.resolve_extensions().owned().await?
206    } else {
207        let mut ext = Vec::new();
208        if opt.enable_typescript && opt.enable_react {
209            ext.push(rcstr!(".tsx"));
210        }
211        if opt.enable_typescript {
212            ext.push(rcstr!(".ts"));
213        }
214        if opt.enable_react {
215            ext.push(rcstr!(".jsx"));
216        }
217        ext.push(rcstr!(".js"));
218        if opt.enable_mjs_extension {
219            ext.push(rcstr!(".mjs"));
220        }
221        if opt.enable_node_native_modules {
222            ext.push(rcstr!(".node"));
223        }
224        ext.push(rcstr!(".json"));
225        ext
226    };
227    Ok(ResolveOptions {
228        extensions,
229        modules: if let Some(environment) = emulating {
230            if *environment.resolve_node_modules().await? {
231                vec![ResolveModules::Nested(
232                    root.clone(),
233                    vec![rcstr!("node_modules")],
234                )]
235            } else {
236                Vec::new()
237            }
238        } else {
239            let mut mods = Vec::new();
240            if let Some(dir) = &opt.enable_node_modules {
241                mods.push(ResolveModules::Nested(
242                    dir.clone(),
243                    vec![rcstr!("node_modules")],
244                ));
245            }
246            mods
247        },
248        into_package: {
249            let mut resolve_into = vec![ResolveIntoPackage::ExportsField {
250                conditions: conditions.clone(),
251                unspecified_conditions: ConditionValue::Unset,
252            }];
253            if opt.browser {
254                resolve_into.push(ResolveIntoPackage::MainField {
255                    field: rcstr!("browser"),
256                });
257            }
258            if opt.module {
259                resolve_into.push(ResolveIntoPackage::MainField {
260                    field: rcstr!("module"),
261                });
262            }
263            resolve_into.push(ResolveIntoPackage::MainField {
264                field: rcstr!("main"),
265            });
266            resolve_into
267        },
268        in_package: {
269            let mut resolve_in = vec![ResolveInPackage::ImportsField {
270                conditions,
271                unspecified_conditions: ConditionValue::Unset,
272            }];
273            if opt.browser {
274                resolve_in.push(ResolveInPackage::AliasField(rcstr!("browser")));
275            }
276            resolve_in
277        },
278        default_files: vec![rcstr!("index")],
279        import_map: Some(import_map),
280        resolved_map: opt.resolved_map,
281        after_resolve_plugins: opt.after_resolve_plugins.clone(),
282        before_resolve_plugins: opt.before_resolve_plugins.clone(),
283        loose_errors: opt.loose_errors,
284        ..Default::default()
285    }
286    .into())
287}
288
289#[turbo_tasks::function]
290pub async fn resolve_options(
291    resolve_path: FileSystemPath,
292    options_context: Vc<ResolveOptionsContext>,
293) -> Result<Vc<ResolveOptions>> {
294    let options_context_value = options_context.await?;
295    if !options_context_value.rules.is_empty() {
296        for (condition, new_options_context) in options_context_value.rules.iter() {
297            if condition.matches(&resolve_path) {
298                return Ok(resolve_options(resolve_path, **new_options_context));
299            }
300        }
301    }
302
303    let resolve_options = base_resolve_options(*resolve_path.fs, options_context);
304
305    let resolve_options = if options_context_value.enable_typescript {
306        let find_tsconfig = async || {
307            // Otherwise, attempt to find a tsconfig up the file tree
308            let tsconfig = find_context_file(resolve_path.clone(), tsconfig()).await?;
309            anyhow::Ok::<Vc<ResolveOptions>>(match &*tsconfig {
310                FindContextFileResult::Found(path, _) => apply_tsconfig_resolve_options(
311                    resolve_options,
312                    tsconfig_resolve_options(path.clone()),
313                ),
314                FindContextFileResult::NotFound(_) => resolve_options,
315            })
316        };
317
318        // Use a specified tsconfig path if provided. In Next.js, this is always provided by the
319        // default config, at the very least.
320        if let Some(tsconfig_path) = &options_context_value.tsconfig_path {
321            let meta = tsconfig_path.metadata().await;
322            if meta.is_ok() {
323                // If the file exists, use it.
324                apply_tsconfig_resolve_options(
325                    resolve_options,
326                    tsconfig_resolve_options(tsconfig_path.clone()),
327                )
328            } else {
329                // Otherwise, try and find one.
330                // TODO: If the user provides a tsconfig.json explicitly, this should fail
331                // explicitly. Currently implemented this way for parity with webpack.
332                find_tsconfig().await?
333            }
334        } else {
335            find_tsconfig().await?
336        }
337    } else {
338        resolve_options
339    };
340
341    // Make sure to always apply `options_context.import_map` last, so it properly
342    // overwrites any other mappings.
343    let resolve_options = options_context_value
344        .import_map
345        .map(|import_map| resolve_options.with_extended_import_map(*import_map))
346        .unwrap_or(resolve_options);
347    // And the same for the fallback_import_map
348    let resolve_options = options_context_value
349        .fallback_import_map
350        .map(|fallback_import_map| {
351            resolve_options.with_extended_fallback_import_map(*fallback_import_map)
352        })
353        .unwrap_or(resolve_options);
354
355    Ok(resolve_options)
356}