Skip to main content

turbopack/
lib.rs

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