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