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