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
87#[turbo_tasks::function]
88async fn base_resolve_options(
89 fs: ResolvedVc<Box<dyn FileSystem>>,
90 options_context: Vc<ResolveOptionsContext>,
91) -> Result<Vc<ResolveOptions>> {
92 let opt = options_context.await?;
93 let emulating = opt.emulate_environment;
94 let root = fs.root().await?.clone_value();
95 let mut direct_mappings = AliasMap::new();
96 let node_externals = if let Some(environment) = emulating {
97 environment.node_externals().owned().await?
98 } else {
99 opt.enable_node_externals
100 };
101 if node_externals || opt.enable_edge_node_externals {
102 let untraced_external_cell =
103 ImportMapping::External(None, ExternalType::CommonJs, ExternalTraced::Untraced)
104 .resolved_cell();
105
106 if node_externals {
107 for req in NODE_EXTERNALS {
108 direct_mappings.insert(AliasPattern::exact(req), untraced_external_cell);
109 direct_mappings.insert(
110 AliasPattern::exact(format!("node:{req}")),
111 untraced_external_cell,
112 );
113 }
114 }
115 if opt.enable_edge_node_externals {
116 for req in EDGE_NODE_EXTERNALS {
117 direct_mappings.insert(
118 AliasPattern::exact(req),
119 ImportMapping::External(
120 Some(format!("node:{req}").into()),
121 ExternalType::CommonJs,
122 ExternalTraced::Untraced,
123 )
124 .resolved_cell(),
125 );
126 direct_mappings.insert(
127 AliasPattern::exact(format!("node:{req}")),
128 untraced_external_cell,
129 );
130 }
131 }
132 }
133
134 let mut import_map = ImportMap::new(direct_mappings);
135 if let Some(additional_import_map) = opt.import_map {
136 let additional_import_map = additional_import_map.await?;
137 import_map.extend_ref(&additional_import_map);
138 }
139 let import_map = import_map.resolved_cell();
140
141 let conditions = {
142 let mut conditions: ResolutionConditions = [
143 (rcstr!("import"), ConditionValue::Unknown),
144 (rcstr!("require"), ConditionValue::Unknown),
145 ]
146 .into_iter()
147 .collect();
148 if opt.browser {
149 conditions.insert(rcstr!("browser"), ConditionValue::Set);
150 }
151 if opt.module {
152 conditions.insert(rcstr!("module"), ConditionValue::Set);
153 }
154 if let Some(environment) = emulating {
155 for condition in environment.resolve_conditions().await?.iter() {
156 conditions.insert(condition.clone(), ConditionValue::Set);
157 }
158 }
159 for condition in opt.custom_conditions.iter() {
160 conditions.insert(condition.clone(), ConditionValue::Set);
161 }
162 let dev = conditions.get("development").cloned();
164 let prod = conditions.get("production").cloned();
165 if prod.is_none() {
166 conditions.insert(
167 rcstr!("production"),
168 if matches!(dev, Some(ConditionValue::Set)) {
169 ConditionValue::Unset
170 } else {
171 ConditionValue::Unknown
172 },
173 );
174 }
175 if dev.is_none() {
176 conditions.insert(
177 rcstr!("development"),
178 if matches!(prod, Some(ConditionValue::Set)) {
179 ConditionValue::Unset
180 } else {
181 ConditionValue::Unknown
182 },
183 );
184 }
185 conditions
186 };
187
188 let extensions = if let Some(custom_extension) = &opt.custom_extensions {
189 custom_extension.clone()
190 } else if let Some(environment) = emulating {
191 environment.resolve_extensions().owned().await?
192 } else {
193 let mut ext = Vec::new();
194 if opt.enable_typescript && opt.enable_react {
195 ext.push(rcstr!(".tsx"));
196 }
197 if opt.enable_typescript {
198 ext.push(rcstr!(".ts"));
199 }
200 if opt.enable_react {
201 ext.push(rcstr!(".jsx"));
202 }
203 ext.push(rcstr!(".js"));
204 if opt.enable_mjs_extension {
205 ext.push(rcstr!(".mjs"));
206 }
207 if opt.enable_node_native_modules {
208 ext.push(rcstr!(".node"));
209 }
210 ext.push(rcstr!(".json"));
211 ext
212 };
213 Ok(ResolveOptions {
214 extensions,
215 modules: if let Some(environment) = emulating {
216 if *environment.resolve_node_modules().await? {
217 vec![ResolveModules::Nested(
218 root.clone(),
219 vec![rcstr!("node_modules")],
220 )]
221 } else {
222 Vec::new()
223 }
224 } else {
225 let mut mods = Vec::new();
226 if let Some(dir) = &opt.enable_node_modules {
227 mods.push(ResolveModules::Nested(
228 dir.clone(),
229 vec![rcstr!("node_modules")],
230 ));
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: rcstr!("browser"),
242 });
243 }
244 if opt.module {
245 resolve_into.push(ResolveIntoPackage::MainField {
246 field: rcstr!("module"),
247 });
248 }
249 resolve_into.push(ResolveIntoPackage::MainField {
250 field: rcstr!("main"),
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(rcstr!("browser")));
261 }
262 resolve_in
263 },
264 default_files: vec![rcstr!("index")],
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: 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 for (condition, new_options_context) in options_context_value.rules.iter() {
283 if condition.matches(&resolve_path) {
284 return Ok(resolve_options(resolve_path, **new_options_context));
285 }
286 }
287 }
288
289 let resolve_options = base_resolve_options(*resolve_path.fs, options_context);
290
291 let resolve_options = if options_context_value.enable_typescript {
292 let find_tsconfig = async || {
293 let tsconfig = find_context_file(resolve_path.clone(), tsconfig()).await?;
295 anyhow::Ok::<Vc<ResolveOptions>>(match &*tsconfig {
296 FindContextFileResult::Found(path, _) => apply_tsconfig_resolve_options(
297 resolve_options,
298 tsconfig_resolve_options(path.clone()),
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.clone()),
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}