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