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