Skip to main content

turbopack_node/transforms/
webpack.rs

1use std::mem::take;
2
3use anyhow::{Context, Result, bail};
4use base64::Engine;
5use bincode::{Decode, Encode};
6use either::Either;
7use futures::try_join;
8use serde::{Deserialize, Serialize};
9use serde_json::{Map as JsonMap, Value as JsonValue, json};
10use serde_with::serde_as;
11use tracing::Instrument;
12use turbo_rcstr::{RcStr, rcstr};
13use turbo_tasks::{
14    Completion, OperationVc, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt, Vc,
15    trace::TraceRawVcs, turbobail,
16};
17use turbo_tasks_env::ProcessEnv;
18use turbo_tasks_fs::{
19    File, FileContent, FileSystemPath,
20    glob::{Glob, GlobOptions},
21    json::parse_json_with_source_context,
22    rope::Rope,
23};
24use turbopack_core::{
25    asset::{Asset, AssetContent},
26    chunk::ChunkingContext,
27    context::{AssetContext, ProcessResult},
28    file_source::FileSource,
29    ident::AssetIdent,
30    issue::{
31        Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
32        OptionStyledString, StyledString,
33    },
34    module_graph::{ModuleGraph, SingleModuleGraph},
35    reference_type::{InnerAssets, ReferenceType},
36    resolve::{
37        options::{ConditionValue, ResolveInPackage, ResolveIntoPackage, ResolveOptions},
38        parse::Request,
39        pattern::Pattern,
40        resolve,
41    },
42    source::Source,
43    source_map::{GenerateSourceMap, utils::resolve_source_map_sources},
44    source_transform::SourceTransform,
45    virtual_source::VirtualSource,
46};
47use turbopack_resolve::{
48    ecmascript::get_condition_maps, resolve::resolve_options,
49    resolve_options_context::ResolveOptionsContext,
50};
51
52use crate::{
53    AssetsForSourceMapping,
54    backend::NodeBackend,
55    debug::should_debug,
56    embed_js::embed_file_path,
57    evaluate::{
58        EnvVarTracking, EvaluateContext, EvaluateEntries, EvaluatePool, EvaluationIssue,
59        custom_evaluate, get_evaluate_entries, get_evaluate_pool,
60    },
61    execution_context::ExecutionContext,
62    format::FormattingMode,
63    source_map::{StackFrame, StructuredError},
64    transforms::util::{EmittedAsset, emitted_assets_to_virtual_sources},
65};
66
67#[serde_as]
68#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Encode, Decode)]
69struct BytesBase64 {
70    #[serde_as(as = "serde_with::base64::Base64")]
71    binary: Vec<u8>,
72}
73
74#[derive(Debug, Clone, Deserialize)]
75#[turbo_tasks::value]
76#[serde(rename_all = "camelCase")]
77struct WebpackLoadersProcessingResult {
78    #[serde(with = "either::serde_untagged")]
79    #[bincode(with = "turbo_bincode::either")]
80    #[turbo_tasks(debug_ignore, trace_ignore)]
81    source: Either<RcStr, BytesBase64>,
82    map: Option<RcStr>,
83    #[turbo_tasks(trace_ignore)]
84    assets: Option<Vec<EmittedAsset>>,
85}
86
87pub use turbopack_core::loader::{WebpackLoaderItem, WebpackLoaderItems};
88
89#[turbo_tasks::value]
90pub struct WebpackLoaders {
91    evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
92    execution_context: ResolvedVc<ExecutionContext>,
93    loaders: ResolvedVc<WebpackLoaderItems>,
94    rename_as: Option<RcStr>,
95    resolve_options_context: ResolvedVc<ResolveOptionsContext>,
96    source_maps: bool,
97}
98
99#[turbo_tasks::value_impl]
100impl WebpackLoaders {
101    #[turbo_tasks::function]
102    pub fn new(
103        evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
104        execution_context: ResolvedVc<ExecutionContext>,
105        loaders: ResolvedVc<WebpackLoaderItems>,
106        rename_as: Option<RcStr>,
107        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
108        source_maps: bool,
109    ) -> Vc<Self> {
110        WebpackLoaders {
111            evaluate_context,
112            execution_context,
113            loaders,
114            rename_as,
115            resolve_options_context,
116            source_maps,
117        }
118        .cell()
119    }
120}
121
122#[turbo_tasks::value_impl]
123impl SourceTransform for WebpackLoaders {
124    #[turbo_tasks::function]
125    fn transform(
126        self: ResolvedVc<Self>,
127        source: ResolvedVc<Box<dyn Source>>,
128    ) -> Vc<Box<dyn Source>> {
129        Vc::upcast(
130            WebpackLoadersProcessedAsset {
131                transform: self,
132                source,
133            }
134            .cell(),
135        )
136    }
137}
138
139#[turbo_tasks::value]
140struct WebpackLoadersProcessedAsset {
141    transform: ResolvedVc<WebpackLoaders>,
142    source: ResolvedVc<Box<dyn Source>>,
143}
144
145#[turbo_tasks::value_impl]
146impl Source for WebpackLoadersProcessedAsset {
147    #[turbo_tasks::function]
148    async fn ident(&self) -> Result<Vc<AssetIdent>> {
149        Ok(
150            if let Some(rename_as) = self.transform.await?.rename_as.as_deref() {
151                self.source.ident().rename_as(rename_as.into())
152            } else {
153                self.source.ident()
154            },
155        )
156    }
157}
158
159#[turbo_tasks::value_impl]
160impl Asset for WebpackLoadersProcessedAsset {
161    #[turbo_tasks::function]
162    async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
163        Ok(*self.process().await?.content)
164    }
165}
166
167#[turbo_tasks::value_impl]
168impl GenerateSourceMap for WebpackLoadersProcessedAsset {
169    #[turbo_tasks::function]
170    async fn generate_source_map(self: Vc<Self>) -> Result<Vc<FileContent>> {
171        Ok(*self.process().await?.source_map)
172    }
173}
174
175#[turbo_tasks::value]
176struct ProcessWebpackLoadersResult {
177    content: ResolvedVc<AssetContent>,
178    source_map: ResolvedVc<FileContent>,
179    assets: Vec<ResolvedVc<VirtualSource>>,
180}
181
182#[turbo_tasks::function]
183async fn webpack_loaders_executor(
184    evaluate_context: Vc<Box<dyn AssetContext>>,
185) -> Result<Vc<ProcessResult>> {
186    Ok(evaluate_context.process(
187        Vc::upcast(FileSource::new(
188            embed_file_path(rcstr!("transforms/webpack-loaders.ts"))
189                .owned()
190                .await?,
191        )),
192        ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
193    ))
194}
195
196#[turbo_tasks::value_impl]
197impl WebpackLoadersProcessedAsset {
198    #[turbo_tasks::function]
199    async fn process(&self) -> Result<Vc<ProcessWebpackLoadersResult>> {
200        let transform = self.transform.await?;
201        let loaders = transform.loaders.await?;
202
203        let webpack_span = tracing::info_span!(
204            "webpack loader",
205            name = display(ReadRef::<WebpackLoaderItems>::as_raw_ref(&loaders))
206        );
207
208        async {
209            let ExecutionContext {
210                project_path,
211                chunking_context,
212                env,
213                node_backend,
214            } = &*transform.execution_context.await?;
215            let source_content = self.source.content();
216            let AssetContent::File(file) = *source_content.await? else {
217                bail!("Webpack Loaders transform only support transforming files");
218            };
219            let FileContent::Content(file_content) = &*file.await? else {
220                return Ok(ProcessWebpackLoadersResult {
221                    content: AssetContent::File(FileContent::NotFound.resolved_cell())
222                        .resolved_cell(),
223                    assets: Vec::new(),
224                    source_map: FileContent::NotFound.resolved_cell(),
225                }
226                .cell());
227            };
228
229            // If the content is not a valid string (e.g. binary file), handle the error and pass a
230            // Buffer to Webpack instead of a Base64 string so the build process doesn't crash.
231            let content: JsonValue = match file_content.content().to_str() {
232                Ok(utf8_str) => utf8_str.to_string().into(),
233                Err(_) => JsonValue::Object(JsonMap::from_iter(std::iter::once((
234                    "binary".to_string(),
235                    JsonValue::from(
236                        base64::engine::general_purpose::STANDARD
237                            .encode(file_content.content().to_bytes()),
238                    ),
239                )))),
240            };
241            let evaluate_context = transform.evaluate_context;
242
243            let webpack_loaders_executor = webpack_loaders_executor(*evaluate_context).module();
244
245            let entries = get_evaluate_entries(
246                webpack_loaders_executor,
247                *evaluate_context,
248                **node_backend,
249                None,
250            )
251            .to_resolved()
252            .await?;
253
254            let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries(
255                entries.graph_entries().to_resolved().await?,
256                false,
257                false,
258            ))
259            .connect()
260            .to_resolved()
261            .await?;
262
263            let resource_fs_path = self.source.ident().path().await?;
264            let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path) else {
265                turbobail!(
266                    "Resource path \"{resource_fs_path}\" needs to be on project filesystem \
267                     \"{project_path}\"",
268                );
269            };
270            let config_value = evaluate_webpack_loader(WebpackLoaderContext {
271                entries,
272                cwd: project_path.clone(),
273                env: *env,
274                node_backend: *node_backend,
275                context_source_for_issue: self.source,
276                chunking_context: *chunking_context,
277                module_graph,
278                resolve_options_context: Some(transform.resolve_options_context),
279                args: vec![
280                    ResolvedVc::cell(content),
281                    // We need to pass the query string to the loader
282                    ResolvedVc::cell(resource_path.to_string().into()),
283                    ResolvedVc::cell(self.source.ident().await?.query.to_string().into()),
284                    ResolvedVc::cell(json!(*loaders)),
285                    ResolvedVc::cell(transform.source_maps.into()),
286                ],
287                additional_invalidation: Completion::immutable().to_resolved().await?,
288            })
289            .await?;
290
291            let Some(val) = &*config_value else {
292                // An error happened, which has already been converted into an issue.
293                return Ok(ProcessWebpackLoadersResult {
294                    content: AssetContent::File(FileContent::NotFound.resolved_cell())
295                        .resolved_cell(),
296                    assets: Vec::new(),
297                    source_map: FileContent::NotFound.resolved_cell(),
298                }
299                .cell());
300            };
301            let processed: WebpackLoadersProcessingResult = parse_json_with_source_context(val)
302                .context(
303                    "Unable to deserializate response from webpack loaders transform operation",
304                )?;
305
306            // handle SourceMap
307            let source_map = if !transform.source_maps {
308                None
309            } else {
310                processed
311                    .map
312                    .map(|source_map| Rope::from(source_map.into_owned()))
313            };
314            let source_map =
315                resolve_source_map_sources(source_map.as_ref(), &resource_fs_path).await?;
316
317            let file = match processed.source {
318                Either::Left(str) => File::from(str),
319                Either::Right(bytes) => File::from(bytes.binary),
320            };
321            let assets = emitted_assets_to_virtual_sources(processed.assets).await?;
322
323            let content =
324                AssetContent::File(FileContent::Content(file).resolved_cell()).resolved_cell();
325            Ok(ProcessWebpackLoadersResult {
326                content,
327                assets,
328                source_map: if let Some(source_map) = source_map {
329                    FileContent::Content(File::from(source_map)).resolved_cell()
330                } else {
331                    FileContent::NotFound.resolved_cell()
332                },
333            }
334            .cell())
335        }
336        .instrument(webpack_span)
337        .await
338    }
339}
340
341#[turbo_tasks::function]
342pub(crate) async fn evaluate_webpack_loader(
343    webpack_loader_context: WebpackLoaderContext,
344) -> Result<Vc<Option<RcStr>>> {
345    custom_evaluate(webpack_loader_context).await
346}
347
348#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
349#[serde(rename_all = "camelCase")]
350enum LogType {
351    Error,
352    Warn,
353    Info,
354    Log,
355    Debug,
356    Trace,
357    Group,
358    GroupCollapsed,
359    GroupEnd,
360    Profile,
361    ProfileEnd,
362    Time,
363    Clear,
364    Status,
365}
366
367#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
368#[serde(rename_all = "camelCase")]
369pub struct LogInfo {
370    time: u64,
371    log_type: LogType,
372    #[bincode(with = "turbo_bincode::serde_self_describing")]
373    args: Vec<JsonValue>,
374    trace: Option<Vec<StackFrame<'static>>>,
375}
376
377#[derive(Deserialize, Debug)]
378#[serde(tag = "type", rename_all = "camelCase")]
379pub enum InfoMessage {
380    // Sent to inform Turbopack about the dependencies of the task.
381    // All fields are `default` since it is ok for the client to
382    // simply omit instead of sending empty arrays.
383    #[serde(rename_all = "camelCase")]
384    Dependencies {
385        #[serde(default)]
386        env_variables: Vec<RcStr>,
387        #[serde(default)]
388        file_paths: Vec<RcStr>,
389        #[serde(default)]
390        directories: Vec<(RcStr, RcStr)>,
391        #[serde(default)]
392        build_file_paths: Vec<RcStr>,
393    },
394    EmittedError {
395        severity: IssueSeverity,
396        error: StructuredError,
397    },
398    Log {
399        logs: Vec<LogInfo>,
400    },
401}
402
403#[derive(
404    Debug, Clone, TaskInput, Hash, PartialEq, Eq, Deserialize, TraceRawVcs, Encode, Decode,
405)]
406#[serde(rename_all = "camelCase")]
407pub struct WebpackResolveOptions {
408    alias_fields: Option<Vec<RcStr>>,
409    condition_names: Option<Vec<RcStr>>,
410    no_package_json: bool,
411    extensions: Option<Vec<RcStr>>,
412    main_fields: Option<Vec<RcStr>>,
413    no_exports_field: bool,
414    main_files: Option<Vec<RcStr>>,
415    no_modules: bool,
416    prefer_relative: bool,
417}
418
419#[derive(Deserialize, Debug)]
420#[serde(tag = "type", rename_all = "camelCase")]
421pub enum RequestMessage {
422    #[serde(rename_all = "camelCase")]
423    Resolve {
424        options: WebpackResolveOptions,
425        lookup_path: RcStr,
426        request: RcStr,
427    },
428    #[serde(rename_all = "camelCase")]
429    TrackFileRead { file: RcStr },
430}
431
432#[derive(Serialize, Debug)]
433#[serde(untagged)]
434pub enum ResponseMessage {
435    Resolve { path: RcStr },
436    // Only used for tracking invalidations, no content is returned.
437    TrackFileRead {},
438}
439
440#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Debug, TraceRawVcs, Encode, Decode)]
441pub struct WebpackLoaderContext {
442    pub entries: ResolvedVc<EvaluateEntries>,
443    pub cwd: FileSystemPath,
444    pub env: ResolvedVc<Box<dyn ProcessEnv>>,
445    pub node_backend: ResolvedVc<Box<dyn NodeBackend>>,
446    pub context_source_for_issue: ResolvedVc<Box<dyn Source>>,
447    pub module_graph: ResolvedVc<ModuleGraph>,
448    pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
449    pub resolve_options_context: Option<ResolvedVc<ResolveOptionsContext>>,
450    pub args: Vec<ResolvedVc<JsonValue>>,
451    pub additional_invalidation: ResolvedVc<Completion>,
452}
453
454impl EvaluateContext for WebpackLoaderContext {
455    type InfoMessage = InfoMessage;
456    type RequestMessage = RequestMessage;
457    type ResponseMessage = ResponseMessage;
458    type State = Vec<LogInfo>;
459
460    fn pool(&self) -> OperationVc<EvaluatePool> {
461        get_evaluate_pool(
462            self.entries,
463            self.cwd.clone(),
464            self.env,
465            self.node_backend,
466            self.chunking_context,
467            self.module_graph,
468            self.additional_invalidation,
469            should_debug("webpack_loader"),
470            // Env vars are read untracked, since we want a more granular dependency on certain env
471            // vars only. So the runtime code tracks which env vars are read and send a dependency
472            // message for them.
473            EnvVarTracking::Untracked,
474        )
475    }
476
477    fn args(&self) -> &[ResolvedVc<serde_json::Value>] {
478        &self.args
479    }
480
481    fn cwd(&self) -> Vc<turbo_tasks_fs::FileSystemPath> {
482        self.cwd.clone().cell()
483    }
484
485    fn keep_alive(&self) -> bool {
486        true
487    }
488
489    async fn emit_error(&self, error: StructuredError, pool: &EvaluatePool) -> Result<()> {
490        EvaluationIssue {
491            error,
492            source: IssueSource::from_source_only(self.context_source_for_issue),
493            assets_for_source_mapping: pool.assets_for_source_mapping,
494            assets_root: pool.assets_root.clone(),
495            root_path: self.chunking_context.root_path().owned().await?,
496        }
497        .resolved_cell()
498        .emit();
499        Ok(())
500    }
501
502    async fn info(
503        &self,
504        state: &mut Self::State,
505        data: Self::InfoMessage,
506        pool: &EvaluatePool,
507    ) -> Result<()> {
508        match data {
509            InfoMessage::Dependencies {
510                env_variables,
511                file_paths,
512                directories,
513                build_file_paths,
514            } => {
515                // We only process these dependencies to help with tracking, so if it is disabled
516                // dont bother.
517                if turbo_tasks::turbo_tasks().is_tracking_dependencies() {
518                    // Track dependencies of the loader task
519                    // TODO: Because these are reported _after_ the loader actually read the
520                    // dependency there is a race condition where we may miss
521                    // updates that race with the loader execution.
522
523                    // Track all the subscriptions in parallel, since certain loaders like tailwind
524                    // might add thousands of subscriptions.
525                    let env_subscriptions = env_variables
526                        .iter()
527                        .map(|e| self.env.read(e.clone()))
528                        .try_join();
529                    let file_subscriptions = file_paths
530                        .iter()
531                        .map(|p| async move { self.cwd.join(p)?.read().await })
532                        .try_join();
533                    let directory_subscriptions = directories
534                        .iter()
535                        .map(|(dir, glob)| async move {
536                            self.cwd
537                                .join(dir)?
538                                .track_glob(Glob::new(glob.clone(), GlobOptions::default()), false)
539                                .await
540                        })
541                        .try_join();
542                    try_join!(
543                        env_subscriptions,
544                        file_subscriptions,
545                        directory_subscriptions
546                    )?;
547
548                    for build_path in build_file_paths {
549                        let build_path = self.cwd.join(&build_path)?;
550                        BuildDependencyIssue {
551                            source: IssueSource::from_source_only(self.context_source_for_issue),
552                            path: build_path,
553                        }
554                        .resolved_cell()
555                        .emit();
556                    }
557                }
558            }
559            InfoMessage::EmittedError { error, severity } => {
560                EvaluateEmittedErrorIssue {
561                    source: IssueSource::from_source_only(self.context_source_for_issue),
562                    error,
563                    severity,
564                    assets_for_source_mapping: pool.assets_for_source_mapping,
565                    assets_root: pool.assets_root.clone(),
566                    project_dir: self.chunking_context.root_path().owned().await?,
567                }
568                .resolved_cell()
569                .emit();
570            }
571            InfoMessage::Log { logs } => {
572                state.extend(logs);
573            }
574        }
575        Ok(())
576    }
577
578    async fn request(
579        &self,
580        _state: &mut Self::State,
581        data: Self::RequestMessage,
582        _pool: &EvaluatePool,
583    ) -> Result<Self::ResponseMessage> {
584        match data {
585            RequestMessage::Resolve {
586                options: webpack_options,
587                lookup_path,
588                request,
589            } => {
590                let Some(resolve_options_context) = self.resolve_options_context else {
591                    bail!("Resolve options are not available in this context");
592                };
593                let lookup_path = self.cwd.join(&lookup_path)?;
594                let request = Request::parse(Pattern::Constant(request));
595                let options = resolve_options(lookup_path.clone(), *resolve_options_context);
596
597                let options = apply_webpack_resolve_options(options, webpack_options);
598
599                let resolved = resolve(
600                    lookup_path.clone(),
601                    ReferenceType::Undefined,
602                    request,
603                    options,
604                );
605
606                if let Some(source) = *resolved.first_source().await? {
607                    if let Some(path) = self
608                        .cwd
609                        .get_relative_path_to(&*source.ident().path().await?)
610                    {
611                        Ok(ResponseMessage::Resolve { path })
612                    } else {
613                        turbobail!(
614                            "Resolving {request} in {lookup_path} ends up on a different \
615                             filesystem"
616                        );
617                    }
618                } else {
619                    turbobail!("Unable to resolve {request} in {lookup_path}");
620                }
621            }
622            RequestMessage::TrackFileRead { file } => {
623                // Ignore result, we read on the JS side again to prevent some IPC overhead. Still
624                // await the read though to cover at least one class of race conditions.
625                let _ = &*self.cwd.join(&file)?.read().await?;
626                Ok(ResponseMessage::TrackFileRead {})
627            }
628        }
629    }
630
631    async fn finish(&self, state: Self::State, pool: &EvaluatePool) -> Result<()> {
632        let has_errors = state.iter().any(|log| log.log_type == LogType::Error);
633        let has_warnings = state.iter().any(|log| log.log_type == LogType::Warn);
634        if has_errors || has_warnings {
635            let logs = state
636                .into_iter()
637                .filter(|log| {
638                    matches!(
639                        log.log_type,
640                        LogType::Error
641                            | LogType::Warn
642                            | LogType::Info
643                            | LogType::Log
644                            | LogType::Clear,
645                    )
646                })
647                .collect();
648
649            EvaluateErrorLoggingIssue {
650                source: IssueSource::from_source_only(self.context_source_for_issue),
651                logging: logs,
652                severity: if has_errors {
653                    IssueSeverity::Error
654                } else {
655                    IssueSeverity::Warning
656                },
657                assets_for_source_mapping: pool.assets_for_source_mapping,
658                assets_root: pool.assets_root.clone(),
659                project_dir: self.chunking_context.root_path().owned().await?,
660            }
661            .resolved_cell()
662            .emit();
663        }
664        Ok(())
665    }
666}
667
668#[turbo_tasks::function]
669async fn apply_webpack_resolve_options(
670    resolve_options: Vc<ResolveOptions>,
671    webpack_resolve_options: WebpackResolveOptions,
672) -> Result<Vc<ResolveOptions>> {
673    let mut resolve_options = resolve_options.owned().await?;
674    if let Some(alias_fields) = webpack_resolve_options.alias_fields {
675        let mut old = resolve_options
676            .in_package
677            .extract_if(0.., |field| {
678                matches!(field, ResolveInPackage::AliasField(..))
679            })
680            .collect::<Vec<_>>();
681        for field in alias_fields {
682            if &*field == "..." {
683                resolve_options.in_package.extend(take(&mut old));
684            } else {
685                resolve_options
686                    .in_package
687                    .push(ResolveInPackage::AliasField(field));
688            }
689        }
690    }
691    if let Some(condition_names) = webpack_resolve_options.condition_names {
692        for conditions in get_condition_maps(&mut resolve_options) {
693            let mut old = take(conditions);
694            for name in &condition_names {
695                if name == "..." {
696                    conditions.extend(take(&mut old));
697                } else {
698                    conditions.insert(name.clone(), ConditionValue::Set);
699                }
700            }
701        }
702    }
703    if webpack_resolve_options.no_package_json {
704        resolve_options.into_package.retain(|item| {
705            !matches!(
706                item,
707                ResolveIntoPackage::ExportsField { .. } | ResolveIntoPackage::MainField { .. }
708            )
709        });
710    }
711    if let Some(mut extensions) = webpack_resolve_options.extensions {
712        if let Some(pos) = extensions.iter().position(|ext| ext == "...") {
713            extensions.splice(pos..=pos, take(&mut resolve_options.extensions));
714        }
715        resolve_options.extensions = extensions;
716    }
717    if let Some(main_fields) = webpack_resolve_options.main_fields {
718        let mut old = resolve_options
719            .into_package
720            .extract_if(0.., |field| {
721                matches!(field, ResolveIntoPackage::MainField { .. })
722            })
723            .collect::<Vec<_>>();
724        for field in main_fields {
725            if &*field == "..." {
726                resolve_options.into_package.extend(take(&mut old));
727            } else {
728                resolve_options
729                    .into_package
730                    .push(ResolveIntoPackage::MainField { field });
731            }
732        }
733    }
734    if webpack_resolve_options.no_exports_field {
735        resolve_options
736            .into_package
737            .retain(|field| !matches!(field, ResolveIntoPackage::ExportsField { .. }));
738    }
739    if let Some(main_files) = webpack_resolve_options.main_files {
740        resolve_options.default_files = main_files;
741    }
742    if webpack_resolve_options.no_modules {
743        resolve_options.modules.clear();
744    }
745    if webpack_resolve_options.prefer_relative {
746        resolve_options.prefer_relative = true;
747    }
748    Ok(resolve_options.cell())
749}
750
751/// An issue that occurred while evaluating node code.
752#[turbo_tasks::value(shared)]
753pub struct BuildDependencyIssue {
754    pub path: FileSystemPath,
755    pub source: IssueSource,
756}
757
758#[turbo_tasks::value_impl]
759impl Issue for BuildDependencyIssue {
760    fn severity(&self) -> IssueSeverity {
761        IssueSeverity::Warning
762    }
763
764    #[turbo_tasks::function]
765    fn title(&self) -> Vc<StyledString> {
766        StyledString::Text(rcstr!("Build dependencies are not yet supported")).cell()
767    }
768
769    #[turbo_tasks::function]
770    fn stage(&self) -> Vc<IssueStage> {
771        IssueStage::Unsupported.cell()
772    }
773
774    #[turbo_tasks::function]
775    fn file_path(&self) -> Vc<FileSystemPath> {
776        self.source.file_path()
777    }
778
779    #[turbo_tasks::function]
780    async fn description(&self) -> Result<Vc<OptionStyledString>> {
781        Ok(Vc::cell(Some(
782            StyledString::Line(vec![
783                StyledString::Text(rcstr!("The file at ")),
784                StyledString::Code(self.path.to_string().into()),
785                StyledString::Text(
786                    " is a build dependency, which is not yet implemented.
787    Changing this file or any dependency will not be recognized and might require restarting the \
788                     server"
789                        .into(),
790                ),
791            ])
792            .resolved_cell(),
793        )))
794    }
795
796    #[turbo_tasks::function]
797    fn source(&self) -> Vc<OptionIssueSource> {
798        Vc::cell(Some(self.source))
799    }
800}
801
802#[turbo_tasks::value(shared)]
803pub struct EvaluateEmittedErrorIssue {
804    pub source: IssueSource,
805    pub severity: IssueSeverity,
806    pub error: StructuredError,
807    pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
808    pub assets_root: FileSystemPath,
809    pub project_dir: FileSystemPath,
810}
811
812#[turbo_tasks::value_impl]
813impl Issue for EvaluateEmittedErrorIssue {
814    #[turbo_tasks::function]
815    fn file_path(&self) -> Vc<FileSystemPath> {
816        self.source.file_path()
817    }
818
819    #[turbo_tasks::function]
820    fn stage(&self) -> Vc<IssueStage> {
821        IssueStage::Transform.cell()
822    }
823
824    fn severity(&self) -> IssueSeverity {
825        self.severity
826    }
827
828    #[turbo_tasks::function]
829    fn title(&self) -> Vc<StyledString> {
830        StyledString::Text(rcstr!("Issue while running loader")).cell()
831    }
832
833    #[turbo_tasks::function]
834    async fn description(&self) -> Result<Vc<OptionStyledString>> {
835        Ok(Vc::cell(Some(
836            StyledString::Text(
837                self.error
838                    .print(
839                        *self.assets_for_source_mapping,
840                        self.assets_root.clone(),
841                        self.project_dir.clone(),
842                        FormattingMode::Plain,
843                    )
844                    .await?
845                    .into(),
846            )
847            .resolved_cell(),
848        )))
849    }
850
851    #[turbo_tasks::function]
852    fn source(&self) -> Vc<OptionIssueSource> {
853        Vc::cell(Some(self.source))
854    }
855}
856
857#[turbo_tasks::value(shared)]
858pub struct EvaluateErrorLoggingIssue {
859    pub source: IssueSource,
860    pub severity: IssueSeverity,
861    #[turbo_tasks(trace_ignore)]
862    pub logging: Vec<LogInfo>,
863    pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
864    pub assets_root: FileSystemPath,
865    pub project_dir: FileSystemPath,
866}
867
868#[turbo_tasks::value_impl]
869impl Issue for EvaluateErrorLoggingIssue {
870    #[turbo_tasks::function]
871    fn file_path(&self) -> Vc<FileSystemPath> {
872        self.source.file_path()
873    }
874
875    #[turbo_tasks::function]
876    fn stage(&self) -> Vc<IssueStage> {
877        IssueStage::Transform.cell()
878    }
879
880    fn severity(&self) -> IssueSeverity {
881        self.severity
882    }
883
884    #[turbo_tasks::function]
885    fn title(&self) -> Vc<StyledString> {
886        StyledString::Text(rcstr!("Error logging while running loader")).cell()
887    }
888
889    #[turbo_tasks::function]
890    fn description(&self) -> Vc<OptionStyledString> {
891        fn fmt_args(prefix: String, args: &[JsonValue]) -> String {
892            let mut iter = args.iter();
893            let Some(first) = iter.next() else {
894                return "".to_string();
895            };
896            let mut result = prefix;
897            if let JsonValue::String(s) = first {
898                result.push_str(s);
899            } else {
900                result.push_str(&first.to_string());
901            }
902            for arg in iter {
903                result.push(' ');
904                result.push_str(&arg.to_string());
905            }
906            result
907        }
908        let lines = self
909            .logging
910            .iter()
911            .map(|log| match log.log_type {
912                LogType::Error => {
913                    StyledString::Strong(fmt_args("<e> ".to_string(), &log.args).into())
914                }
915                LogType::Warn => StyledString::Text(fmt_args("<w> ".to_string(), &log.args).into()),
916                LogType::Info => StyledString::Text(fmt_args("<i> ".to_string(), &log.args).into()),
917                LogType::Log => StyledString::Text(fmt_args("<l> ".to_string(), &log.args).into()),
918                LogType::Clear => StyledString::Strong(rcstr!("---")),
919                _ => {
920                    unimplemented!("{:?} is not implemented", log.log_type)
921                }
922            })
923            .collect::<Vec<_>>();
924        Vc::cell(Some(StyledString::Stack(lines).resolved_cell()))
925    }
926
927    #[turbo_tasks::function]
928    fn source(&self) -> Vc<OptionIssueSource> {
929        Vc::cell(Some(self.source))
930    }
931}