1use std::mem::take;
2
3use anyhow::{Context, Result, bail};
4use base64::Engine;
5use either::Either;
6use futures::try_join;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map as JsonMap, Value as JsonValue, json};
9use serde_with::serde_as;
10use turbo_rcstr::{RcStr, rcstr};
11use turbo_tasks::{
12 Completion, NonLocalValue, OperationValue, OperationVc, ResolvedVc, TaskInput, TryJoinIterExt,
13 ValueToString, Vc, trace::TraceRawVcs,
14};
15use turbo_tasks_env::ProcessEnv;
16use turbo_tasks_fs::{
17 File, FileContent, FileSystemPath,
18 glob::{Glob, GlobOptions},
19 json::parse_json_with_source_context,
20 rope::Rope,
21};
22use turbopack_core::{
23 asset::{Asset, AssetContent},
24 chunk::ChunkingContext,
25 context::{AssetContext, ProcessResult},
26 file_source::FileSource,
27 ident::AssetIdent,
28 issue::{
29 Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
30 OptionStyledString, StyledString,
31 },
32 module_graph::ModuleGraph,
33 reference_type::{InnerAssets, ReferenceType},
34 resolve::{
35 options::{ConditionValue, ResolveInPackage, ResolveIntoPackage, ResolveOptions},
36 parse::Request,
37 pattern::Pattern,
38 resolve,
39 },
40 source::Source,
41 source_map::{
42 GenerateSourceMap, OptionStringifiedSourceMap, utils::resolve_source_map_sources,
43 },
44 source_transform::SourceTransform,
45 virtual_source::VirtualSource,
46};
47use turbopack_resolve::{
48 ecmascript::get_condition_maps, resolve::resolve_options,
49 resolve_options_context::ResolveOptionsContext,
50};
51
52use super::util::{EmittedAsset, emitted_assets_to_virtual_sources};
53use crate::{
54 AssetsForSourceMapping,
55 debug::should_debug,
56 embed_js::embed_file_path,
57 evaluate::{
58 EnvVarTracking, EvaluateContext, EvaluateEntries, EvaluationIssue, custom_evaluate,
59 get_evaluate_entries, get_evaluate_pool,
60 },
61 execution_context::ExecutionContext,
62 pool::{FormattingMode, NodeJsPool},
63 source_map::{StackFrame, StructuredError},
64};
65
66#[serde_as]
67#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
68struct BytesBase64 {
69 #[serde_as(as = "serde_with::base64::Base64")]
70 binary: Vec<u8>,
71}
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
74#[serde(rename_all = "camelCase")]
75#[turbo_tasks::value(serialization = "custom")]
76struct WebpackLoadersProcessingResult {
77 #[serde(with = "either::serde_untagged")]
78 #[turbo_tasks(debug_ignore, trace_ignore)]
79 source: Either<RcStr, BytesBase64>,
80 map: Option<RcStr>,
81 #[turbo_tasks(trace_ignore)]
82 assets: Option<Vec<EmittedAsset>>,
83}
84
85#[derive(
86 Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize, NonLocalValue, OperationValue,
87)]
88pub struct WebpackLoaderItem {
89 pub loader: RcStr,
90 #[serde(default)]
91 pub options: serde_json::Map<String, serde_json::Value>,
92}
93
94#[derive(Debug, Clone)]
95#[turbo_tasks::value(shared, transparent)]
96pub struct WebpackLoaderItems(pub Vec<WebpackLoaderItem>);
97
98#[turbo_tasks::value]
99pub struct WebpackLoaders {
100 evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
101 execution_context: ResolvedVc<ExecutionContext>,
102 loaders: ResolvedVc<WebpackLoaderItems>,
103 rename_as: Option<RcStr>,
104 resolve_options_context: ResolvedVc<ResolveOptionsContext>,
105 source_maps: bool,
106}
107
108#[turbo_tasks::value_impl]
109impl WebpackLoaders {
110 #[turbo_tasks::function]
111 pub fn new(
112 evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
113 execution_context: ResolvedVc<ExecutionContext>,
114 loaders: ResolvedVc<WebpackLoaderItems>,
115 rename_as: Option<RcStr>,
116 resolve_options_context: ResolvedVc<ResolveOptionsContext>,
117 source_maps: bool,
118 ) -> Vc<Self> {
119 WebpackLoaders {
120 evaluate_context,
121 execution_context,
122 loaders,
123 rename_as,
124 resolve_options_context,
125 source_maps,
126 }
127 .cell()
128 }
129}
130
131#[turbo_tasks::value_impl]
132impl SourceTransform for WebpackLoaders {
133 #[turbo_tasks::function]
134 fn transform(
135 self: ResolvedVc<Self>,
136 source: ResolvedVc<Box<dyn Source>>,
137 ) -> Vc<Box<dyn Source>> {
138 Vc::upcast(
139 WebpackLoadersProcessedAsset {
140 transform: self,
141 source,
142 }
143 .cell(),
144 )
145 }
146}
147
148#[turbo_tasks::value]
149struct WebpackLoadersProcessedAsset {
150 transform: ResolvedVc<WebpackLoaders>,
151 source: ResolvedVc<Box<dyn Source>>,
152}
153
154#[turbo_tasks::value_impl]
155impl Source for WebpackLoadersProcessedAsset {
156 #[turbo_tasks::function]
157 async fn ident(&self) -> Result<Vc<AssetIdent>> {
158 Ok(
159 if let Some(rename_as) = self.transform.await?.rename_as.as_deref() {
160 self.source.ident().rename_as(rename_as.into())
161 } else {
162 self.source.ident()
163 },
164 )
165 }
166}
167
168#[turbo_tasks::value_impl]
169impl Asset for WebpackLoadersProcessedAsset {
170 #[turbo_tasks::function]
171 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
172 Ok(*self.process().await?.content)
173 }
174}
175
176#[turbo_tasks::value_impl]
177impl GenerateSourceMap for WebpackLoadersProcessedAsset {
178 #[turbo_tasks::function]
179 async fn generate_source_map(self: Vc<Self>) -> Result<Vc<OptionStringifiedSourceMap>> {
180 Ok(*self.process().await?.source_map)
181 }
182}
183
184#[turbo_tasks::value]
185struct ProcessWebpackLoadersResult {
186 content: ResolvedVc<AssetContent>,
187 source_map: ResolvedVc<OptionStringifiedSourceMap>,
188 assets: Vec<ResolvedVc<VirtualSource>>,
189}
190
191#[turbo_tasks::function]
192async fn webpack_loaders_executor(
193 evaluate_context: Vc<Box<dyn AssetContext>>,
194) -> Result<Vc<ProcessResult>> {
195 Ok(evaluate_context.process(
196 Vc::upcast(FileSource::new(
197 embed_file_path(rcstr!("transforms/webpack-loaders.ts"))
198 .owned()
199 .await?,
200 )),
201 ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
202 ))
203}
204
205#[turbo_tasks::value_impl]
206impl WebpackLoadersProcessedAsset {
207 #[turbo_tasks::function]
208 async fn process(self: Vc<Self>) -> Result<Vc<ProcessWebpackLoadersResult>> {
209 let this = self.await?;
210 let transform = this.transform.await?;
211
212 let ExecutionContext {
213 project_path,
214 chunking_context,
215 env,
216 } = &*transform.execution_context.await?;
217 let source_content = this.source.content();
218 let AssetContent::File(file) = *source_content.await? else {
219 bail!("Webpack Loaders transform only support transforming files");
220 };
221 let FileContent::Content(file_content) = &*file.await? else {
222 return Ok(ProcessWebpackLoadersResult {
223 content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
224 assets: Vec::new(),
225 source_map: ResolvedVc::cell(None),
226 }
227 .cell());
228 };
229
230 let content: JsonValue = match file_content.content().to_str() {
233 Ok(utf8_str) => utf8_str.to_string().into(),
234 Err(_) => JsonValue::Object(JsonMap::from_iter(std::iter::once((
235 "binary".to_string(),
236 JsonValue::from(
237 base64::engine::general_purpose::STANDARD
238 .encode(file_content.content().to_bytes()),
239 ),
240 )))),
241 };
242 let evaluate_context = transform.evaluate_context;
243
244 let webpack_loaders_executor = webpack_loaders_executor(*evaluate_context).module();
245
246 let entries = get_evaluate_entries(webpack_loaders_executor, *evaluate_context, None)
247 .to_resolved()
248 .await?;
249
250 let module_graph = ModuleGraph::from_modules(entries.graph_entries(), false)
251 .to_resolved()
252 .await?;
253
254 let resource_fs_path = this.source.ident().path().await?;
255 let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path) else {
256 bail!(format!(
257 "Resource path \"{}\" need to be on project filesystem \"{}\"",
258 resource_fs_path, project_path
259 ));
260 };
261 let loaders = transform.loaders.await?;
262 let config_value = evaluate_webpack_loader(WebpackLoaderContext {
263 entries,
264 cwd: project_path.clone(),
265 env: *env,
266 context_source_for_issue: this.source,
267 chunking_context: *chunking_context,
268 module_graph,
269 resolve_options_context: Some(transform.resolve_options_context),
270 args: vec![
271 ResolvedVc::cell(content),
272 ResolvedVc::cell(resource_path.to_string().into()),
274 ResolvedVc::cell(this.source.ident().await?.query.to_string().into()),
275 ResolvedVc::cell(json!(*loaders)),
276 ResolvedVc::cell(transform.source_maps.into()),
277 ],
278 additional_invalidation: Completion::immutable().to_resolved().await?,
279 })
280 .await?;
281
282 let Some(val) = &*config_value else {
283 return Ok(ProcessWebpackLoadersResult {
285 content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
286 assets: Vec::new(),
287 source_map: ResolvedVc::cell(None),
288 }
289 .cell());
290 };
291 let processed: WebpackLoadersProcessingResult = parse_json_with_source_context(val)
292 .context("Unable to deserializate response from webpack loaders transform operation")?;
293
294 let source_map = if !transform.source_maps {
296 None
297 } else {
298 processed
299 .map
300 .map(|source_map| Rope::from(source_map.into_owned()))
301 };
302 let source_map = resolve_source_map_sources(source_map.as_ref(), &resource_fs_path).await?;
303
304 let file = match processed.source {
305 Either::Left(str) => File::from(str),
306 Either::Right(bytes) => File::from(bytes.binary),
307 };
308 let assets = emitted_assets_to_virtual_sources(processed.assets).await?;
309
310 let content =
311 AssetContent::File(FileContent::Content(file).resolved_cell()).resolved_cell();
312 Ok(ProcessWebpackLoadersResult {
313 content,
314 assets,
315 source_map: ResolvedVc::cell(source_map),
316 }
317 .cell())
318 }
319}
320
321#[turbo_tasks::function]
322pub(crate) async fn evaluate_webpack_loader(
323 webpack_loader_context: WebpackLoaderContext,
324) -> Result<Vc<Option<RcStr>>> {
325 custom_evaluate(webpack_loader_context).await
326}
327
328#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
329#[serde(rename_all = "camelCase")]
330enum LogType {
331 Error,
332 Warn,
333 Info,
334 Log,
335 Debug,
336 Trace,
337 Group,
338 GroupCollapsed,
339 GroupEnd,
340 Profile,
341 ProfileEnd,
342 Time,
343 Clear,
344 Status,
345}
346
347#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
348#[serde(rename_all = "camelCase")]
349pub struct LogInfo {
350 time: u64,
351 log_type: LogType,
352 args: Vec<JsonValue>,
353 trace: Option<Vec<StackFrame<'static>>>,
354}
355
356#[derive(Deserialize, Debug)]
357#[serde(tag = "type", rename_all = "camelCase")]
358pub enum InfoMessage {
359 #[serde(rename_all = "camelCase")]
363 Dependencies {
364 #[serde(default)]
365 env_variables: Vec<RcStr>,
366 #[serde(default)]
367 file_paths: Vec<RcStr>,
368 #[serde(default)]
369 directories: Vec<(RcStr, RcStr)>,
370 #[serde(default)]
371 build_file_paths: Vec<RcStr>,
372 },
373 EmittedError {
374 severity: IssueSeverity,
375 error: StructuredError,
376 },
377 Log {
378 logs: Vec<LogInfo>,
379 },
380}
381
382#[derive(Debug, Clone, TaskInput, Hash, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
383#[serde(rename_all = "camelCase")]
384pub struct WebpackResolveOptions {
385 alias_fields: Option<Vec<RcStr>>,
386 condition_names: Option<Vec<RcStr>>,
387 no_package_json: bool,
388 extensions: Option<Vec<RcStr>>,
389 main_fields: Option<Vec<RcStr>>,
390 no_exports_field: bool,
391 main_files: Option<Vec<RcStr>>,
392 no_modules: bool,
393 prefer_relative: bool,
394}
395
396#[derive(Deserialize, Debug)]
397#[serde(tag = "type", rename_all = "camelCase")]
398pub enum RequestMessage {
399 #[serde(rename_all = "camelCase")]
400 Resolve {
401 options: WebpackResolveOptions,
402 lookup_path: RcStr,
403 request: RcStr,
404 },
405 #[serde(rename_all = "camelCase")]
406 TrackFileRead { file: RcStr },
407}
408
409#[derive(Serialize, Debug)]
410#[serde(untagged)]
411pub enum ResponseMessage {
412 Resolve { path: RcStr },
413 TrackFileRead {},
415}
416
417#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Serialize, Deserialize, Debug, TraceRawVcs)]
418pub struct WebpackLoaderContext {
419 pub entries: ResolvedVc<EvaluateEntries>,
420 pub cwd: FileSystemPath,
421 pub env: ResolvedVc<Box<dyn ProcessEnv>>,
422 pub context_source_for_issue: ResolvedVc<Box<dyn Source>>,
423 pub module_graph: ResolvedVc<ModuleGraph>,
424 pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
425 pub resolve_options_context: Option<ResolvedVc<ResolveOptionsContext>>,
426 pub args: Vec<ResolvedVc<JsonValue>>,
427 pub additional_invalidation: ResolvedVc<Completion>,
428}
429
430impl EvaluateContext for WebpackLoaderContext {
431 type InfoMessage = InfoMessage;
432 type RequestMessage = RequestMessage;
433 type ResponseMessage = ResponseMessage;
434 type State = Vec<LogInfo>;
435
436 fn pool(&self) -> OperationVc<crate::pool::NodeJsPool> {
437 get_evaluate_pool(
438 self.entries,
439 self.cwd.clone(),
440 self.env,
441 self.chunking_context,
442 self.module_graph,
443 self.additional_invalidation,
444 should_debug("webpack_loader"),
445 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 if turbo_tasks::turbo_tasks().is_tracking_dependencies() {
493 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 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#[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}