turbopack_node/transforms/
webpack.rs

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