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 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 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 if let Some(tsconfig_path) = &options_context_value.tsconfig_path {
321 let meta = tsconfig_path.metadata().await;
322 if meta.is_ok() {
323 apply_tsconfig_resolve_options(
325 resolve_options,
326 tsconfig_resolve_options(tsconfig_path.clone()),
327 )
328 } else {
329 find_tsconfig().await?
333 }
334 } else {
335 find_tsconfig().await?
336 }
337 } else {
338 resolve_options
339 };
340
341 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 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}