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