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::{Result, bail};
16use module_options::{ModuleOptions, ModuleOptionsContext, ModuleRuleEffect, ModuleType};
17use tracing::{Instrument, field::Empty};
18use turbo_rcstr::{RcStr, rcstr};
19use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
20use turbo_tasks_fs::FileSystemPath;
21pub use turbopack_core::condition;
22use turbopack_core::{
23    asset::Asset,
24    chunk::SourceMapsType,
25    compile_time_info::CompileTimeInfo,
26    context::{AssetContext, ProcessResult},
27    ident::{AssetIdent, Layer},
28    issue::{IssueExt, IssueSource, module::ModuleIssue},
29    module::{Module, ModuleSideEffects},
30    node_addon_module::NodeAddonModule,
31    output::{ExpandedOutputAssets, OutputAsset},
32    raw_module::RawModule,
33    reference_type::{
34        CssReferenceSubType, EcmaScriptModulesReferenceSubType, ImportContext, InnerAssets,
35        ReferenceType,
36    },
37    resolve::{
38        ExternalTraced, ExternalType, ModulePart, ModuleResolveResult, ModuleResolveResultItem,
39        ResolveResult, ResolveResultItem, options::ResolveOptions, origin::PlainResolveOrigin,
40        parse::Request, resolve,
41    },
42    source::Source,
43};
44use turbopack_css::{CssModuleAsset, ModuleCssAsset};
45use turbopack_ecmascript::{
46    AnalyzeMode, EcmascriptInputTransforms, EcmascriptModuleAsset, EcmascriptModuleAssetType,
47    TreeShakingMode,
48    chunk::EcmascriptChunkPlaceable,
49    inlined_bytes_module::InlinedBytesJsModule,
50    references::{
51        FollowExportsResult,
52        external_module::{CachedExternalModule, CachedExternalTracingMode, CachedExternalType},
53        follow_reexports,
54    },
55    side_effect_optimization::{
56        facade::module::EcmascriptModuleFacadeModule, locals::module::EcmascriptModuleLocalsModule,
57    },
58    tree_shake::asset::EcmascriptModulePartAsset,
59};
60use turbopack_json::JsonModuleAsset;
61use turbopack_resolve::{
62    resolve::resolve_options, resolve_options_context::ResolveOptionsContext,
63    typescript::type_resolve,
64};
65use turbopack_static::{css::StaticUrlCssModule, ecma::StaticUrlJsModule};
66use turbopack_wasm::{module_asset::WebAssemblyModuleAsset, source::WebAssemblySource};
67
68use crate::{
69    module_options::{
70        CssOptionsContext, CustomModuleType, EcmascriptOptionsContext, TypescriptTransformOptions,
71    },
72    transition::{Transition, TransitionOptions},
73};
74
75async fn apply_module_type(
76    source: ResolvedVc<Box<dyn Source>>,
77    module_asset_context: Vc<ModuleAssetContext>,
78    module_type: Vc<ModuleType>,
79    part: Option<ModulePart>,
80    inner_assets: Option<ResolvedVc<InnerAssets>>,
81    css_import_context: Option<ResolvedVc<ImportContext>>,
82    runtime_code: bool,
83) -> Result<Vc<ProcessResult>> {
84    let tree_shaking_mode = module_asset_context
85        .module_options_context()
86        .await?
87        .tree_shaking_mode;
88    let is_evaluation = matches!(&part, Some(ModulePart::Evaluation));
89
90    let module_type = &*module_type.await?;
91    let module = match module_type {
92        ModuleType::Ecmascript {
93            preprocess,
94            main,
95            postprocess,
96            options,
97        }
98        | ModuleType::EcmascriptExtensionless {
99            preprocess,
100            main,
101            postprocess,
102            options,
103        }
104        | ModuleType::Typescript {
105            preprocess,
106            main,
107            postprocess,
108            tsx: _,
109            analyze_types: _,
110            options,
111        }
112        | ModuleType::TypescriptDeclaration {
113            preprocess,
114            main,
115            postprocess,
116            options,
117        } => {
118            let context_for_module = match module_type {
119                ModuleType::Typescript { analyze_types, .. } if *analyze_types => {
120                    module_asset_context.with_types_resolving_enabled()
121                }
122                ModuleType::TypescriptDeclaration { .. } => {
123                    module_asset_context.with_types_resolving_enabled()
124                }
125                _ => module_asset_context,
126            }
127            .to_resolved()
128            .await?;
129            let side_effect_free_packages = module_asset_context
130                .module_options_context()
131                .await?
132                .side_effect_free_packages;
133            let mut builder = EcmascriptModuleAsset::builder(
134                source,
135                ResolvedVc::upcast(context_for_module),
136                preprocess
137                    .extend(**main)
138                    .extend(**postprocess)
139                    .to_resolved()
140                    .await?,
141                *options,
142                module_asset_context
143                    .compile_time_info()
144                    .to_resolved()
145                    .await?,
146                side_effect_free_packages,
147            );
148            match module_type {
149                ModuleType::Ecmascript { .. } => {
150                    builder = builder.with_type(EcmascriptModuleAssetType::Ecmascript)
151                }
152                ModuleType::EcmascriptExtensionless { .. } => {
153                    builder = builder.with_type(EcmascriptModuleAssetType::EcmascriptExtensionless)
154                }
155                ModuleType::Typescript {
156                    tsx, analyze_types, ..
157                } => {
158                    builder = builder.with_type(EcmascriptModuleAssetType::Typescript {
159                        tsx: *tsx,
160                        analyze_types: *analyze_types,
161                    })
162                }
163                ModuleType::TypescriptDeclaration { .. } => {
164                    builder = builder.with_type(EcmascriptModuleAssetType::TypescriptDeclaration)
165                }
166                _ => unreachable!(),
167            }
168
169            if let Some(inner_assets) = inner_assets {
170                builder = builder.with_inner_assets(inner_assets);
171            }
172
173            let module = builder.build().to_resolved().await?;
174            if runtime_code {
175                ResolvedVc::upcast(module)
176            } else {
177                // Check side effect free on the intermediate module before following reexports
178                // This can skip the module earlier and could skip more modules than only doing it
179                // at the end. Also we avoid parsing/analyzing the module in this
180                // case, because we would need to parse/analyze it for reexports.
181                if tree_shaking_mode.is_some() && is_evaluation {
182                    // If we are tree shaking, skip the evaluation part if the module is marked as
183                    // side effect free.
184                    if *module.side_effects().await? == ModuleSideEffects::SideEffectFree {
185                        return Ok(ProcessResult::Ignore.cell());
186                    }
187                }
188
189                match tree_shaking_mode {
190                    Some(TreeShakingMode::ModuleFragments) => {
191                        Vc::upcast(EcmascriptModulePartAsset::select_part(
192                            *module,
193                            part.unwrap_or(ModulePart::facade()),
194                        ))
195                    }
196                    Some(TreeShakingMode::ReexportsOnly) => {
197                        if let Some(part) = part {
198                            match part {
199                                ModulePart::Evaluation => {
200                                    if *module.get_exports().split_locals_and_reexports().await? {
201                                        Vc::upcast(EcmascriptModuleLocalsModule::new(*module))
202                                    } else {
203                                        Vc::upcast(*module)
204                                    }
205                                }
206                                ModulePart::Export(_) => {
207                                    if *module.get_exports().split_locals_and_reexports().await? {
208                                        apply_reexport_tree_shaking(
209                                            Vc::upcast(
210                                                EcmascriptModuleFacadeModule::new(
211                                                    Vc::upcast(*module),
212                                                    ModulePart::facade(),
213                                                )
214                                                .resolve()
215                                                .await?,
216                                            ),
217                                            part,
218                                        )
219                                        .await?
220                                    } else {
221                                        apply_reexport_tree_shaking(Vc::upcast(*module), part)
222                                            .await?
223                                    }
224                                }
225                                _ => bail!(
226                                    "Invalid module part \"{}\" for reexports only tree shaking \
227                                     mode",
228                                    part
229                                ),
230                            }
231                        } else if *module.get_exports().split_locals_and_reexports().await? {
232                            Vc::upcast(EcmascriptModuleFacadeModule::new(
233                                Vc::upcast(*module),
234                                ModulePart::facade(),
235                            ))
236                        } else {
237                            Vc::upcast(*module)
238                        }
239                    }
240                    None => Vc::upcast(*module),
241                }
242                .to_resolved()
243                .await?
244            }
245        }
246        ModuleType::Json => ResolvedVc::upcast(JsonModuleAsset::new(*source).to_resolved().await?),
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            ModuleCssAsset::new(*source, Vc::upcast(module_asset_context))
253                .to_resolved()
254                .await?,
255        ),
256
257        ModuleType::Css { ty, environment } => ResolvedVc::upcast(
258            CssModuleAsset::new(
259                *source,
260                Vc::upcast(module_asset_context),
261                *ty,
262                css_import_context.map(|c| *c),
263                environment.as_deref().copied(),
264            )
265            .to_resolved()
266            .await?,
267        ),
268        ModuleType::StaticUrlJs { tag } => ResolvedVc::upcast(
269            StaticUrlJsModule::new(*source, tag.clone())
270                .to_resolved()
271                .await?,
272        ),
273        ModuleType::StaticUrlCss { tag } => ResolvedVc::upcast(
274            StaticUrlCssModule::new(*source, tag.clone())
275                .to_resolved()
276                .await?,
277        ),
278        ModuleType::InlinedBytesJs => {
279            ResolvedVc::upcast(InlinedBytesJsModule::new(*source).to_resolved().await?)
280        }
281        ModuleType::WebAssembly { source_ty } => ResolvedVc::upcast(
282            WebAssemblyModuleAsset::new(
283                WebAssemblySource::new(*source, *source_ty),
284                Vc::upcast(module_asset_context),
285            )
286            .to_resolved()
287            .await?,
288        ),
289        ModuleType::Custom(custom) => {
290            custom
291                .create_module(*source, module_asset_context, part)
292                .to_resolved()
293                .await?
294        }
295    };
296
297    if tree_shaking_mode.is_some() && is_evaluation {
298        // If we are tree shaking, skip the evaluation part if the module is marked as
299        // side effect free.
300        if *module.side_effects().await? == ModuleSideEffects::SideEffectFree {
301            return Ok(ProcessResult::Ignore.cell());
302        }
303    }
304
305    Ok(ProcessResult::Module(module).cell())
306}
307
308async fn apply_reexport_tree_shaking(
309    module: Vc<Box<dyn EcmascriptChunkPlaceable>>,
310    part: ModulePart,
311) -> Result<Vc<Box<dyn Module>>> {
312    if let ModulePart::Export(export) = &part {
313        let FollowExportsResult {
314            module: final_module,
315            export_name: new_export,
316            ..
317        } = &*follow_reexports(module, export.clone(), true).await?;
318        let module = if let Some(new_export) = new_export {
319            if *new_export == *export {
320                Vc::upcast(**final_module)
321            } else {
322                Vc::upcast(EcmascriptModuleFacadeModule::new(
323                    **final_module,
324                    ModulePart::renamed_export(new_export.clone(), export.clone()),
325                ))
326            }
327        } else {
328            Vc::upcast(EcmascriptModuleFacadeModule::new(
329                **final_module,
330                ModulePart::renamed_namespace(export.clone()),
331            ))
332        };
333        return Ok(module);
334    }
335    Ok(Vc::upcast(module))
336}
337
338#[turbo_tasks::value]
339#[derive(Debug)]
340pub struct ModuleAssetContext {
341    pub transitions: ResolvedVc<TransitionOptions>,
342    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
343    pub module_options_context: ResolvedVc<ModuleOptionsContext>,
344    pub resolve_options_context: ResolvedVc<ResolveOptionsContext>,
345    pub layer: Layer,
346    transition: Option<ResolvedVc<Box<dyn Transition>>>,
347    /// Whether to replace external resolutions with CachedExternalModules. Used with
348    /// ModuleOptionsContext.enable_externals_tracing to handle transitive external dependencies.
349    replace_externals: bool,
350}
351
352#[turbo_tasks::value_impl]
353impl ModuleAssetContext {
354    #[turbo_tasks::function]
355    pub fn new(
356        transitions: ResolvedVc<TransitionOptions>,
357        compile_time_info: ResolvedVc<CompileTimeInfo>,
358        module_options_context: ResolvedVc<ModuleOptionsContext>,
359        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
360        layer: Layer,
361    ) -> Vc<Self> {
362        Self::cell(ModuleAssetContext {
363            transitions,
364            compile_time_info,
365            module_options_context,
366            resolve_options_context,
367            transition: None,
368            layer,
369            replace_externals: true,
370        })
371    }
372
373    #[turbo_tasks::function]
374    pub fn new_transition(
375        transitions: ResolvedVc<TransitionOptions>,
376        compile_time_info: ResolvedVc<CompileTimeInfo>,
377        module_options_context: ResolvedVc<ModuleOptionsContext>,
378        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
379        layer: Layer,
380        transition: ResolvedVc<Box<dyn Transition>>,
381    ) -> Vc<Self> {
382        Self::cell(ModuleAssetContext {
383            transitions,
384            compile_time_info,
385            module_options_context,
386            resolve_options_context,
387            layer,
388            transition: Some(transition),
389            replace_externals: true,
390        })
391    }
392
393    /// Doesn't replace external resolve results with a CachedExternalModule.
394    #[turbo_tasks::function]
395    pub fn new_without_replace_externals(
396        transitions: ResolvedVc<TransitionOptions>,
397        compile_time_info: ResolvedVc<CompileTimeInfo>,
398        module_options_context: ResolvedVc<ModuleOptionsContext>,
399        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
400        layer: Layer,
401    ) -> Vc<Self> {
402        Self::cell(ModuleAssetContext {
403            transitions,
404            compile_time_info,
405            module_options_context,
406            resolve_options_context,
407            transition: None,
408            layer,
409            replace_externals: false,
410        })
411    }
412
413    #[turbo_tasks::function]
414    pub fn module_options_context(&self) -> Vc<ModuleOptionsContext> {
415        *self.module_options_context
416    }
417
418    #[turbo_tasks::function]
419    pub fn resolve_options_context(&self) -> Vc<ResolveOptionsContext> {
420        *self.resolve_options_context
421    }
422
423    #[turbo_tasks::function]
424    pub async fn is_types_resolving_enabled(&self) -> Result<Vc<bool>> {
425        let resolve_options_context = self.resolve_options_context.await?;
426        Ok(Vc::cell(
427            resolve_options_context.enable_types && resolve_options_context.enable_typescript,
428        ))
429    }
430
431    #[turbo_tasks::function]
432    pub async fn with_types_resolving_enabled(self: Vc<Self>) -> Result<Vc<ModuleAssetContext>> {
433        if *self.is_types_resolving_enabled().await? {
434            return Ok(self);
435        }
436        let this = self.await?;
437        let resolve_options_context = this
438            .resolve_options_context
439            .with_types_enabled()
440            .resolve()
441            .await?;
442
443        Ok(ModuleAssetContext::new(
444            *this.transitions,
445            *this.compile_time_info,
446            *this.module_options_context,
447            resolve_options_context,
448            this.layer.clone(),
449        ))
450    }
451}
452
453impl ModuleAssetContext {
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 path_ref = ident.path().await?;
656    let options = ModuleOptions::new(
657        path_ref.parent(),
658        module_asset_context.module_options_context(),
659        module_asset_context.resolve_options_context(),
660    );
661
662    let part: Option<ModulePart> = match &reference_type {
663        ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::ImportPart(part)) => {
664            Some(part.clone())
665        }
666        _ => None,
667    };
668    let inner_assets = match &reference_type {
669        ReferenceType::Internal(inner_assets) => Some(*inner_assets),
670        _ => None,
671    };
672    let mut current_source = source;
673    let mut current_module_type = None;
674
675    // Collect transforms from ExtendEcmascriptTransforms effects.
676    // They will be applied when ModuleType is set.
677    let mut collected_preprocess: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
678    let mut collected_main: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
679    let mut collected_postprocess: Vec<ResolvedVc<EcmascriptInputTransforms>> = Vec::new();
680
681    let options_value = options.await?;
682    'outer: for (i, rule) in options_value.rules.iter().enumerate() {
683        if processed_rules.contains(&i) {
684            continue;
685        }
686        if rule.matches(source, &path_ref, &reference_type).await? {
687            for effect in rule.effects() {
688                match effect {
689                    ModuleRuleEffect::Ignore => {
690                        return Ok(ProcessResult::Ignore.cell());
691                    }
692                    ModuleRuleEffect::SourceTransforms(transforms) => {
693                        current_source =
694                            transforms.transform(*current_source).to_resolved().await?;
695                        if current_source.ident().to_resolved().await? != ident {
696                            // The ident has been changed, so we need to apply new rules.
697                            if let Some(transition) = module_asset_context
698                                .await?
699                                .transitions
700                                .await?
701                                .get_by_rules(current_source, &reference_type)
702                                .await?
703                            {
704                                return Ok(transition.process(
705                                    *current_source,
706                                    module_asset_context,
707                                    reference_type,
708                                ));
709                            } else {
710                                let mut processed_rules = processed_rules.clone();
711                                processed_rules.push(i);
712                                return Box::pin(process_default(
713                                    module_asset_context,
714                                    current_source,
715                                    reference_type,
716                                    processed_rules,
717                                ))
718                                .await;
719                            }
720                        }
721                    }
722                    ModuleRuleEffect::ModuleType(module) => {
723                        // Apply any collected transforms to this module type and exit rule
724                        // processing. Once a ModuleType is determined, we
725                        // stop processing further rules.
726                        let mut module = module.clone();
727                        apply_module_rule_transforms(
728                            &mut module,
729                            &mut collected_preprocess,
730                            &mut collected_main,
731                            &mut collected_postprocess,
732                            ident,
733                            current_source,
734                        )
735                        .await?;
736                        current_module_type = Some(module);
737                        break 'outer;
738                    }
739                    ModuleRuleEffect::ExtendEcmascriptTransforms {
740                        preprocess: extend_preprocess,
741                        main: extend_main,
742                        postprocess: extend_postprocess,
743                    } => {
744                        // Collect transforms. They will be applied when ModuleType is set.
745                        collected_preprocess.push(*extend_preprocess);
746                        collected_main.push(*extend_main);
747                        collected_postprocess.push(*extend_postprocess);
748                    }
749                }
750            }
751        }
752    }
753
754    let Some(module_type) = current_module_type else {
755        return Ok(ProcessResult::Unknown(current_source).cell());
756    };
757
758    let module = apply_module_type(
759        current_source,
760        module_asset_context,
761        module_type.cell(),
762        part,
763        inner_assets,
764        if let ReferenceType::Css(CssReferenceSubType::AtImport(import)) = reference_type {
765            import
766        } else {
767            None
768        },
769        matches!(reference_type, ReferenceType::Runtime),
770    )
771    .await?;
772
773    Ok(module)
774}
775
776#[turbo_tasks::function]
777pub async fn externals_tracing_module_context(
778    compile_time_info: Vc<CompileTimeInfo>,
779) -> Result<Vc<ModuleAssetContext>> {
780    let resolve_options = ResolveOptionsContext {
781        enable_node_native_modules: true,
782        emulate_environment: Some(compile_time_info.await?.environment),
783        loose_errors: true,
784        collect_affecting_sources: true,
785        custom_conditions: vec![rcstr!("node")],
786        ..Default::default()
787    };
788
789    Ok(ModuleAssetContext::new_without_replace_externals(
790        Default::default(),
791        compile_time_info,
792        // This config should be kept in sync with
793        // turbopack/crates/turbopack-tracing/tests/node-file-trace.rs and
794        // turbopack/crates/turbopack-tracing/tests/unit.rs and
795        // turbopack/crates/turbopack/src/lib.rs and
796        // turbopack/crates/turbopack-nft/src/nft.rs
797        ModuleOptionsContext {
798            ecmascript: EcmascriptOptionsContext {
799                enable_typescript_transform: Some(
800                    TypescriptTransformOptions::default().resolved_cell(),
801                ),
802                // enable_types should not be enabled here. It gets set automatically when a TS file
803                // is encountered.
804                source_maps: SourceMapsType::None,
805                ..Default::default()
806            },
807            css: CssOptionsContext {
808                source_maps: SourceMapsType::None,
809                enable_raw_css: true,
810                ..Default::default()
811            },
812            // Environment is not passed in order to avoid downleveling JS / CSS for
813            // node-file-trace.
814            environment: None,
815            analyze_mode: AnalyzeMode::Tracing,
816            // Disable tree shaking. Even side-effect-free imports need to be traced, as they will
817            // execute at runtime.
818            tree_shaking_mode: None,
819            ..Default::default()
820        }
821        .cell(),
822        resolve_options.cell(),
823        Layer::new(rcstr!("externals-tracing")),
824    ))
825}
826
827#[turbo_tasks::value_impl]
828impl AssetContext for ModuleAssetContext {
829    #[turbo_tasks::function]
830    fn compile_time_info(&self) -> Vc<CompileTimeInfo> {
831        *self.compile_time_info
832    }
833
834    fn layer(&self) -> Layer {
835        self.layer.clone()
836    }
837
838    #[turbo_tasks::function]
839    async fn resolve_options(
840        self: Vc<Self>,
841        origin_path: FileSystemPath,
842    ) -> Result<Vc<ResolveOptions>> {
843        let this = self.await?;
844        let module_asset_context = if let Some(transition) = this.transition {
845            transition.process_context(self)
846        } else {
847            self
848        };
849        // TODO move `apply_commonjs/esm_resolve_options` etc. to here
850        Ok(resolve_options(
851            origin_path.parent(),
852            *module_asset_context.await?.resolve_options_context,
853        ))
854    }
855
856    #[turbo_tasks::function]
857    async fn resolve_asset(
858        self: Vc<Self>,
859        origin_path: FileSystemPath,
860        request: Vc<Request>,
861        resolve_options: Vc<ResolveOptions>,
862        reference_type: ReferenceType,
863    ) -> Result<Vc<ModuleResolveResult>> {
864        let context_path = origin_path.parent();
865
866        let result = resolve(
867            context_path,
868            reference_type.clone(),
869            request,
870            resolve_options,
871        );
872
873        let mut result = self.process_resolve_result(result.resolve().await?, reference_type);
874
875        if *self.is_types_resolving_enabled().await? {
876            let types_result = type_resolve(
877                Vc::upcast(PlainResolveOrigin::new(Vc::upcast(self), origin_path)),
878                request,
879            );
880
881            result = ModuleResolveResult::alternatives(vec![result, types_result]);
882        }
883
884        Ok(result)
885    }
886
887    #[turbo_tasks::function]
888    async fn process_resolve_result(
889        self: Vc<Self>,
890        result: Vc<ResolveResult>,
891        reference_type: ReferenceType,
892    ) -> Result<Vc<ModuleResolveResult>> {
893        let this = self.await?;
894
895        let replace_externals = this.replace_externals;
896        let import_externals = this
897            .module_options_context
898            .await?
899            .ecmascript
900            .import_externals;
901
902        let result = result.await?;
903
904        let result = result
905            .map_primary_items(|item| {
906                let reference_type = reference_type.clone();
907                async move {
908                    Ok(match item {
909                        ResolveResultItem::Source(source) => {
910                            match &*self.process(*source, reference_type).await? {
911                                ProcessResult::Module(module) => {
912                                    ModuleResolveResultItem::Module(*module)
913                                }
914                                ProcessResult::Unknown(source) => {
915                                    ModuleResolveResultItem::Unknown(*source)
916                                }
917                                ProcessResult::Ignore => ModuleResolveResultItem::Ignore,
918                            }
919                        }
920                        ResolveResultItem::External {
921                            name,
922                            ty,
923                            traced,
924                            target,
925                        } => {
926                            let replacement = if replace_externals {
927                                // Determine the package folder, `target` is the full path to the
928                                // resolved file.
929                                let target = if let Some(mut target) = target {
930                                    loop {
931                                        let parent = target.parent();
932                                        if parent.is_root() {
933                                            break;
934                                        }
935                                        if parent.file_name() == "node_modules" {
936                                            break;
937                                        }
938                                        if parent.file_name().starts_with("@")
939                                            && parent.parent().file_name() == "node_modules"
940                                        {
941                                            break;
942                                        }
943                                        target = parent;
944                                    }
945                                    Some(target)
946                                } else {
947                                    None
948                                };
949
950                                let analyze_mode = if traced == ExternalTraced::Traced
951                                    && let Some(options) = &self
952                                        .module_options_context()
953                                        .await?
954                                        .enable_externals_tracing
955                                {
956                                    // result.affecting_sources can be ignored for tracing, as this
957                                    // request will later be resolved relative to tracing_root (or
958                                    // the .next/node_modules/lodash-1238123 symlink) anyway.
959
960                                    let options = options.await?;
961                                    let origin = PlainResolveOrigin::new(
962                                        Vc::upcast(externals_tracing_module_context(
963                                            *options.compile_time_info,
964                                        )),
965                                        // If target is specified, a symlink will be created to
966                                        // make the folder
967                                        // itself available, but we still need to trace
968                                        // resolving the individual file(s) inside the package.
969                                        target
970                                            .as_ref()
971                                            .unwrap_or(&options.tracing_root)
972                                            .join("_")?,
973                                    );
974                                    CachedExternalTracingMode::Traced {
975                                        origin: ResolvedVc::upcast(origin.to_resolved().await?),
976                                    }
977                                } else {
978                                    CachedExternalTracingMode::Untraced
979                                };
980
981                                replace_external(&name, ty, target, import_externals, analyze_mode)
982                                    .await?
983                            } else {
984                                None
985                            };
986
987                            replacement
988                                .unwrap_or_else(|| ModuleResolveResultItem::External { name, ty })
989                        }
990                        ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
991                        ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
992                        ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
993                        ResolveResultItem::Custom(u8) => ModuleResolveResultItem::Custom(u8),
994                    })
995                }
996            })
997            .await?;
998
999        Ok(result.cell())
1000    }
1001
1002    #[turbo_tasks::function]
1003    async fn process(
1004        self: Vc<Self>,
1005        asset: ResolvedVc<Box<dyn Source>>,
1006        reference_type: ReferenceType,
1007    ) -> Result<Vc<ProcessResult>> {
1008        let this = self.await?;
1009        if let Some(transition) = this.transition {
1010            Ok(transition.process(*asset, self, reference_type))
1011        } else {
1012            Ok(self
1013                .process_with_transition_rules(asset, reference_type)
1014                .await?)
1015        }
1016    }
1017
1018    #[turbo_tasks::function]
1019    async fn with_transition(&self, transition: RcStr) -> Result<Vc<Box<dyn AssetContext>>> {
1020        Ok(
1021            if let Some(transition) = self.transitions.await?.get_named(transition) {
1022                Vc::upcast(ModuleAssetContext::new_transition(
1023                    *self.transitions,
1024                    *self.compile_time_info,
1025                    *self.module_options_context,
1026                    *self.resolve_options_context,
1027                    self.layer.clone(),
1028                    *transition,
1029                ))
1030            } else {
1031                // TODO report issue
1032                Vc::upcast(ModuleAssetContext::new(
1033                    *self.transitions,
1034                    *self.compile_time_info,
1035                    *self.module_options_context,
1036                    *self.resolve_options_context,
1037                    self.layer.clone(),
1038                ))
1039            },
1040        )
1041    }
1042}
1043
1044#[turbo_tasks::function]
1045pub async fn emit_asset(asset: Vc<Box<dyn OutputAsset>>) -> Result<()> {
1046    asset
1047        .content()
1048        .write(asset.path().owned().await?)
1049        .as_side_effect()
1050        .await?;
1051
1052    Ok(())
1053}
1054
1055#[turbo_tasks::function]
1056pub async fn emit_assets_into_dir(
1057    assets: Vc<ExpandedOutputAssets>,
1058    output_dir: FileSystemPath,
1059) -> Result<()> {
1060    let assets = assets.await?;
1061    let paths = assets.iter().map(|&asset| asset.path()).try_join().await?;
1062    for (&asset, path) in assets.iter().zip(paths.iter()) {
1063        if path.is_inside_ref(&output_dir) {
1064            emit_asset(*asset).as_side_effect().await?;
1065        }
1066    }
1067    Ok(())
1068}
1069
1070#[turbo_tasks::function(operation)]
1071pub async fn emit_assets_into_dir_operation(
1072    assets: ResolvedVc<ExpandedOutputAssets>,
1073    output_dir: FileSystemPath,
1074) -> Result<Vc<()>> {
1075    emit_assets_into_dir(*assets, output_dir)
1076        .as_side_effect()
1077        .await?;
1078    Ok(Vc::cell(()))
1079}
1080
1081/// Replaces the externals in the result with `ExternalModuleAsset` instances.
1082pub async fn replace_external(
1083    name: &RcStr,
1084    ty: ExternalType,
1085    target: Option<FileSystemPath>,
1086    import_externals: bool,
1087    analyze_mode: CachedExternalTracingMode,
1088) -> Result<Option<ModuleResolveResultItem>> {
1089    let external_type = match ty {
1090        ExternalType::CommonJs => CachedExternalType::CommonJs,
1091        ExternalType::EcmaScriptModule => {
1092            if import_externals {
1093                CachedExternalType::EcmaScriptViaImport
1094            } else {
1095                CachedExternalType::EcmaScriptViaRequire
1096            }
1097        }
1098        ExternalType::Global => CachedExternalType::Global,
1099        ExternalType::Script => CachedExternalType::Script,
1100        ExternalType::Url => {
1101            // we don't want to wrap url externals.
1102            return Ok(None);
1103        }
1104    };
1105
1106    let module = CachedExternalModule::new(name.clone(), target, external_type, analyze_mode)
1107        .to_resolved()
1108        .await?;
1109
1110    Ok(Some(ModuleResolveResultItem::Module(ResolvedVc::upcast(
1111        module,
1112    ))))
1113}