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