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