turbopack_node/transforms/
webpack.rs

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