Skip to main content

turbopack/
lib.rs

1#![feature(trivial_bounds)]
2#![feature(min_specialization)]
3#![recursion_limit = "256"]
4#![feature(arbitrary_self_types)]
5#![feature(arbitrary_self_types_pointers)]
6
7pub mod evaluate_context;
8pub mod global_module_ids;
9pub mod module_options;
10pub mod runtime_asset_context;
11pub mod transition;
12
13use anyhow::{Context as _, Result, bail};
14use module_options::{
15    ConfiguredModuleType, ModuleOptions, ModuleOptionsContext, ModuleRuleEffect, ModuleType,
16};
17pub use runtime_asset_context::get_runtime_asset_context;
18use tracing::{Instrument, field::Empty};
19use turbo_rcstr::{RcStr, rcstr};
20use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
21use turbo_tasks_fs::FileSystemPath;
22pub use turbopack_core::condition;
23use turbopack_core::{
24    asset::Asset,
25    chunk::SourceMapsType,
26    compile_time_info::CompileTimeInfo,
27    context::{AssetContext, ProcessResult},
28    ident::{AssetIdent, Layer},
29    issue::{IssueExt, IssueSource, module::ModuleIssue},
30    module::{Module, ModuleSideEffects},
31    node_addon_module::NodeAddonModule,
32    output::{ExpandedOutputAssets, OutputAsset},
33    raw_module::RawModule,
34    reference_type::{
35        CssReferenceSubType, EcmaScriptModulesReferenceSubType, InnerAssets, ReferenceType,
36    },
37    resolve::{
38        ExternalTraced, ExternalType, ModulePart, ModuleResolveResult, ModuleResolveResultItem,
39        ResolveResult, ResolveResultItem,
40        options::{ConditionValue, ResolveOptions},
41        origin::PlainResolveOrigin,
42        parse::Request,
43        resolve,
44    },
45    source::Source,
46    source_transform::SourceTransforms,
47};
48use turbopack_css::{CssModule, EcmascriptCssModule};
49use turbopack_ecmascript::{
50    AnalyzeMode, EcmascriptInputTransforms, EcmascriptModuleAsset, EcmascriptModuleAssetType,
51    EcmascriptOptions,
52    chunk::EcmascriptChunkPlaceable,
53    module_fragments::part::module::EcmascriptModulePartAsset,
54    references::{
55        FollowExportsResult,
56        external_module::{CachedExternalModule, CachedExternalTracingMode, CachedExternalType},
57        follow_reexports,
58    },
59    rename::module::EcmascriptModuleRenameModule,
60    side_effect_optimization::{
61        facade::module::EcmascriptModuleFacadeModule, locals::module::EcmascriptModuleLocalsModule,
62    },
63};
64use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems, WebpackLoaders};
65use turbopack_resolve::{
66    resolve::resolve_options, resolve_options_context::ResolveOptionsContext,
67    typescript::type_resolve,
68};
69use turbopack_static::{css::StaticUrlCssModule, ecma::StaticUrlJsModule};
70use turbopack_wasm::{module_asset::WebAssemblyModuleAsset, source::WebAssemblySource};
71
72use crate::{
73    evaluate_context::node_evaluate_asset_context,
74    module_options::{
75        CssOptionsContext, CustomModuleType, EcmascriptOptionsContext, TypescriptTransformOptions,
76        package_import_map_from_context, package_import_map_from_import_mapping,
77    },
78    transition::{Transition, TransitionOptions},
79};
80
81async fn apply_module_type(
82    source: ResolvedVc<Box<dyn Source>>,
83    module_asset_context: Vc<ModuleAssetContext>,
84    module_type: Vc<ModuleType>,
85    reference_type: ReferenceType,
86    inner_assets: Option<ResolvedVc<InnerAssets>>,
87) -> Result<Vc<ProcessResult>> {
88    let module_options = module_asset_context.module_options_context().await?;
89    let follow_reexports = module_options.follow_reexports;
90    let module_fragments_enabled = module_options.module_fragments_enabled;
91    let part = match &reference_type {
92        ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::ImportPart(part)) => {
93            Some(part)
94        }
95        _ => None,
96    };
97    let css_import_context = match reference_type {
98        ReferenceType::Css(CssReferenceSubType::AtImport(import)) => import,
99        _ => None,
100    };
101    let is_evaluation = matches!(&part, Some(ModulePart::Evaluation));
102
103    let module_type = &*module_type.await?;
104    let module = match module_type {
105        ModuleType::Ecmascript {
106            preprocess,
107            main,
108            postprocess,
109            options,
110        }
111        | ModuleType::EcmascriptExtensionless {
112            preprocess,
113            main,
114            postprocess,
115            options,
116        }
117        | ModuleType::Typescript {
118            preprocess,
119            main,
120            postprocess,
121            tsx: _,
122            analyze_types: _,
123            options,
124        }
125        | ModuleType::TypescriptDeclaration {
126            preprocess,
127            main,
128            postprocess,
129            options,
130        } => {
131            let context_for_module = match module_type {
132                ModuleType::Typescript { analyze_types, .. } if *analyze_types => {
133                    module_asset_context.with_types_resolving_enabled()
134                }
135                ModuleType::TypescriptDeclaration { .. } => {
136                    module_asset_context.with_types_resolving_enabled()
137                }
138                _ => module_asset_context,
139            }
140            .to_resolved()
141            .await?;
142            let side_effect_free_packages = module_asset_context
143                .module_options_context()
144                .await?
145                .side_effect_free_packages;
146            let mut builder = EcmascriptModuleAsset::builder(
147                source,
148                ResolvedVc::upcast(context_for_module),
149                preprocess
150                    .extend(**main)
151                    .extend(**postprocess)
152                    .to_resolved()
153                    .await?,
154                *options,
155                module_asset_context
156                    .compile_time_info()
157                    .to_resolved()
158                    .await?,
159                side_effect_free_packages,
160            );
161            match module_type {
162                ModuleType::Ecmascript { .. } => {
163                    builder = builder.with_type(EcmascriptModuleAssetType::Ecmascript)
164                }
165                ModuleType::EcmascriptExtensionless { .. } => {
166                    builder = builder.with_type(EcmascriptModuleAssetType::EcmascriptExtensionless)
167                }
168                ModuleType::Typescript {
169                    tsx, analyze_types, ..
170                } => {
171                    builder = builder.with_type(EcmascriptModuleAssetType::Typescript {
172                        tsx: *tsx,
173                        analyze_types: *analyze_types,
174                    })
175                }
176                ModuleType::TypescriptDeclaration { .. } => {
177                    builder = builder.with_type(EcmascriptModuleAssetType::TypescriptDeclaration)
178                }
179                _ => unreachable!(),
180            }
181
182            if let Some(inner_assets) = inner_assets {
183                builder = builder.with_inner_assets(inner_assets);
184            }
185
186            let module = builder.build().to_resolved().await?;
187            if matches!(reference_type, ReferenceType::Runtime) {
188                ResolvedVc::upcast(module)
189            } else {
190                // Check side effect free on the intermediate module before following reexports
191                // This can skip the module earlier and could skip more modules than only doing it
192                // at the end. Also we avoid parsing/analyzing the module in this
193                // case, because we would need to parse/analyze it for reexports.
194                if (follow_reexports || module_fragments_enabled) && is_evaluation {
195                    // If we are tree shaking, skip the evaluation part if the module is marked as
196                    // side effect free.
197                    if *module.side_effects().await? == ModuleSideEffects::SideEffectFree {
198                        return Ok(ProcessResult::Ignore.cell());
199                    }
200                }
201
202                if module_fragments_enabled {
203                    Vc::upcast(EcmascriptModulePartAsset::select_part(
204                        *module,
205                        part.cloned().unwrap_or(ModulePart::facade()),
206                    ))
207                } else if follow_reexports {
208                    if *module.get_exports().split_locals_and_reexports().await? {
209                        if let Some(part) = part {
210                            match part {
211                                ModulePart::Evaluation => {
212                                    Vc::upcast(EcmascriptModuleLocalsModule::new(*module))
213                                }
214                                ModulePart::Export(_) => {
215                                    apply_reexport_tree_shaking(
216                                        Vc::upcast(
217                                            *EcmascriptModuleFacadeModule::new(Vc::upcast(*module))
218                                                .to_resolved()
219                                                .await?,
220                                        ),
221                                        part.clone(),
222                                    )
223                                    .await?
224                                }
225                                _ => bail!(
226                                    "Invalid module part \"{}\" for reexports only tree shaking \
227                                     mode",
228                                    part
229                                ),
230                            }
231                        } else {
232                            Vc::upcast(EcmascriptModuleFacadeModule::new(Vc::upcast(*module)))
233                        }
234                    } else {
235                        Vc::upcast(*module)
236                    }
237                } else {
238                    Vc::upcast(*module)
239                }
240                .to_resolved()
241                .await?
242            }
243        }
244        ModuleType::Raw => ResolvedVc::upcast(RawModule::new(*source).to_resolved().await?),
245        ModuleType::NodeAddon => {
246            ResolvedVc::upcast(NodeAddonModule::new(*source).to_resolved().await?)
247        }
248        ModuleType::CssModule => ResolvedVc::upcast(
249            EcmascriptCssModule::new(*source, Vc::upcast(module_asset_context))
250                .to_resolved()
251                .await?,
252        ),
253
254        ModuleType::Css {
255            ty,
256            environment,
257            lightningcss_features,
258        } => ResolvedVc::upcast(
259            CssModule::new(
260                *source,
261                Vc::upcast(module_asset_context),
262                *ty,
263                css_import_context.map(|c| *c),
264                environment.as_deref().copied(),
265                *lightningcss_features,
266            )
267            .to_resolved()
268            .await?,
269        ),
270        ModuleType::StaticUrlJs { tag } => ResolvedVc::upcast(
271            StaticUrlJsModule::new(*source, tag.clone())
272                .to_resolved()
273                .await?,
274        ),
275        ModuleType::StaticUrlCss { tag } => ResolvedVc::upcast(
276            StaticUrlCssModule::new(*source, tag.clone())
277                .to_resolved()
278                .await?,
279        ),
280        ModuleType::WebAssembly { source_ty } => ResolvedVc::upcast(
281            WebAssemblyModuleAsset::new(
282                WebAssemblySource::new(*source, *source_ty),
283                Vc::upcast(module_asset_context),
284            )
285            .to_resolved()
286            .await?,
287        ),
288        ModuleType::Custom(custom) => {
289            custom
290                .create_module(*source, module_asset_context, reference_type)
291                .to_resolved()
292                .await?
293        }
294    };
295
296    if (follow_reexports || module_fragments_enabled) && is_evaluation {
297        // If we are tree shaking, skip the evaluation part if the module is marked as
298        // side effect free.
299        if *module.side_effects().await? == ModuleSideEffects::SideEffectFree {
300            return Ok(ProcessResult::Ignore.cell());
301        }
302    }
303
304    Ok(ProcessResult::Module(module).cell())
305}
306
307async fn apply_reexport_tree_shaking(
308    module: Vc<Box<dyn EcmascriptChunkPlaceable>>,
309    part: ModulePart,
310) -> Result<Vc<Box<dyn Module>>> {
311    if let ModulePart::Export(export) = &part {
312        let FollowExportsResult {
313            module: final_module,
314            export_name: new_export,
315            ..
316        } = &*follow_reexports(module, export.clone(), true).await?;
317        let module = if let Some(new_export) = new_export {
318            if *new_export == *export {
319                Vc::upcast(**final_module)
320            } else {
321                Vc::upcast(EcmascriptModuleRenameModule::new(
322                    **final_module,
323                    ModulePart::renamed_export(new_export.clone(), export.clone()),
324                ))
325            }
326        } else {
327            Vc::upcast(EcmascriptModuleRenameModule::new(
328                **final_module,
329                ModulePart::renamed_namespace(export.clone()),
330            ))
331        };
332        return Ok(module);
333    }
334    Ok(Vc::upcast(module))
335}
336
337#[turbo_tasks::value]
338#[derive(Debug)]
339pub struct ModuleAssetContext {
340    pub transitions: ResolvedVc<TransitionOptions>,
341    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
342    pub module_options_context: ResolvedVc<ModuleOptionsContext>,
343    pub resolve_options_context: ResolvedVc<ResolveOptionsContext>,
344    pub layer: Layer,
345    transition: Option<ResolvedVc<Box<dyn Transition>>>,
346    /// Whether to replace external resolutions with CachedExternalModules. Used with
347    /// ModuleOptionsContext.enable_externals_tracing to handle transitive external dependencies.
348    replace_externals: bool,
349}
350
351#[turbo_tasks::value_impl]
352impl ModuleAssetContext {
353    #[turbo_tasks::function]
354    pub fn new(
355        transitions: ResolvedVc<TransitionOptions>,
356        compile_time_info: ResolvedVc<CompileTimeInfo>,
357        module_options_context: ResolvedVc<ModuleOptionsContext>,
358        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
359        layer: Layer,
360    ) -> Vc<Self> {
361        Self::cell(ModuleAssetContext {
362            transitions,
363            compile_time_info,
364            module_options_context,
365            resolve_options_context,
366            transition: None,
367            layer,
368            replace_externals: true,
369        })
370    }
371
372    #[turbo_tasks::function]
373    pub fn new_transition(
374        transitions: ResolvedVc<TransitionOptions>,
375        compile_time_info: ResolvedVc<CompileTimeInfo>,
376        module_options_context: ResolvedVc<ModuleOptionsContext>,
377        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
378        layer: Layer,
379        transition: ResolvedVc<Box<dyn Transition>>,
380    ) -> Vc<Self> {
381        Self::cell(ModuleAssetContext {
382            transitions,
383            compile_time_info,
384            module_options_context,
385            resolve_options_context,
386            layer,
387            transition: Some(transition),
388            replace_externals: true,
389        })
390    }
391
392    /// Doesn't replace external resolve results with a CachedExternalModule.
393    #[turbo_tasks::function]
394    pub fn new_without_replace_externals(
395        transitions: ResolvedVc<TransitionOptions>,
396        compile_time_info: ResolvedVc<CompileTimeInfo>,
397        module_options_context: ResolvedVc<ModuleOptionsContext>,
398        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
399        layer: Layer,
400    ) -> Vc<Self> {
401        Self::cell(ModuleAssetContext {
402            transitions,
403            compile_time_info,
404            module_options_context,
405            resolve_options_context,
406            transition: None,
407            layer,
408            replace_externals: false,
409        })
410    }
411
412    #[turbo_tasks::function]
413    pub fn module_options_context(&self) -> Vc<ModuleOptionsContext> {
414        *self.module_options_context
415    }
416
417    #[turbo_tasks::function]
418    pub fn resolve_options_context(&self) -> Vc<ResolveOptionsContext> {
419        *self.resolve_options_context
420    }
421
422    #[turbo_tasks::function]
423    pub async fn with_types_resolving_enabled(self: Vc<Self>) -> Result<Vc<ModuleAssetContext>> {
424        let this = self.await?;
425        if this.is_types_resolving_enabled().await? {
426            return Ok(self);
427        }
428        let resolve_options_context = *this
429            .resolve_options_context
430            .with_types_enabled()
431            .to_resolved()
432            .await?;
433
434        Ok(ModuleAssetContext::new(
435            *this.transitions,
436            *this.compile_time_info,
437            *this.module_options_context,
438            resolve_options_context,
439            this.layer.clone(),
440        ))
441    }
442}
443
444impl ModuleAssetContext {
445    async fn is_types_resolving_enabled(&self) -> Result<bool> {
446        let resolve_options_context = self.resolve_options_context.await?;
447        Ok(resolve_options_context.enable_types && resolve_options_context.enable_typescript)
448    }
449    async fn process_with_transition_rules(
450        self: Vc<Self>,
451        source: ResolvedVc<Box<dyn Source>>,
452        reference_type: ReferenceType,
453    ) -> Result<Vc<ProcessResult>> {
454        let this = self.await?;
455        Ok(
456            if let Some(transition) = this
457                .transitions
458                .await?
459                .get_by_rules(source, &reference_type)
460                .await?
461            {
462                transition.process(*source, self, reference_type)
463            } else {
464                self.process_default(source, reference_type).await?
465            },
466        )
467    }
468
469    async fn process_default(
470        self: Vc<Self>,
471        source: ResolvedVc<Box<dyn Source>>,
472        reference_type: ReferenceType,
473    ) -> Result<Vc<ProcessResult>> {
474        process_default(self, source, reference_type, Vec::new()).await
475    }
476}
477
478async fn process_default(
479    module_asset_context: Vc<ModuleAssetContext>,
480    source: ResolvedVc<Box<dyn Source>>,
481    reference_type: ReferenceType,
482    processed_rules: Vec<usize>,
483) -> Result<Vc<ProcessResult>> {
484    let span = tracing::info_span!(
485        "process module",
486        name = %source.ident().to_string().await?,
487        layer = Empty,
488        reference_type = display(&reference_type)
489    );
490    if !span.is_disabled() {
491        // You can't await multiple times in the span macro call parameters.
492        span.record("layer", module_asset_context.await?.layer.name().as_str());
493    }
494
495    process_default_internal(
496        module_asset_context,
497        source,
498        reference_type,
499        processed_rules,
500    )
501    .instrument(span)
502    .await
503}
504
505/// Apply collected transforms to a module type.
506/// For Ecmascript/Typescript variants: merge collected transforms into the module type.
507/// For Custom: call extend_ecmascript_transforms() if any transforms exist.
508/// For non-ecmascript types: warn if transforms exist, return unchanged.
509async fn apply_module_rule_transforms(
510    module_type: &mut ModuleType,
511    collected_preprocess: &mut Vec<ResolvedVc<EcmascriptInputTransforms>>,
512    collected_main: &mut Vec<ResolvedVc<EcmascriptInputTransforms>>,
513    collected_postprocess: &mut Vec<ResolvedVc<EcmascriptInputTransforms>>,
514    ident: ResolvedVc<AssetIdent>,
515    current_source: ResolvedVc<Box<dyn Source>>,
516) -> Result<()> {
517    let has_transforms = !collected_preprocess.is_empty()
518        || !collected_main.is_empty()
519        || !collected_postprocess.is_empty();
520
521    // If no transforms were collected, return early
522    if !has_transforms {
523        return Ok(());
524    }
525
526    match module_type {
527        ModuleType::Ecmascript {
528            preprocess,
529            main,
530            postprocess,
531            ..
532        }
533        | ModuleType::Typescript {
534            preprocess,
535            main,
536            postprocess,
537            ..
538        }
539        | ModuleType::TypescriptDeclaration {
540            preprocess,
541            main,
542            postprocess,
543            ..
544        }
545        | ModuleType::EcmascriptExtensionless {
546            preprocess,
547            main,
548            postprocess,
549            ..
550        } => {
551            // Apply collected preprocess/main in order, then module type's transforms
552            let mut final_preprocess = EcmascriptInputTransforms::empty();
553            for vc in collected_preprocess.drain(..) {
554                final_preprocess = final_preprocess.extend(*vc);
555            }
556            final_preprocess = final_preprocess.extend(**preprocess);
557            *preprocess = final_preprocess.to_resolved().await?;
558
559            let mut final_main = EcmascriptInputTransforms::empty();
560            for vc in collected_main.drain(..) {
561                final_main = final_main.extend(*vc);
562            }
563            final_main = final_main.extend(**main);
564            *main = final_main.to_resolved().await?;
565
566            // Apply module type's postprocess first, then collected postprocess
567            let mut final_postprocess = **postprocess;
568            for vc in collected_postprocess.drain(..) {
569                final_postprocess = final_postprocess.extend(*vc);
570            }
571            *postprocess = final_postprocess.to_resolved().await?;
572        }
573        ModuleType::Custom(custom_module_type) => {
574            if has_transforms {
575                // Combine collected transforms into single Vcs
576                let mut combined_preprocess = EcmascriptInputTransforms::empty();
577                for vc in collected_preprocess.drain(..) {
578                    combined_preprocess = combined_preprocess.extend(*vc);
579                }
580                let mut combined_main = EcmascriptInputTransforms::empty();
581                for vc in collected_main.drain(..) {
582                    combined_main = combined_main.extend(*vc);
583                }
584                let mut combined_postprocess = EcmascriptInputTransforms::empty();
585                for vc in collected_postprocess.drain(..) {
586                    combined_postprocess = combined_postprocess.extend(*vc);
587                }
588
589                match custom_module_type
590                    .extend_ecmascript_transforms(
591                        combined_preprocess,
592                        combined_main,
593                        combined_postprocess,
594                    )
595                    .to_resolved()
596                    .await
597                {
598                    Ok(new_custom_module_type) => {
599                        *custom_module_type = new_custom_module_type;
600                    }
601                    Err(_) => {
602                        ModuleIssue::new(
603                            *ident,
604                            rcstr!("Invalid module type"),
605                            rcstr!(
606                                "The custom module type didn't accept the additional Ecmascript \
607                                 transforms"
608                            ),
609                            Some(IssueSource::from_source_only(current_source)),
610                        )
611                        .to_resolved()
612                        .await?
613                        .emit();
614                    }
615                }
616            }
617        }
618        other => {
619            if has_transforms {
620                ModuleIssue::new(
621                    *ident,
622                    rcstr!("Invalid module type"),
623                    format!(
624                        "The module type must be Ecmascript or Typescript to add Ecmascript \
625                         transforms (got {})",
626                        other
627                    )
628                    .into(),
629                    Some(IssueSource::from_source_only(current_source)),
630                )
631                .to_resolved()
632                .await?
633                .emit();
634                collected_preprocess.clear();
635                collected_main.clear();
636                collected_postprocess.clear();
637            }
638        }
639    }
640    Ok(())
641}
642
643async fn process_default_internal(
644    module_asset_context: Vc<ModuleAssetContext>,
645    source: ResolvedVc<Box<dyn Source>>,
646    reference_type: ReferenceType,
647    processed_rules: Vec<usize>,
648) -> Result<Vc<ProcessResult>> {
649    let ident = source.ident().to_resolved().await?;
650    let ident_ref = ident.await?;
651    let path_ref = &ident_ref.path;
652    let options = ModuleOptions::new(
653        path_ref.parent(),
654        module_asset_context.module_options_context(),
655        module_asset_context.resolve_options_context(),
656    );
657
658    let inner_assets = match &reference_type {
659        ReferenceType::Internal(inner_assets) => Some(*inner_assets),
660        _ => None,
661    };
662    let mut current_source = source;
663    let mut current_module_type = None;
664
665    // Handle turbopackLoader import attributes: apply inline loader as source transform
666    if let ReferenceType::EcmaScriptModules(
667        EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
668            ref loader,
669            ref rename_as,
670            ref module_type,
671        },
672    ) = reference_type
673    {
674        let module_options_context = module_asset_context.module_options_context().await?;
675        let webpack_loaders_options = module_options_context
676            .enable_webpack_loaders
677            .as_ref()
678            .context(
679                "turbopackUse import assertions require webpack loaders to be enabled \
680                 (enable_webpack_loaders)",
681            )?
682            .await?;
683        let execution_context = module_options_context
684            .execution_context
685            .context("execution_context is required for turbopackUse import assertions")?;
686        let execution_context_value = execution_context.await?;
687
688        let resolve_options_context = module_asset_context
689            .resolve_options_context()
690            .to_resolved()
691            .await?;
692        let source_maps = matches!(
693            module_options_context.ecmascript.source_maps,
694            SourceMapsType::Full
695        );
696
697        // Determine the import map for loader-runner
698        let loader_runner_package = webpack_loaders_options.loader_runner_package;
699
700        let import_map = if let Some(loader_runner_package) = loader_runner_package {
701            package_import_map_from_import_mapping(rcstr!("loader-runner"), *loader_runner_package)
702        } else {
703            package_import_map_from_context(
704                rcstr!("loader-runner"),
705                execution_context_value.project_path.clone(),
706            )
707        };
708
709        let evaluate_context = node_evaluate_asset_context(
710            *execution_context,
711            Some(import_map),
712            None,
713            Layer::new(rcstr!("webpack_loaders")),
714            false,
715        )
716        .to_resolved()
717        .await?;
718
719        let loader_relative_path = execution_context_value
720            .project_path
721            .get_relative_path_to(&loader.loader)
722            .context("Loader path must be on project filesystem")?;
723        let webpack_loader_item = WebpackLoaderItem {
724            loader: loader_relative_path,
725            options: loader.options.clone(),
726        };
727        let loaders_vc = WebpackLoaderItems(vec![webpack_loader_item]).cell();
728        let webpack_loaders = WebpackLoaders::new(
729            *evaluate_context,
730            *execution_context,
731            loaders_vc,
732            rename_as.clone(),
733            *resolve_options_context,
734            source_maps,
735        )
736        .to_resolved()
737        .await?;
738
739        let transforms = Vc::<SourceTransforms>::cell(vec![ResolvedVc::upcast(webpack_loaders)]);
740        current_source = transforms
741            .transform(*current_source, Vc::upcast(module_asset_context))
742            .to_resolved()
743            .await?;
744
745        // If turbopackModuleType is specified, skip rule matching and directly
746        // apply the requested module type with empty transforms (loader output
747        // is already processed).
748        if let Some(type_str) = module_type {
749            let empty_transforms = EcmascriptInputTransforms::empty().to_resolved().await?;
750            let default_options = EcmascriptOptions::default().resolved_cell();
751            let effect = ConfiguredModuleType::parse(type_str)?
752                .into_effect(
753                    empty_transforms,
754                    empty_transforms,
755                    empty_transforms,
756                    default_options,
757                    None,
758                    Default::default(),
759                )
760                .await?;
761            match effect {
762                ModuleRuleEffect::ModuleType(module_type) => {
763                    return apply_module_type(
764                        current_source,
765                        module_asset_context,
766                        module_type.cell(),
767                        reference_type,
768                        inner_assets,
769                    )
770                    .await;
771                }
772                ModuleRuleEffect::SourceTransforms(transforms) => {
773                    current_source = transforms
774                        .transform(*current_source, Vc::upcast(module_asset_context))
775                        .to_resolved()
776                        .await?;
777                    // Fall through to re-process with new ident
778                }
779                _ => bail!("Unexpected module rule effect for turbopackModuleType"),
780            }
781        }
782
783        // If the ident changed (e.g., due to rename_as), re-process from the
784        // beginning so the new extension is matched by the correct rules.
785        // Use a plain Import reference type to avoid re-applying turbopackUse
786        // loaders in the recursive call (which would cause an infinite loop).
787        if current_source.ident().to_resolved().await? != ident {
788            let plain_reference_type =
789                ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::Import);
790            if let Some(transition) = module_asset_context
791                .await?
792                .transitions
793                .await?
794                .get_by_rules(current_source, &plain_reference_type)
795                .await?
796            {
797                return Ok(transition.process(
798                    *current_source,
799                    module_asset_context,
800                    plain_reference_type,
801                ));
802            } else {
803                return Box::pin(process_default(
804                    module_asset_context,
805                    current_source,
806                    plain_reference_type,
807                    processed_rules,
808                ))
809                .await;
810            }
811        }
812    }
813
814    // Collect transforms from ExtendEcmascriptTransforms effects.
815    // They will be applied when ModuleType is set.
816    let mut collected_preprocess: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
817    let mut collected_main: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
818    let mut collected_postprocess: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
819
820    let options_value = options.await?;
821    'outer: for (i, rule) in options_value.rules.iter().enumerate() {
822        if processed_rules.contains(&i) {
823            continue;
824        }
825        if rule.matches(source, path_ref, &reference_type).await? {
826            for effect in rule.effects() {
827                match effect {
828                    ModuleRuleEffect::Ignore => {
829                        return Ok(ProcessResult::Ignore.cell());
830                    }
831                    ModuleRuleEffect::SourceTransforms(transforms) => {
832                        current_source = transforms
833                            .transform(*current_source, Vc::upcast(module_asset_context))
834                            .to_resolved()
835                            .await?;
836                        if current_source.ident().to_resolved().await? != ident {
837                            // The ident has been changed, so we need to apply new rules.
838                            if let Some(transition) = module_asset_context
839                                .await?
840                                .transitions
841                                .await?
842                                .get_by_rules(current_source, &reference_type)
843                                .await?
844                            {
845                                return Ok(transition.process(
846                                    *current_source,
847                                    module_asset_context,
848                                    reference_type,
849                                ));
850                            } else {
851                                let mut processed_rules = processed_rules.clone();
852                                processed_rules.push(i);
853                                return Box::pin(process_default(
854                                    module_asset_context,
855                                    current_source,
856                                    reference_type,
857                                    processed_rules,
858                                ))
859                                .await;
860                            }
861                        }
862                    }
863                    ModuleRuleEffect::ModuleType(module) => {
864                        // Apply any collected transforms to this module type and exit rule
865                        // processing. Once a ModuleType is determined, we
866                        // stop processing further rules.
867                        let mut module = module.clone();
868                        apply_module_rule_transforms(
869                            &mut module,
870                            &mut collected_preprocess,
871                            &mut collected_main,
872                            &mut collected_postprocess,
873                            ident,
874                            current_source,
875                        )
876                        .await?;
877                        current_module_type = Some(module);
878                        break 'outer;
879                    }
880                    ModuleRuleEffect::ExtendEcmascriptTransforms {
881                        preprocess: extend_preprocess,
882                        main: extend_main,
883                        postprocess: extend_postprocess,
884                    } => {
885                        // Collect transforms. They will be applied when ModuleType is set.
886                        collected_preprocess.push(*extend_preprocess);
887                        collected_main.push(*extend_main);
888                        collected_postprocess.push(*extend_postprocess);
889                    }
890                }
891            }
892        }
893    }
894
895    let Some(module_type) = current_module_type else {
896        return Ok(ProcessResult::Unknown(current_source).cell());
897    };
898
899    let module = apply_module_type(
900        current_source,
901        module_asset_context,
902        module_type.cell(),
903        reference_type,
904        inner_assets,
905    )
906    .await?;
907
908    Ok(module)
909}
910
911#[turbo_tasks::function]
912pub async fn externals_tracing_module_context(
913    compile_time_info: Vc<CompileTimeInfo>,
914    resolve_typescript: bool,
915) -> Result<Vc<ModuleAssetContext>> {
916    let mut extensions = vec![rcstr!(".js"), rcstr!(".node"), rcstr!(".json")];
917    if resolve_typescript {
918        extensions.insert(0, rcstr!(".ts"));
919    }
920
921    let resolve_options = ResolveOptionsContext {
922        custom_extensions: Some(extensions),
923        emulate_environment: Some(compile_time_info.await?.environment),
924        loose_errors: true,
925        collect_affecting_sources: true,
926        custom_conditions: vec![rcstr!("node")],
927        module_sync: ConditionValue::Unknown,
928        ..Default::default()
929    };
930
931    Ok(ModuleAssetContext::new_without_replace_externals(
932        Default::default(),
933        compile_time_info,
934        // This config should be kept in sync with
935        // turbopack/crates/turbopack-tracing/tests/node-file-trace.rs and
936        // turbopack/crates/turbopack-tracing/tests/unit.rs and
937        // turbopack/crates/turbopack/src/lib.rs and
938        // turbopack/crates/turbopack-nft/src/nft.rs
939        ModuleOptionsContext {
940            ecmascript: EcmascriptOptionsContext {
941                enable_typescript_transform: Some(
942                    TypescriptTransformOptions::default().resolved_cell(),
943                ),
944                // enable_types should not be enabled here. It gets set automatically when a TS file
945                // is encountered.
946                source_maps: SourceMapsType::None,
947                ..Default::default()
948            },
949            css: CssOptionsContext {
950                source_maps: SourceMapsType::None,
951                enable_raw_css: true,
952                ..Default::default()
953            },
954            // Environment is not passed in order to avoid downleveling JS / CSS for
955            // node-file-trace.
956            environment: None,
957            analyze_mode: AnalyzeMode::Tracing,
958            // Disable tree shaking. Even side-effect-free imports need to be traced, as they will
959            // execute at runtime.
960            ..Default::default()
961        }
962        .cell(),
963        resolve_options.cell(),
964        Layer::new(rcstr!("externals-tracing")),
965    ))
966}
967
968#[turbo_tasks::value_impl]
969impl AssetContext for ModuleAssetContext {
970    #[turbo_tasks::function]
971    fn compile_time_info(&self) -> Vc<CompileTimeInfo> {
972        *self.compile_time_info
973    }
974
975    fn layer(&self) -> Layer {
976        self.layer.clone()
977    }
978
979    #[turbo_tasks::function]
980    async fn resolve_options(
981        self: Vc<Self>,
982        origin_path: FileSystemPath,
983    ) -> Result<Vc<ResolveOptions>> {
984        let this = self.await?;
985        let module_asset_context = if let Some(transition) = this.transition {
986            transition.process_context(self)
987        } else {
988            self
989        };
990        // TODO move `apply_commonjs/esm_resolve_options` etc. to here
991        let options = resolve_options(
992            origin_path.parent(),
993            *module_asset_context.await?.resolve_options_context,
994        );
995        // Inject the turbopack-ecmascript-runtime import map so that
996        // @turbopack/* built-in modules and @vercel/turbopack-ecmascript-runtime/*
997        // paths are always resolvable.
998        let runtime_import_map = turbopack_ecmascript_runtime::turbopack_runtime_import_map()
999            .to_resolved()
1000            .await?;
1001        Ok(options.with_extended_import_map(*runtime_import_map))
1002    }
1003
1004    #[turbo_tasks::function]
1005    async fn resolve_asset(
1006        self: Vc<Self>,
1007        origin_path: FileSystemPath,
1008        request: Vc<Request>,
1009        resolve_options: Vc<ResolveOptions>,
1010        reference_type: ReferenceType,
1011    ) -> Result<Vc<ModuleResolveResult>> {
1012        let context_path = origin_path.parent();
1013
1014        let result = resolve(
1015            context_path,
1016            reference_type.clone(),
1017            request,
1018            resolve_options,
1019        );
1020
1021        let mut result = self.process_resolve_result(*result.to_resolved().await?, reference_type);
1022        let this = self.await?;
1023        if this.is_types_resolving_enabled().await? {
1024            let types_result = type_resolve(
1025                Vc::upcast(PlainResolveOrigin::new(Vc::upcast(self), origin_path)),
1026                request,
1027            );
1028
1029            result = ModuleResolveResult::alternatives(vec![result, types_result]);
1030        }
1031
1032        Ok(result)
1033    }
1034
1035    #[turbo_tasks::function]
1036    async fn process_resolve_result(
1037        self: Vc<Self>,
1038        result: Vc<ResolveResult>,
1039        reference_type: ReferenceType,
1040    ) -> Result<Vc<ModuleResolveResult>> {
1041        let this = self.await?;
1042
1043        let replace_externals = this.replace_externals;
1044        let import_externals = this
1045            .module_options_context
1046            .await?
1047            .ecmascript
1048            .import_externals;
1049
1050        let result = result.await?;
1051
1052        let result = result
1053            .map_primary_items(|item| {
1054                let reference_type = reference_type.clone();
1055                async move {
1056                    Ok(match item {
1057                        ResolveResultItem::Source(source) => {
1058                            match &*self.process(*source, reference_type).await? {
1059                                ProcessResult::Module(module) => {
1060                                    ModuleResolveResultItem::Module(*module)
1061                                }
1062                                ProcessResult::Unknown(source) => {
1063                                    ModuleResolveResultItem::Unknown(*source)
1064                                }
1065                                ProcessResult::Ignore => ModuleResolveResultItem::Ignore,
1066                            }
1067                        }
1068                        ResolveResultItem::External {
1069                            name,
1070                            ty,
1071                            traced,
1072                            target,
1073                        } => {
1074                            let replacement = if replace_externals {
1075                                // Determine the package folder, `target` is the full path to the
1076                                // resolved file.
1077                                let target = if let Some(mut target) = target {
1078                                    loop {
1079                                        let parent = target.parent();
1080                                        if parent.is_root() {
1081                                            break;
1082                                        }
1083                                        if parent.file_name() == "node_modules" {
1084                                            break;
1085                                        }
1086                                        if parent.file_name().starts_with("@")
1087                                            && parent.parent().file_name() == "node_modules"
1088                                        {
1089                                            break;
1090                                        }
1091                                        target = parent;
1092                                    }
1093                                    Some(target)
1094                                } else {
1095                                    None
1096                                };
1097
1098                                let analyze_mode = if traced == ExternalTraced::Traced
1099                                    && let Some(options) = &self
1100                                        .module_options_context()
1101                                        .await?
1102                                        .enable_externals_tracing
1103                                {
1104                                    // result.affecting_sources can be ignored for tracing, as this
1105                                    // request will later be resolved relative to tracing_root (or
1106                                    // the .next/node_modules/lodash-1238123 symlink) anyway.
1107
1108                                    let options = options.await?;
1109                                    let origin = PlainResolveOrigin::new(
1110                                        Vc::upcast(externals_tracing_module_context(
1111                                            *options.compile_time_info,
1112                                            false,
1113                                        )),
1114                                        // If target is specified, a symlink will be created to
1115                                        // make the folder
1116                                        // itself available, but we still need to trace
1117                                        // resolving the individual file(s) inside the package.
1118                                        target
1119                                            .as_ref()
1120                                            .unwrap_or(&options.tracing_root)
1121                                            .join("_")?,
1122                                    );
1123                                    CachedExternalTracingMode::Traced {
1124                                        origin: ResolvedVc::upcast(origin.to_resolved().await?),
1125                                    }
1126                                } else {
1127                                    CachedExternalTracingMode::Untraced
1128                                };
1129
1130                                replace_external(&name, ty, target, import_externals, analyze_mode)
1131                                    .await?
1132                            } else {
1133                                None
1134                            };
1135
1136                            replacement
1137                                .unwrap_or_else(|| ModuleResolveResultItem::External { name, ty })
1138                        }
1139                        ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
1140                        ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
1141                        ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
1142                        ResolveResultItem::Custom(u8) => ModuleResolveResultItem::Custom(u8),
1143                    })
1144                }
1145            })
1146            .await?;
1147
1148        Ok(result.cell())
1149    }
1150
1151    #[turbo_tasks::function]
1152    async fn process(
1153        self: Vc<Self>,
1154        asset: ResolvedVc<Box<dyn Source>>,
1155        reference_type: ReferenceType,
1156    ) -> Result<Vc<ProcessResult>> {
1157        let this = self.await?;
1158        if let Some(transition) = this.transition {
1159            Ok(transition.process(*asset, self, reference_type))
1160        } else {
1161            Ok(self
1162                .process_with_transition_rules(asset, reference_type)
1163                .await?)
1164        }
1165    }
1166
1167    #[turbo_tasks::function]
1168    async fn with_transition(&self, transition: RcStr) -> Result<Vc<Box<dyn AssetContext>>> {
1169        Ok(
1170            if let Some(transition) = self.transitions.await?.get_named(transition) {
1171                Vc::upcast(ModuleAssetContext::new_transition(
1172                    *self.transitions,
1173                    *self.compile_time_info,
1174                    *self.module_options_context,
1175                    *self.resolve_options_context,
1176                    self.layer.clone(),
1177                    *transition,
1178                ))
1179            } else {
1180                // TODO report issue
1181                Vc::upcast(ModuleAssetContext::new(
1182                    *self.transitions,
1183                    *self.compile_time_info,
1184                    *self.module_options_context,
1185                    *self.resolve_options_context,
1186                    self.layer.clone(),
1187                ))
1188            },
1189        )
1190    }
1191}
1192
1193#[turbo_tasks::function]
1194pub async fn emit_asset(asset: Vc<Box<dyn OutputAsset>>) -> Result<()> {
1195    asset
1196        .content()
1197        .write(asset.path().owned().await?)
1198        .as_side_effect()
1199        .await?;
1200
1201    Ok(())
1202}
1203
1204#[turbo_tasks::function]
1205pub async fn emit_assets_into_dir(
1206    assets: Vc<ExpandedOutputAssets>,
1207    output_dir: FileSystemPath,
1208) -> Result<()> {
1209    let assets = assets.await?;
1210    let paths = assets.iter().map(|&asset| asset.path()).try_join().await?;
1211    for (&asset, path) in assets.iter().zip(paths.iter()) {
1212        if path.is_inside_ref(&output_dir) {
1213            emit_asset(*asset).as_side_effect().await?;
1214        }
1215    }
1216    Ok(())
1217}
1218
1219#[turbo_tasks::function(operation, root)]
1220pub async fn emit_assets_into_dir_operation(
1221    assets: ResolvedVc<ExpandedOutputAssets>,
1222    output_dir: FileSystemPath,
1223) -> Result<()> {
1224    emit_assets_into_dir(*assets, output_dir)
1225        .as_side_effect()
1226        .await?;
1227    Ok(())
1228}
1229
1230/// Replaces the externals in the result with `ExternalModuleAsset` instances.
1231pub async fn replace_external(
1232    name: &RcStr,
1233    ty: ExternalType,
1234    target: Option<FileSystemPath>,
1235    import_externals: bool,
1236    analyze_mode: CachedExternalTracingMode,
1237) -> Result<Option<ModuleResolveResultItem>> {
1238    let external_type = match ty {
1239        ExternalType::CommonJs => CachedExternalType::CommonJs,
1240        ExternalType::EcmaScriptModule => {
1241            if import_externals {
1242                CachedExternalType::EcmaScriptViaImport
1243            } else {
1244                CachedExternalType::EcmaScriptViaRequire
1245            }
1246        }
1247        ExternalType::Global => CachedExternalType::Global,
1248        ExternalType::Script => CachedExternalType::Script,
1249        ExternalType::Url => {
1250            // we don't want to wrap url externals.
1251            return Ok(None);
1252        }
1253    };
1254
1255    let module = CachedExternalModule::new(name.clone(), target, external_type, analyze_mode)
1256        .to_resolved()
1257        .await?;
1258
1259    Ok(Some(ModuleResolveResultItem::Module(ResolvedVc::upcast(
1260        module,
1261    ))))
1262}