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