Skip to main content

turbopack_node/transforms/
webpack.rs

1use std::mem::take;
2
3use anyhow::{Context, Result, bail};
4use base64::Engine;
5use bincode::{Decode, Encode};
6use either::Either;
7use futures::try_join;
8use serde::{Deserialize, Serialize};
9use serde_json::{Map as JsonMap, Value as JsonValue, json};
10use serde_with::serde_as;
11use turbo_rcstr::{RcStr, rcstr};
12use turbo_tasks::{
13    Completion, OperationVc, ResolvedVc, TaskInput, TryJoinIterExt, ValueToString, Vc,
14    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, SingleModuleGraph},
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
85pub use turbopack_core::loader::{WebpackLoaderItem, WebpackLoaderItems};
86
87#[turbo_tasks::value]
88pub struct WebpackLoaders {
89    evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
90    execution_context: ResolvedVc<ExecutionContext>,
91    loaders: ResolvedVc<WebpackLoaderItems>,
92    rename_as: Option<RcStr>,
93    resolve_options_context: ResolvedVc<ResolveOptionsContext>,
94    source_maps: bool,
95}
96
97#[turbo_tasks::value_impl]
98impl WebpackLoaders {
99    #[turbo_tasks::function]
100    pub fn new(
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    ) -> Vc<Self> {
108        WebpackLoaders {
109            evaluate_context,
110            execution_context,
111            loaders,
112            rename_as,
113            resolve_options_context,
114            source_maps,
115        }
116        .cell()
117    }
118}
119
120#[turbo_tasks::value_impl]
121impl SourceTransform for WebpackLoaders {
122    #[turbo_tasks::function]
123    fn transform(
124        self: ResolvedVc<Self>,
125        source: ResolvedVc<Box<dyn Source>>,
126    ) -> Vc<Box<dyn Source>> {
127        Vc::upcast(
128            WebpackLoadersProcessedAsset {
129                transform: self,
130                source,
131            }
132            .cell(),
133        )
134    }
135}
136
137#[turbo_tasks::value]
138struct WebpackLoadersProcessedAsset {
139    transform: ResolvedVc<WebpackLoaders>,
140    source: ResolvedVc<Box<dyn Source>>,
141}
142
143#[turbo_tasks::value_impl]
144impl Source for WebpackLoadersProcessedAsset {
145    #[turbo_tasks::function]
146    async fn ident(&self) -> Result<Vc<AssetIdent>> {
147        Ok(
148            if let Some(rename_as) = self.transform.await?.rename_as.as_deref() {
149                self.source.ident().rename_as(rename_as.into())
150            } else {
151                self.source.ident()
152            },
153        )
154    }
155}
156
157#[turbo_tasks::value_impl]
158impl Asset for WebpackLoadersProcessedAsset {
159    #[turbo_tasks::function]
160    async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
161        Ok(*self.process().await?.content)
162    }
163}
164
165#[turbo_tasks::value_impl]
166impl GenerateSourceMap for WebpackLoadersProcessedAsset {
167    #[turbo_tasks::function]
168    async fn generate_source_map(self: Vc<Self>) -> Result<Vc<FileContent>> {
169        Ok(*self.process().await?.source_map)
170    }
171}
172
173#[turbo_tasks::value]
174struct ProcessWebpackLoadersResult {
175    content: ResolvedVc<AssetContent>,
176    source_map: ResolvedVc<FileContent>,
177    assets: Vec<ResolvedVc<VirtualSource>>,
178}
179
180#[turbo_tasks::function]
181async fn webpack_loaders_executor(
182    evaluate_context: Vc<Box<dyn AssetContext>>,
183) -> Result<Vc<ProcessResult>> {
184    Ok(evaluate_context.process(
185        Vc::upcast(FileSource::new(
186            embed_file_path(rcstr!("transforms/webpack-loaders.ts"))
187                .owned()
188                .await?,
189        )),
190        ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
191    ))
192}
193
194#[turbo_tasks::value_impl]
195impl WebpackLoadersProcessedAsset {
196    #[turbo_tasks::function]
197    async fn process(&self) -> Result<Vc<ProcessWebpackLoadersResult>> {
198        let transform = self.transform.await?;
199
200        let ExecutionContext {
201            project_path,
202            chunking_context,
203            env,
204        } = &*transform.execution_context.await?;
205        let source_content = self.source.content();
206        let AssetContent::File(file) = *source_content.await? else {
207            bail!("Webpack Loaders transform only support transforming files");
208        };
209        let FileContent::Content(file_content) = &*file.await? else {
210            return Ok(ProcessWebpackLoadersResult {
211                content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
212                assets: Vec::new(),
213                source_map: FileContent::NotFound.resolved_cell(),
214            }
215            .cell());
216        };
217
218        // If the content is not a valid string (e.g. binary file), handle the error and pass a
219        // Buffer to Webpack instead of a Base64 string so the build process doesn't crash.
220        let content: JsonValue = match file_content.content().to_str() {
221            Ok(utf8_str) => utf8_str.to_string().into(),
222            Err(_) => JsonValue::Object(JsonMap::from_iter(std::iter::once((
223                "binary".to_string(),
224                JsonValue::from(
225                    base64::engine::general_purpose::STANDARD
226                        .encode(file_content.content().to_bytes()),
227                ),
228            )))),
229        };
230        let evaluate_context = transform.evaluate_context;
231
232        let webpack_loaders_executor = webpack_loaders_executor(*evaluate_context).module();
233
234        let entries = get_evaluate_entries(webpack_loaders_executor, *evaluate_context, None)
235            .to_resolved()
236            .await?;
237
238        let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries(
239            entries.graph_entries().to_resolved().await?,
240            false,
241            false,
242        ))
243        .connect()
244        .to_resolved()
245        .await?;
246
247        let resource_fs_path = self.source.ident().path().await?;
248        let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path) else {
249            bail!(format!(
250                "Resource path \"{}\" need to be on project filesystem \"{}\"",
251                resource_fs_path, project_path
252            ));
253        };
254        let loaders = transform.loaders.await?;
255        let config_value = evaluate_webpack_loader(WebpackLoaderContext {
256            entries,
257            cwd: project_path.clone(),
258            env: *env,
259            context_source_for_issue: self.source,
260            chunking_context: *chunking_context,
261            module_graph,
262            resolve_options_context: Some(transform.resolve_options_context),
263            args: vec![
264                ResolvedVc::cell(content),
265                // We need to pass the query string to the loader
266                ResolvedVc::cell(resource_path.to_string().into()),
267                ResolvedVc::cell(self.source.ident().await?.query.to_string().into()),
268                ResolvedVc::cell(json!(*loaders)),
269                ResolvedVc::cell(transform.source_maps.into()),
270            ],
271            additional_invalidation: Completion::immutable().to_resolved().await?,
272        })
273        .await?;
274
275        let Some(val) = &*config_value else {
276            // An error happened, which has already been converted into an issue.
277            return Ok(ProcessWebpackLoadersResult {
278                content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
279                assets: Vec::new(),
280                source_map: FileContent::NotFound.resolved_cell(),
281            }
282            .cell());
283        };
284        let processed: WebpackLoadersProcessingResult = parse_json_with_source_context(val)
285            .context("Unable to deserializate response from webpack loaders transform operation")?;
286
287        // handle SourceMap
288        let source_map = if !transform.source_maps {
289            None
290        } else {
291            processed
292                .map
293                .map(|source_map| Rope::from(source_map.into_owned()))
294        };
295        let source_map = resolve_source_map_sources(source_map.as_ref(), &resource_fs_path).await?;
296
297        let file = match processed.source {
298            Either::Left(str) => File::from(str),
299            Either::Right(bytes) => File::from(bytes.binary),
300        };
301        let assets = emitted_assets_to_virtual_sources(processed.assets).await?;
302
303        let content =
304            AssetContent::File(FileContent::Content(file).resolved_cell()).resolved_cell();
305        Ok(ProcessWebpackLoadersResult {
306            content,
307            assets,
308            source_map: if let Some(source_map) = source_map {
309                FileContent::Content(File::from(source_map)).resolved_cell()
310            } else {
311                FileContent::NotFound.resolved_cell()
312            },
313        }
314        .cell())
315    }
316}
317
318#[turbo_tasks::function]
319pub(crate) async fn evaluate_webpack_loader(
320    webpack_loader_context: WebpackLoaderContext,
321) -> Result<Vc<Option<RcStr>>> {
322    custom_evaluate(webpack_loader_context).await
323}
324
325#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
326#[serde(rename_all = "camelCase")]
327enum LogType {
328    Error,
329    Warn,
330    Info,
331    Log,
332    Debug,
333    Trace,
334    Group,
335    GroupCollapsed,
336    GroupEnd,
337    Profile,
338    ProfileEnd,
339    Time,
340    Clear,
341    Status,
342}
343
344#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
345#[serde(rename_all = "camelCase")]
346pub struct LogInfo {
347    time: u64,
348    log_type: LogType,
349    #[bincode(with = "turbo_bincode::serde_self_describing")]
350    args: Vec<JsonValue>,
351    trace: Option<Vec<StackFrame<'static>>>,
352}
353
354#[derive(Deserialize, Debug)]
355#[serde(tag = "type", rename_all = "camelCase")]
356pub enum InfoMessage {
357    // Sent to inform Turbopack about the dependencies of the task.
358    // All fields are `default` since it is ok for the client to
359    // simply omit instead of sending empty arrays.
360    #[serde(rename_all = "camelCase")]
361    Dependencies {
362        #[serde(default)]
363        env_variables: Vec<RcStr>,
364        #[serde(default)]
365        file_paths: Vec<RcStr>,
366        #[serde(default)]
367        directories: Vec<(RcStr, RcStr)>,
368        #[serde(default)]
369        build_file_paths: Vec<RcStr>,
370    },
371    EmittedError {
372        severity: IssueSeverity,
373        error: StructuredError,
374    },
375    Log {
376        logs: Vec<LogInfo>,
377    },
378}
379
380#[derive(
381    Debug, Clone, TaskInput, Hash, PartialEq, Eq, Deserialize, TraceRawVcs, Encode, Decode,
382)]
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, Debug, TraceRawVcs, Encode, Decode)]
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}