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