turbopack_node/transforms/
webpack.rs

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