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, ChunkingContextExt, EvaluatableAsset},
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::{
35 ModuleGraph, SingleModuleGraph,
36 chunk_group_info::{ChunkGroup, ChunkGroupEntry},
37 },
38 output::{ExpandOutputAssetsInput, OutputAsset, OutputAssets, expand_output_assets},
39 reference_type::{EcmaScriptModulesReferenceSubType, InnerAssets, ReferenceType},
40 resolve::{
41 ResolveErrorMode,
42 options::{ConditionValue, ResolveInPackage, ResolveIntoPackage, ResolveOptions},
43 origin::PlainResolveOrigin,
44 parse::Request,
45 pattern::Pattern,
46 resolve,
47 },
48 source::Source,
49 source_map::{GenerateSourceMap, utils::resolve_source_map_sources},
50 source_transform::SourceTransform,
51 virtual_source::VirtualSource,
52};
53use turbopack_resolve::{
54 ecmascript::{esm_resolve, get_condition_maps},
55 resolve::resolve_options,
56 resolve_options_context::ResolveOptionsContext,
57};
58
59use crate::{
60 AssetsForSourceMapping,
61 backend::NodeBackend,
62 debug::should_debug,
63 embed_js::embed_file_path,
64 evaluate::{
65 EnvVarTracking, EvaluateContext, EvaluateEntries, EvaluatePool, EvaluationIssue,
66 custom_evaluate, get_evaluate_entries, get_evaluate_pool,
67 },
68 execution_context::ExecutionContext,
69 format::FormattingMode,
70 source_map::{StackFrame, StructuredError},
71 transforms::util::{EmittedAsset, emitted_assets_to_virtual_sources},
72};
73
74#[serde_as]
75#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Encode, Decode)]
76struct BytesBase64 {
77 #[serde_as(as = "serde_with::base64::Base64")]
78 binary: Vec<u8>,
79}
80
81#[derive(Debug, Clone, Deserialize)]
82#[turbo_tasks::value]
83#[serde(rename_all = "camelCase")]
84struct WebpackLoadersProcessingResult {
85 #[serde(with = "either::serde_untagged")]
86 #[bincode(with = "turbo_bincode::either")]
87 #[turbo_tasks(debug_ignore, trace_ignore)]
88 source: Either<RcStr, BytesBase64>,
89 map: Option<RcStr>,
90 #[turbo_tasks(trace_ignore)]
91 assets: Option<Vec<EmittedAsset>>,
92}
93
94pub use turbopack_core::loader::{WebpackLoaderItem, WebpackLoaderItems};
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 asset_context: ResolvedVc<Box<dyn AssetContext>>,
136 ) -> Vc<Box<dyn Source>> {
137 Vc::upcast(
138 WebpackLoadersProcessedAsset {
139 transform: self,
140 source,
141 asset_context,
142 }
143 .cell(),
144 )
145 }
146}
147
148#[turbo_tasks::value]
149struct WebpackLoadersProcessedAsset {
150 transform: ResolvedVc<WebpackLoaders>,
151 source: ResolvedVc<Box<dyn Source>>,
152 asset_context: ResolvedVc<Box<dyn AssetContext>>,
153}
154
155#[turbo_tasks::value_impl]
156impl Source for WebpackLoadersProcessedAsset {
157 #[turbo_tasks::function]
158 async fn ident(&self) -> Result<Vc<AssetIdent>> {
159 Ok(
160 if let Some(rename_as) = self.transform.await?.rename_as.as_deref() {
161 self.source.ident().rename_as(rename_as.into())
162 } else {
163 self.source.ident()
164 },
165 )
166 }
167
168 #[turbo_tasks::function]
169 async fn description(&self) -> Result<Vc<RcStr>> {
170 let inner = self.source.description().await?;
171 let loaders = self.transform.await?.loaders.await?;
172 let loader_names: Vec<&str> = loaders.iter().map(|l| l.loader.as_str()).collect();
173 Ok(Vc::cell(
174 format!(
175 "loaders [{}] transform of {}",
176 loader_names.join(", "),
177 inner
178 )
179 .into(),
180 ))
181 }
182}
183
184#[turbo_tasks::value_impl]
185impl Asset for WebpackLoadersProcessedAsset {
186 #[turbo_tasks::function]
187 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
188 Ok(*self.process().await?.content)
189 }
190}
191
192#[turbo_tasks::value_impl]
193impl GenerateSourceMap for WebpackLoadersProcessedAsset {
194 #[turbo_tasks::function]
195 async fn generate_source_map(self: Vc<Self>) -> Result<Vc<FileContent>> {
196 Ok(*self.process().await?.source_map)
197 }
198}
199
200#[turbo_tasks::value]
201struct ProcessWebpackLoadersResult {
202 content: ResolvedVc<AssetContent>,
203 source_map: ResolvedVc<FileContent>,
204 assets: Vec<ResolvedVc<VirtualSource>>,
205}
206
207#[turbo_tasks::function]
208async fn webpack_loaders_executor(
209 evaluate_context: Vc<Box<dyn AssetContext>>,
210) -> Result<Vc<ProcessResult>> {
211 Ok(evaluate_context.process(
212 Vc::upcast(FileSource::new(
213 embed_file_path(rcstr!("transforms/webpack-loaders.ts"))
214 .owned()
215 .await?,
216 )),
217 ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
218 ))
219}
220
221#[turbo_tasks::value_impl]
222impl WebpackLoadersProcessedAsset {
223 #[turbo_tasks::function]
224 async fn process(&self) -> Result<Vc<ProcessWebpackLoadersResult>> {
225 let transform = self.transform.await?;
226 let loaders = transform.loaders.await?;
227
228 let webpack_span = tracing::info_span!(
229 "webpack loader",
230 name = display(ReadRef::<WebpackLoaderItems>::as_raw_ref(&loaders))
231 );
232
233 async {
234 let ExecutionContext {
235 project_path,
236 chunking_context,
237 env,
238 node_backend,
239 } = &*transform.execution_context.await?;
240 let source_content = self.source.content();
241 let AssetContent::File(file) = *source_content.await? else {
242 bail!("Webpack Loaders transform only support transforming files");
243 };
244 let FileContent::Content(file_content) = &*file.await? else {
245 return Ok(ProcessWebpackLoadersResult {
246 content: AssetContent::File(FileContent::NotFound.resolved_cell())
247 .resolved_cell(),
248 assets: Vec::new(),
249 source_map: FileContent::NotFound.resolved_cell(),
250 }
251 .cell());
252 };
253
254 let content: JsonValue = match file_content.content().to_str() {
257 Ok(utf8_str) => utf8_str.to_string().into(),
258 Err(_) => JsonValue::Object(JsonMap::from_iter(std::iter::once((
259 "binary".to_string(),
260 JsonValue::from(
261 base64::engine::general_purpose::STANDARD
262 .encode(file_content.content().to_bytes()),
263 ),
264 )))),
265 };
266 let evaluate_context = transform.evaluate_context;
267
268 let webpack_loaders_executor = webpack_loaders_executor(*evaluate_context).module();
269
270 let entries = get_evaluate_entries(
271 webpack_loaders_executor,
272 *evaluate_context,
273 **node_backend,
274 None,
275 )
276 .to_resolved()
277 .await?;
278
279 let module_graph = ModuleGraph::from_single_graph(SingleModuleGraph::new_with_entries(
280 entries.graph_entries().to_resolved().await?,
281 false,
282 false,
283 ))
284 .connect()
285 .to_resolved()
286 .await?;
287
288 let resource_fs_path = self.source.ident().path().await?;
289 let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path) else {
290 bail!(
291 "Resource path \"{}\" needs to be on project filesystem \"{}\"",
292 resource_fs_path,
293 project_path
294 );
295 };
296 let config_value = evaluate_webpack_loader(WebpackLoaderContext {
297 entries,
298 cwd: project_path.clone(),
299 env: *env,
300 node_backend: *node_backend,
301 context_source_for_issue: self.source,
302 chunking_context: *chunking_context,
303 evaluate_context: transform.evaluate_context,
304 module_graph,
305 resolve_options_context: Some(transform.resolve_options_context),
306 asset_context: self.asset_context,
307 args: vec![
308 ResolvedVc::cell(content),
309 ResolvedVc::cell(resource_path.to_string().into()),
311 ResolvedVc::cell(self.source.ident().await?.query.to_string().into()),
312 ResolvedVc::cell(json!(*loaders)),
313 ResolvedVc::cell(transform.source_maps.into()),
314 ],
315 additional_invalidation: Completion::immutable().to_resolved().await?,
316 })
317 .await?;
318
319 let Some(val) = &*config_value else {
320 return Ok(ProcessWebpackLoadersResult {
322 content: AssetContent::File(FileContent::NotFound.resolved_cell())
323 .resolved_cell(),
324 assets: Vec::new(),
325 source_map: FileContent::NotFound.resolved_cell(),
326 }
327 .cell());
328 };
329 let processed: WebpackLoadersProcessingResult = parse_json_with_source_context(val)
330 .context(
331 "Unable to deserializate response from webpack loaders transform operation",
332 )?;
333
334 let source_map = if !transform.source_maps {
336 None
337 } else {
338 processed
339 .map
340 .map(|source_map| Rope::from(source_map.into_owned()))
341 };
342 let source_map =
343 resolve_source_map_sources(source_map.as_ref(), &resource_fs_path).await?;
344
345 let file = match processed.source {
346 Either::Left(str) => File::from(str),
347 Either::Right(bytes) => File::from(bytes.binary),
348 };
349 let assets = emitted_assets_to_virtual_sources(processed.assets).await?;
350
351 let content =
352 AssetContent::File(FileContent::Content(file).resolved_cell()).resolved_cell();
353 Ok(ProcessWebpackLoadersResult {
354 content,
355 assets,
356 source_map: if let Some(source_map) = source_map {
357 FileContent::Content(File::from(source_map)).resolved_cell()
358 } else {
359 FileContent::NotFound.resolved_cell()
360 },
361 }
362 .cell())
363 }
364 .instrument(webpack_span)
365 .await
366 }
367}
368
369#[turbo_tasks::function]
370pub(crate) async fn evaluate_webpack_loader(
371 webpack_loader_context: WebpackLoaderContext,
372) -> Result<Vc<Option<RcStr>>> {
373 custom_evaluate(webpack_loader_context).await
374}
375
376#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
377#[serde(rename_all = "camelCase")]
378enum LogType {
379 Error,
380 Warn,
381 Info,
382 Log,
383 Debug,
384 Trace,
385 Group,
386 GroupCollapsed,
387 GroupEnd,
388 Profile,
389 ProfileEnd,
390 Time,
391 Clear,
392 Status,
393}
394
395#[derive(Deserialize, Debug, PartialEq, Eq, Encode, Decode)]
396#[serde(rename_all = "camelCase")]
397pub struct LogInfo {
398 time: u64,
399 log_type: LogType,
400 #[bincode(with = "turbo_bincode::serde_self_describing")]
401 args: Vec<JsonValue>,
402 trace: Option<Vec<StackFrame<'static>>>,
403}
404
405#[derive(Deserialize, Debug)]
406#[serde(tag = "type", rename_all = "camelCase")]
407pub enum InfoMessage {
408 #[serde(rename_all = "camelCase")]
412 Dependencies {
413 #[serde(default)]
414 env_variables: Vec<RcStr>,
415 #[serde(default)]
416 file_paths: Vec<RcStr>,
417 #[serde(default)]
418 directories: Vec<(RcStr, RcStr)>,
419 #[serde(default)]
420 build_file_paths: Vec<RcStr>,
421 },
422 EmittedError {
423 severity: IssueSeverity,
424 error: StructuredError,
425 },
426 Log {
427 logs: Vec<LogInfo>,
428 },
429}
430
431#[derive(
432 Debug, Clone, TaskInput, Hash, PartialEq, Eq, Deserialize, TraceRawVcs, Encode, Decode,
433)]
434#[serde(rename_all = "camelCase")]
435pub struct WebpackResolveOptions {
436 alias_fields: Option<Vec<RcStr>>,
437 condition_names: Option<Vec<RcStr>>,
438 no_package_json: bool,
439 extensions: Option<Vec<RcStr>>,
440 main_fields: Option<Vec<RcStr>>,
441 no_exports_field: bool,
442 main_files: Option<Vec<RcStr>>,
443 no_modules: bool,
444 prefer_relative: bool,
445}
446
447#[derive(Deserialize, Debug)]
448#[serde(tag = "type", rename_all = "camelCase")]
449pub enum RequestMessage {
450 #[serde(rename_all = "camelCase")]
451 Resolve {
452 options: WebpackResolveOptions,
453 lookup_path: RcStr,
454 request: RcStr,
455 },
456 #[serde(rename_all = "camelCase")]
457 TrackFileRead { file: RcStr },
458 #[serde(rename_all = "camelCase")]
459 ImportModule { lookup_path: RcStr, request: RcStr },
460}
461
462#[derive(Serialize, Debug)]
463#[serde(rename_all = "camelCase")]
464pub struct ImportModuleChunk {
465 path: RcStr,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 code: Option<RcStr>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 binary: Option<String>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 source_map: Option<RcStr>,
472}
473
474#[derive(Serialize, Debug)]
475#[serde(untagged)]
476pub enum ResponseMessage {
477 Resolve {
478 path: RcStr,
479 },
480 TrackFileRead {},
482 #[serde(rename_all = "camelCase")]
483 ImportModule {
484 entry_path: RcStr,
485 chunks: Vec<ImportModuleChunk>,
486 },
487}
488
489#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Debug, TraceRawVcs, Encode, Decode)]
490pub struct WebpackLoaderContext {
491 pub entries: ResolvedVc<EvaluateEntries>,
492 pub cwd: FileSystemPath,
493 pub env: ResolvedVc<Box<dyn ProcessEnv>>,
494 pub node_backend: ResolvedVc<Box<dyn NodeBackend>>,
495 pub context_source_for_issue: ResolvedVc<Box<dyn Source>>,
496 pub module_graph: ResolvedVc<ModuleGraph>,
497 pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
498 pub evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
499 pub resolve_options_context: Option<ResolvedVc<ResolveOptionsContext>>,
500 pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
501 pub args: Vec<ResolvedVc<JsonValue>>,
502 pub additional_invalidation: ResolvedVc<Completion>,
503}
504
505impl EvaluateContext for WebpackLoaderContext {
506 type InfoMessage = InfoMessage;
507 type RequestMessage = RequestMessage;
508 type ResponseMessage = ResponseMessage;
509 type State = Vec<LogInfo>;
510
511 fn pool(&self) -> OperationVc<EvaluatePool> {
512 get_evaluate_pool(
513 self.entries,
514 self.cwd.clone(),
515 self.env,
516 self.node_backend,
517 self.chunking_context,
518 self.module_graph,
519 self.additional_invalidation,
520 should_debug("webpack_loader"),
521 EnvVarTracking::Untracked,
525 )
526 }
527
528 fn args(&self) -> &[ResolvedVc<serde_json::Value>] {
529 &self.args
530 }
531
532 fn cwd(&self) -> Vc<turbo_tasks_fs::FileSystemPath> {
533 self.cwd.clone().cell()
534 }
535
536 fn keep_alive(&self) -> bool {
537 true
538 }
539
540 async fn emit_error(&self, error: StructuredError, pool: &EvaluatePool) -> Result<()> {
541 EvaluationIssue {
542 error,
543 source: IssueSource::from_source_only(self.context_source_for_issue),
544 assets_for_source_mapping: pool.assets_for_source_mapping,
545 assets_root: pool.assets_root.clone(),
546 root_path: self.chunking_context.root_path().owned().await?,
547 }
548 .resolved_cell()
549 .emit();
550 Ok(())
551 }
552
553 async fn info(
554 &self,
555 state: &mut Self::State,
556 data: Self::InfoMessage,
557 pool: &EvaluatePool,
558 ) -> Result<()> {
559 match data {
560 InfoMessage::Dependencies {
561 env_variables,
562 file_paths,
563 directories,
564 build_file_paths,
565 } => {
566 if turbo_tasks::turbo_tasks().is_tracking_dependencies() {
569 let env_subscriptions = env_variables
577 .iter()
578 .map(|e| self.env.read(e.clone()))
579 .try_join();
580 let file_subscriptions = file_paths
581 .iter()
582 .map(|p| async move { self.cwd.join(p)?.read().await })
583 .try_join();
584 let directory_subscriptions = directories
585 .iter()
586 .map(|(dir, glob)| async move {
587 self.cwd
588 .join(dir)?
589 .track_glob(Glob::new(glob.clone(), GlobOptions::default()), false)
590 .await
591 })
592 .try_join();
593 try_join!(
594 env_subscriptions,
595 file_subscriptions,
596 directory_subscriptions
597 )?;
598
599 for build_path in build_file_paths {
600 let build_path = self.cwd.join(&build_path)?;
601 BuildDependencyIssue {
602 source: IssueSource::from_source_only(self.context_source_for_issue),
603 path: build_path,
604 }
605 .resolved_cell()
606 .emit();
607 }
608 }
609 }
610 InfoMessage::EmittedError { error, severity } => {
611 EvaluateEmittedErrorIssue {
612 source: IssueSource::from_source_only(self.context_source_for_issue),
613 error,
614 severity,
615 assets_for_source_mapping: pool.assets_for_source_mapping,
616 assets_root: pool.assets_root.clone(),
617 project_dir: self.chunking_context.root_path().owned().await?,
618 }
619 .resolved_cell()
620 .emit();
621 }
622 InfoMessage::Log { logs } => {
623 state.extend(logs);
624 }
625 }
626 Ok(())
627 }
628
629 async fn request(
630 &self,
631 _state: &mut Self::State,
632 data: Self::RequestMessage,
633 _pool: &EvaluatePool,
634 ) -> Result<Self::ResponseMessage> {
635 match data {
636 RequestMessage::Resolve {
637 options: webpack_options,
638 lookup_path,
639 request,
640 } => {
641 let Some(resolve_options_context) = self.resolve_options_context else {
642 bail!("Resolve options are not available in this context");
643 };
644 let lookup_path = self.cwd.join(&lookup_path)?;
645 let request = Request::parse(Pattern::Constant(request));
646 let options = resolve_options(lookup_path.clone(), *resolve_options_context);
647
648 let options = apply_webpack_resolve_options(options, webpack_options);
649
650 let resolved = resolve(
651 lookup_path.clone(),
652 ReferenceType::Undefined,
653 request,
654 options,
655 );
656
657 if let Some(source) = *resolved.first_source().await? {
658 if let Some(path) = self
659 .cwd
660 .get_relative_path_to(&*source.ident().path().await?)
661 {
662 Ok(ResponseMessage::Resolve { path })
663 } else {
664 bail!(
665 "Resolving {} in {} ends up on a different filesystem",
666 request.to_string().await?,
667 lookup_path.value_to_string().await?
668 );
669 }
670 } else {
671 bail!(
672 "Unable to resolve {} in {}",
673 request.to_string().await?,
674 lookup_path.value_to_string().await?
675 );
676 }
677 }
678 RequestMessage::TrackFileRead { file } => {
679 let _ = &*self.cwd.join(&file)?.read().await?;
682 Ok(ResponseMessage::TrackFileRead {})
683 }
684 RequestMessage::ImportModule {
685 lookup_path,
686 request,
687 } => {
688 let lookup_path = self.cwd.join(&lookup_path)?;
689
690 let request_vc = Request::parse(Pattern::Constant(request.clone()));
691 let origin = PlainResolveOrigin::new(*self.asset_context, lookup_path.join("_")?);
692 let resolved = esm_resolve(
693 Vc::upcast(origin),
694 request_vc,
695 EcmaScriptModulesReferenceSubType::ImportModule,
696 ResolveErrorMode::Error,
697 Some(IssueSource::from_source_only(self.context_source_for_issue)),
698 )
699 .await?;
700
701 let Some(module) = *resolved.first_module().await? else {
702 bail!(
703 "importModule: unable to resolve {} in {}",
704 request,
705 lookup_path.value_to_string().await?
706 );
707 };
708
709 let evaluatable = ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(module)
711 .context("importModule: module is not evaluatable")?;
712
713 let single_graph = SingleModuleGraph::new_with_entry(
716 ChunkGroupEntry::Entry(vec![module]),
717 false,
718 false,
719 );
720 let import_module_graph = ModuleGraph::from_single_graph(single_graph)
721 .connect()
722 .to_resolved()
723 .await?;
724
725 let output_root = self.chunking_context.output_root().owned().await?;
727 let entry_path = output_root.join("importModule.js")?;
728
729 let bootstrap = self.chunking_context.root_entry_chunk_group_asset(
730 entry_path.clone(),
731 ChunkGroup::Entry(vec![ResolvedVc::upcast(evaluatable)]),
732 *import_module_graph,
733 OutputAssets::empty(),
734 OutputAssets::empty(),
735 );
736
737 let bootstrap_resolved = bootstrap.to_resolved().await?;
739 let all_assets = expand_output_assets(
740 std::iter::once(ExpandOutputAssetsInput::Asset(bootstrap_resolved)),
741 true,
742 )
743 .await?;
744
745 let mut chunks = Vec::new();
746 for asset in all_assets {
747 let asset_path = asset.path().owned().await?;
748 if !asset_path.is_inside_ref(&output_root) {
749 continue;
750 }
751 let Some(rel_path) = output_root.get_path_to(&asset_path) else {
752 continue;
753 };
754 if rel_path.ends_with(".map") {
756 continue;
757 }
758 let content = asset.content().await?;
759 let AssetContent::File(file_vc) = *content else {
760 continue;
761 };
762 let file_content = file_vc.await?;
763 let FileContent::Content(file) = &*file_content else {
764 continue;
765 };
766
767 if rel_path.ends_with(".js") {
768 let code: RcStr = file.content().to_str()?.into_owned().into();
770 chunks.push(ImportModuleChunk {
771 path: rel_path.into(),
772 code: Some(code),
773 binary: None,
774 source_map: None,
775 });
776 } else {
777 let bytes = file.content().to_bytes();
779 let encoded = base64::engine::general_purpose::STANDARD.encode(&*bytes);
780 chunks.push(ImportModuleChunk {
781 path: rel_path.into(),
782 code: None,
783 binary: Some(encoded),
784 source_map: None,
785 });
786 }
787 }
788
789 let entry_rel = output_root
790 .get_path_to(&entry_path)
791 .context("entry path should be inside output root")?;
792
793 Ok(ResponseMessage::ImportModule {
794 entry_path: entry_rel.into(),
795 chunks,
796 })
797 }
798 }
799 }
800
801 async fn finish(&self, state: Self::State, pool: &EvaluatePool) -> Result<()> {
802 let has_errors = state.iter().any(|log| log.log_type == LogType::Error);
803 let has_warnings = state.iter().any(|log| log.log_type == LogType::Warn);
804 if has_errors || has_warnings {
805 let logs = state
806 .into_iter()
807 .filter(|log| {
808 matches!(
809 log.log_type,
810 LogType::Error
811 | LogType::Warn
812 | LogType::Info
813 | LogType::Log
814 | LogType::Clear,
815 )
816 })
817 .collect();
818
819 EvaluateErrorLoggingIssue {
820 source: IssueSource::from_source_only(self.context_source_for_issue),
821 logging: logs,
822 severity: if has_errors {
823 IssueSeverity::Error
824 } else {
825 IssueSeverity::Warning
826 },
827 assets_for_source_mapping: pool.assets_for_source_mapping,
828 assets_root: pool.assets_root.clone(),
829 project_dir: self.chunking_context.root_path().owned().await?,
830 }
831 .resolved_cell()
832 .emit();
833 }
834 Ok(())
835 }
836}
837
838#[turbo_tasks::function]
839async fn apply_webpack_resolve_options(
840 resolve_options: Vc<ResolveOptions>,
841 webpack_resolve_options: WebpackResolveOptions,
842) -> Result<Vc<ResolveOptions>> {
843 let mut resolve_options = resolve_options.owned().await?;
844 if let Some(alias_fields) = webpack_resolve_options.alias_fields {
845 let mut old = resolve_options
846 .in_package
847 .extract_if(0.., |field| {
848 matches!(field, ResolveInPackage::AliasField(..))
849 })
850 .collect::<Vec<_>>();
851 for field in alias_fields {
852 if &*field == "..." {
853 resolve_options.in_package.extend(take(&mut old));
854 } else {
855 resolve_options
856 .in_package
857 .push(ResolveInPackage::AliasField(field));
858 }
859 }
860 }
861 if let Some(condition_names) = webpack_resolve_options.condition_names {
862 for conditions in get_condition_maps(&mut resolve_options) {
863 let mut old = take(conditions);
864 for name in &condition_names {
865 if name == "..." {
866 conditions.extend(take(&mut old));
867 } else {
868 conditions.insert(name.clone(), ConditionValue::Set);
869 }
870 }
871 }
872 }
873 if webpack_resolve_options.no_package_json {
874 resolve_options.into_package.retain(|item| {
875 !matches!(
876 item,
877 ResolveIntoPackage::ExportsField { .. } | ResolveIntoPackage::MainField { .. }
878 )
879 });
880 }
881 if let Some(mut extensions) = webpack_resolve_options.extensions {
882 if let Some(pos) = extensions.iter().position(|ext| ext == "...") {
883 extensions.splice(pos..=pos, take(&mut resolve_options.extensions));
884 }
885 resolve_options.extensions = extensions;
886 }
887 if let Some(main_fields) = webpack_resolve_options.main_fields {
888 let mut old = resolve_options
889 .into_package
890 .extract_if(0.., |field| {
891 matches!(field, ResolveIntoPackage::MainField { .. })
892 })
893 .collect::<Vec<_>>();
894 for field in main_fields {
895 if &*field == "..." {
896 resolve_options.into_package.extend(take(&mut old));
897 } else {
898 resolve_options
899 .into_package
900 .push(ResolveIntoPackage::MainField { field });
901 }
902 }
903 }
904 if webpack_resolve_options.no_exports_field {
905 resolve_options
906 .into_package
907 .retain(|field| !matches!(field, ResolveIntoPackage::ExportsField { .. }));
908 }
909 if let Some(main_files) = webpack_resolve_options.main_files {
910 resolve_options.default_files = main_files;
911 }
912 if webpack_resolve_options.no_modules {
913 resolve_options.modules.clear();
914 }
915 if webpack_resolve_options.prefer_relative {
916 resolve_options.prefer_relative = true;
917 }
918 Ok(resolve_options.cell())
919}
920
921#[turbo_tasks::value(shared)]
923pub struct BuildDependencyIssue {
924 pub path: FileSystemPath,
925 pub source: IssueSource,
926}
927
928#[turbo_tasks::value_impl]
929impl Issue for BuildDependencyIssue {
930 fn severity(&self) -> IssueSeverity {
931 IssueSeverity::Warning
932 }
933
934 #[turbo_tasks::function]
935 fn title(&self) -> Vc<StyledString> {
936 StyledString::Text(rcstr!("Build dependencies are not yet supported")).cell()
937 }
938
939 #[turbo_tasks::function]
940 fn stage(&self) -> Vc<IssueStage> {
941 IssueStage::Unsupported.cell()
942 }
943
944 #[turbo_tasks::function]
945 fn file_path(&self) -> Vc<FileSystemPath> {
946 self.source.file_path()
947 }
948
949 #[turbo_tasks::function]
950 async fn description(&self) -> Result<Vc<OptionStyledString>> {
951 Ok(Vc::cell(Some(
952 StyledString::Line(vec![
953 StyledString::Text(rcstr!("The file at ")),
954 StyledString::Code(self.path.to_string().into()),
955 StyledString::Text(
956 " is a build dependency, which is not yet implemented.
957 Changing this file or any dependency will not be recognized and might require restarting the \
958 server"
959 .into(),
960 ),
961 ])
962 .resolved_cell(),
963 )))
964 }
965
966 #[turbo_tasks::function]
967 fn source(&self) -> Vc<OptionIssueSource> {
968 Vc::cell(Some(self.source))
969 }
970}
971
972#[turbo_tasks::value(shared)]
973pub struct EvaluateEmittedErrorIssue {
974 pub source: IssueSource,
975 pub severity: IssueSeverity,
976 pub error: StructuredError,
977 pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
978 pub assets_root: FileSystemPath,
979 pub project_dir: FileSystemPath,
980}
981
982#[turbo_tasks::value_impl]
983impl Issue for EvaluateEmittedErrorIssue {
984 #[turbo_tasks::function]
985 fn file_path(&self) -> Vc<FileSystemPath> {
986 self.source.file_path()
987 }
988
989 #[turbo_tasks::function]
990 fn stage(&self) -> Vc<IssueStage> {
991 IssueStage::Transform.cell()
992 }
993
994 fn severity(&self) -> IssueSeverity {
995 self.severity
996 }
997
998 #[turbo_tasks::function]
999 fn title(&self) -> Vc<StyledString> {
1000 StyledString::Text(rcstr!("Issue while running loader")).cell()
1001 }
1002
1003 #[turbo_tasks::function]
1004 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1005 Ok(Vc::cell(Some(
1006 StyledString::Text(
1007 self.error
1008 .print(
1009 *self.assets_for_source_mapping,
1010 self.assets_root.clone(),
1011 self.project_dir.clone(),
1012 FormattingMode::Plain,
1013 )
1014 .await?
1015 .into(),
1016 )
1017 .resolved_cell(),
1018 )))
1019 }
1020
1021 #[turbo_tasks::function]
1022 fn source(&self) -> Vc<OptionIssueSource> {
1023 Vc::cell(Some(self.source))
1024 }
1025}
1026
1027#[turbo_tasks::value(shared)]
1028pub struct EvaluateErrorLoggingIssue {
1029 pub source: IssueSource,
1030 pub severity: IssueSeverity,
1031 #[turbo_tasks(trace_ignore)]
1032 pub logging: Vec<LogInfo>,
1033 pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
1034 pub assets_root: FileSystemPath,
1035 pub project_dir: FileSystemPath,
1036}
1037
1038#[turbo_tasks::value_impl]
1039impl Issue for EvaluateErrorLoggingIssue {
1040 #[turbo_tasks::function]
1041 fn file_path(&self) -> Vc<FileSystemPath> {
1042 self.source.file_path()
1043 }
1044
1045 #[turbo_tasks::function]
1046 fn stage(&self) -> Vc<IssueStage> {
1047 IssueStage::Transform.cell()
1048 }
1049
1050 fn severity(&self) -> IssueSeverity {
1051 self.severity
1052 }
1053
1054 #[turbo_tasks::function]
1055 fn title(&self) -> Vc<StyledString> {
1056 StyledString::Text(rcstr!("Error logging while running loader")).cell()
1057 }
1058
1059 #[turbo_tasks::function]
1060 fn description(&self) -> Vc<OptionStyledString> {
1061 fn fmt_args(prefix: String, args: &[JsonValue]) -> String {
1062 let mut iter = args.iter();
1063 let Some(first) = iter.next() else {
1064 return "".to_string();
1065 };
1066 let mut result = prefix;
1067 if let JsonValue::String(s) = first {
1068 result.push_str(s);
1069 } else {
1070 result.push_str(&first.to_string());
1071 }
1072 for arg in iter {
1073 result.push(' ');
1074 result.push_str(&arg.to_string());
1075 }
1076 result
1077 }
1078 let lines = self
1079 .logging
1080 .iter()
1081 .map(|log| match log.log_type {
1082 LogType::Error => {
1083 StyledString::Strong(fmt_args("<e> ".to_string(), &log.args).into())
1084 }
1085 LogType::Warn => StyledString::Text(fmt_args("<w> ".to_string(), &log.args).into()),
1086 LogType::Info => StyledString::Text(fmt_args("<i> ".to_string(), &log.args).into()),
1087 LogType::Log => StyledString::Text(fmt_args("<l> ".to_string(), &log.args).into()),
1088 LogType::Clear => StyledString::Strong(rcstr!("---")),
1089 _ => {
1090 unimplemented!("{:?} is not implemented", log.log_type)
1091 }
1092 })
1093 .collect::<Vec<_>>();
1094 Vc::cell(Some(StyledString::Stack(lines).resolved_cell()))
1095 }
1096
1097 #[turbo_tasks::function]
1098 fn source(&self) -> Vc<OptionIssueSource> {
1099 Vc::cell(Some(self.source))
1100 }
1101}