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