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