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;
12mod graph;
13pub mod module_options;
14pub mod transition;
15
16use anyhow::{Result, bail};
17use css::{CssModuleAsset, ModuleCssAsset};
18use ecmascript::{
19    EcmascriptModuleAsset, EcmascriptModuleAssetType, TreeShakingMode,
20    chunk::EcmascriptChunkPlaceable,
21    references::{FollowExportsResult, follow_reexports},
22    side_effect_optimization::facade::module::EcmascriptModuleFacadeModule,
23};
24use graph::{AggregatedGraph, AggregatedGraphNodeContent, aggregate};
25use module_options::{ModuleOptions, ModuleOptionsContext, ModuleRuleEffect, ModuleType};
26use tracing::{Instrument, field::Empty};
27use turbo_rcstr::{RcStr, rcstr};
28use turbo_tasks::{ResolvedVc, ValueToString, Vc};
29use turbo_tasks_fs::{
30    FileSystemPath,
31    glob::{Glob, GlobOptions},
32};
33pub use turbopack_core::condition;
34use turbopack_core::{
35    asset::Asset,
36    chunk::SourceMapsType,
37    compile_time_info::CompileTimeInfo,
38    context::{AssetContext, ProcessResult},
39    ident::Layer,
40    issue::{IssueExt, IssueSource, module::ModuleIssue},
41    module::Module,
42    node_addon_module::NodeAddonModule,
43    output::OutputAsset,
44    raw_module::RawModule,
45    reference_type::{
46        CssReferenceSubType, EcmaScriptModulesReferenceSubType, ImportContext, ImportWithType,
47        InnerAssets, ReferenceType,
48    },
49    resolve::{
50        ExternalTraced, ExternalType, ModulePart, ModuleResolveResult, ModuleResolveResultItem,
51        ResolveResult, ResolveResultItem, options::ResolveOptions, origin::PlainResolveOrigin,
52        parse::Request, resolve,
53    },
54    source::Source,
55};
56pub use turbopack_css as css;
57pub use turbopack_ecmascript as ecmascript;
58use turbopack_ecmascript::{
59    AnalyzeMode,
60    inlined_bytes_module::InlinedBytesJsModule,
61    references::external_module::{
62        CachedExternalModule, CachedExternalTracingMode, CachedExternalType,
63    },
64    side_effect_optimization::locals::module::EcmascriptModuleLocalsModule,
65    tree_shake::asset::EcmascriptModulePartAsset,
66};
67use turbopack_json::JsonModuleAsset;
68pub use turbopack_resolve::{resolve::resolve_options, resolve_options_context};
69use turbopack_resolve::{resolve_options_context::ResolveOptionsContext, typescript::type_resolve};
70use turbopack_static::{css::StaticUrlCssModule, ecma::StaticUrlJsModule};
71use turbopack_wasm::{module_asset::WebAssemblyModuleAsset, source::WebAssemblySource};
72
73use self::transition::{Transition, TransitionOptions};
74use crate::module_options::{
75    CssOptionsContext, CustomModuleType, EcmascriptOptionsContext, TypescriptTransformOptions,
76};
77
78async fn apply_module_type(
79    source: ResolvedVc<Box<dyn Source>>,
80    module_asset_context: Vc<ModuleAssetContext>,
81    module_type: Vc<ModuleType>,
82    part: Option<ModulePart>,
83    inner_assets: Option<ResolvedVc<InnerAssets>>,
84    css_import_context: Option<ResolvedVc<ImportContext>>,
85    runtime_code: bool,
86) -> Result<Vc<ProcessResult>> {
87    let module_type = &*module_type.await?;
88    Ok(ProcessResult::Module(match module_type {
89        ModuleType::Ecmascript {
90            preprocess,
91            main,
92            postprocess,
93            options,
94        }
95        | ModuleType::EcmascriptExtensionless {
96            preprocess,
97            main,
98            postprocess,
99            options,
100        }
101        | ModuleType::Typescript {
102            preprocess,
103            main,
104            postprocess,
105            tsx: _,
106            analyze_types: _,
107            options,
108        }
109        | ModuleType::TypescriptDeclaration {
110            preprocess,
111            main,
112            postprocess,
113            options,
114        } => {
115            let context_for_module = match module_type {
116                ModuleType::Typescript { analyze_types, .. } if *analyze_types => {
117                    module_asset_context.with_types_resolving_enabled()
118                }
119                ModuleType::TypescriptDeclaration { .. } => {
120                    module_asset_context.with_types_resolving_enabled()
121                }
122                _ => module_asset_context,
123            }
124            .to_resolved()
125            .await?;
126            let mut builder = EcmascriptModuleAsset::builder(
127                source,
128                ResolvedVc::upcast(context_for_module),
129                preprocess
130                    .extend(**main)
131                    .extend(**postprocess)
132                    .to_resolved()
133                    .await?,
134                *options,
135                module_asset_context
136                    .compile_time_info()
137                    .to_resolved()
138                    .await?,
139            );
140            match module_type {
141                ModuleType::Ecmascript { .. } => {
142                    builder = builder.with_type(EcmascriptModuleAssetType::Ecmascript)
143                }
144                ModuleType::EcmascriptExtensionless { .. } => {
145                    builder = builder.with_type(EcmascriptModuleAssetType::EcmascriptExtensionless)
146                }
147                ModuleType::Typescript {
148                    tsx, analyze_types, ..
149                } => {
150                    builder = builder.with_type(EcmascriptModuleAssetType::Typescript {
151                        tsx: *tsx,
152                        analyze_types: *analyze_types,
153                    })
154                }
155                ModuleType::TypescriptDeclaration { .. } => {
156                    builder = builder.with_type(EcmascriptModuleAssetType::TypescriptDeclaration)
157                }
158                _ => unreachable!(),
159            }
160
161            if let Some(inner_assets) = inner_assets {
162                builder = builder.with_inner_assets(inner_assets);
163            }
164
165            let module = builder.build().to_resolved().await?;
166            if runtime_code {
167                ResolvedVc::upcast(module)
168            } else {
169                if matches!(&part, Some(ModulePart::Evaluation)) {
170                    // Skip the evaluation part if the module is marked as side effect free.
171                    let side_effect_free_packages = module_asset_context
172                        .side_effect_free_packages()
173                        .resolve()
174                        .await?;
175
176                    if *module
177                        .is_marked_as_side_effect_free(side_effect_free_packages)
178                        .await?
179                    {
180                        return Ok(ProcessResult::Ignore.cell());
181                    }
182                }
183
184                let options_value = options.await?;
185                match options_value.tree_shaking_mode {
186                    Some(TreeShakingMode::ModuleFragments) => {
187                        Vc::upcast(EcmascriptModulePartAsset::select_part(
188                            *module,
189                            part.unwrap_or(ModulePart::facade()),
190                        ))
191                    }
192                    Some(TreeShakingMode::ReexportsOnly) => {
193                        if let Some(part) = part {
194                            match part {
195                                ModulePart::Evaluation => {
196                                    if *module.get_exports().split_locals_and_reexports().await? {
197                                        Vc::upcast(EcmascriptModuleLocalsModule::new(*module))
198                                    } else {
199                                        Vc::upcast(*module)
200                                    }
201                                }
202                                ModulePart::Export(_) => {
203                                    let side_effect_free_packages = module_asset_context
204                                        .side_effect_free_packages()
205                                        .resolve()
206                                        .await?;
207
208                                    if *module.get_exports().split_locals_and_reexports().await? {
209                                        apply_reexport_tree_shaking(
210                                            Vc::upcast(
211                                                EcmascriptModuleFacadeModule::new(
212                                                    Vc::upcast(*module),
213                                                    ModulePart::facade(),
214                                                )
215                                                .resolve()
216                                                .await?,
217                                            ),
218                                            part,
219                                            side_effect_free_packages,
220                                        )
221                                        .await?
222                                    } else {
223                                        apply_reexport_tree_shaking(
224                                            Vc::upcast(*module),
225                                            part,
226                                            side_effect_free_packages,
227                                        )
228                                        .await?
229                                    }
230                                }
231                                _ => bail!(
232                                    "Invalid module part \"{}\" for reexports only tree shaking \
233                                     mode",
234                                    part
235                                ),
236                            }
237                        } else if *module.get_exports().split_locals_and_reexports().await? {
238                            Vc::upcast(EcmascriptModuleFacadeModule::new(
239                                Vc::upcast(*module),
240                                ModulePart::facade(),
241                            ))
242                        } else {
243                            Vc::upcast(*module)
244                        }
245                    }
246                    None => Vc::upcast(*module),
247                }
248                .to_resolved()
249                .await?
250            }
251        }
252        ModuleType::Json => ResolvedVc::upcast(JsonModuleAsset::new(*source).to_resolved().await?),
253        ModuleType::Raw => ResolvedVc::upcast(RawModule::new(*source).to_resolved().await?),
254        ModuleType::NodeAddon => {
255            ResolvedVc::upcast(NodeAddonModule::new(*source).to_resolved().await?)
256        }
257        ModuleType::CssModule => ResolvedVc::upcast(
258            ModuleCssAsset::new(*source, Vc::upcast(module_asset_context))
259                .to_resolved()
260                .await?,
261        ),
262
263        ModuleType::Css { ty, environment } => ResolvedVc::upcast(
264            CssModuleAsset::new(
265                *source,
266                Vc::upcast(module_asset_context),
267                *ty,
268                css_import_context.map(|c| *c),
269                environment.as_deref().copied(),
270            )
271            .to_resolved()
272            .await?,
273        ),
274        ModuleType::StaticUrlJs { tag } => ResolvedVc::upcast(
275            StaticUrlJsModule::new(*source, tag.clone())
276                .to_resolved()
277                .await?,
278        ),
279        ModuleType::StaticUrlCss { tag } => ResolvedVc::upcast(
280            StaticUrlCssModule::new(*source, tag.clone())
281                .to_resolved()
282                .await?,
283        ),
284        ModuleType::InlinedBytesJs => {
285            ResolvedVc::upcast(InlinedBytesJsModule::new(*source).to_resolved().await?)
286        }
287        ModuleType::WebAssembly { source_ty } => ResolvedVc::upcast(
288            WebAssemblyModuleAsset::new(
289                WebAssemblySource::new(*source, *source_ty),
290                Vc::upcast(module_asset_context),
291            )
292            .to_resolved()
293            .await?,
294        ),
295        ModuleType::Custom(custom) => {
296            custom
297                .create_module(*source, module_asset_context, part)
298                .to_resolved()
299                .await?
300        }
301    })
302    .cell())
303}
304
305async fn apply_reexport_tree_shaking(
306    module: Vc<Box<dyn EcmascriptChunkPlaceable>>,
307    part: ModulePart,
308    side_effect_free_packages: Vc<Glob>,
309) -> Result<Vc<Box<dyn Module>>> {
310    if let ModulePart::Export(export) = &part {
311        let FollowExportsResult {
312            module: final_module,
313            export_name: new_export,
314            ..
315        } = &*follow_reexports(module, export.clone(), side_effect_free_packages, true).await?;
316        let module = if let Some(new_export) = new_export {
317            if *new_export == *export {
318                Vc::upcast(**final_module)
319            } else {
320                Vc::upcast(EcmascriptModuleFacadeModule::new(
321                    **final_module,
322                    ModulePart::renamed_export(new_export.clone(), export.clone()),
323                ))
324            }
325        } else {
326            Vc::upcast(EcmascriptModuleFacadeModule::new(
327                **final_module,
328                ModulePart::renamed_namespace(export.clone()),
329            ))
330        };
331        return Ok(module);
332    }
333    Ok(Vc::upcast(module))
334}
335
336#[turbo_tasks::value]
337#[derive(Debug)]
338pub struct ModuleAssetContext {
339    pub transitions: ResolvedVc<TransitionOptions>,
340    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
341    pub module_options_context: ResolvedVc<ModuleOptionsContext>,
342    pub resolve_options_context: ResolvedVc<ResolveOptionsContext>,
343    pub layer: Layer,
344    transition: Option<ResolvedVc<Box<dyn Transition>>>,
345    /// Whether to replace external resolutions with CachedExternalModules. Used with
346    /// ModuleOptionsContext.enable_externals_tracing to handle transitive external dependencies.
347    replace_externals: bool,
348}
349
350#[turbo_tasks::value_impl]
351impl ModuleAssetContext {
352    #[turbo_tasks::function]
353    pub fn new(
354        transitions: ResolvedVc<TransitionOptions>,
355        compile_time_info: ResolvedVc<CompileTimeInfo>,
356        module_options_context: ResolvedVc<ModuleOptionsContext>,
357        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
358        layer: Layer,
359    ) -> Vc<Self> {
360        Self::cell(ModuleAssetContext {
361            transitions,
362            compile_time_info,
363            module_options_context,
364            resolve_options_context,
365            transition: None,
366            layer,
367            replace_externals: true,
368        })
369    }
370
371    #[turbo_tasks::function]
372    pub fn new_transition(
373        transitions: ResolvedVc<TransitionOptions>,
374        compile_time_info: ResolvedVc<CompileTimeInfo>,
375        module_options_context: ResolvedVc<ModuleOptionsContext>,
376        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
377        layer: Layer,
378        transition: ResolvedVc<Box<dyn Transition>>,
379    ) -> Vc<Self> {
380        Self::cell(ModuleAssetContext {
381            transitions,
382            compile_time_info,
383            module_options_context,
384            resolve_options_context,
385            layer,
386            transition: Some(transition),
387            replace_externals: true,
388        })
389    }
390
391    #[turbo_tasks::function]
392    fn new_without_replace_externals(
393        transitions: ResolvedVc<TransitionOptions>,
394        compile_time_info: ResolvedVc<CompileTimeInfo>,
395        module_options_context: ResolvedVc<ModuleOptionsContext>,
396        resolve_options_context: ResolvedVc<ResolveOptionsContext>,
397        layer: Layer,
398    ) -> Vc<Self> {
399        Self::cell(ModuleAssetContext {
400            transitions,
401            compile_time_info,
402            module_options_context,
403            resolve_options_context,
404            transition: None,
405            layer,
406            replace_externals: false,
407        })
408    }
409
410    #[turbo_tasks::function]
411    pub fn module_options_context(&self) -> Vc<ModuleOptionsContext> {
412        *self.module_options_context
413    }
414
415    #[turbo_tasks::function]
416    pub fn resolve_options_context(&self) -> Vc<ResolveOptionsContext> {
417        *self.resolve_options_context
418    }
419
420    #[turbo_tasks::function]
421    pub async fn is_types_resolving_enabled(&self) -> Result<Vc<bool>> {
422        let resolve_options_context = self.resolve_options_context.await?;
423        Ok(Vc::cell(
424            resolve_options_context.enable_types && resolve_options_context.enable_typescript,
425        ))
426    }
427
428    #[turbo_tasks::function]
429    pub async fn with_types_resolving_enabled(self: Vc<Self>) -> Result<Vc<ModuleAssetContext>> {
430        if *self.is_types_resolving_enabled().await? {
431            return Ok(self);
432        }
433        let this = self.await?;
434        let resolve_options_context = this
435            .resolve_options_context
436            .with_types_enabled()
437            .resolve()
438            .await?;
439
440        Ok(ModuleAssetContext::new(
441            *this.transitions,
442            *this.compile_time_info,
443            *this.module_options_context,
444            resolve_options_context,
445            this.layer.clone(),
446        ))
447    }
448}
449
450impl ModuleAssetContext {
451    async fn process_with_transition_rules(
452        self: Vc<Self>,
453        source: ResolvedVc<Box<dyn Source>>,
454        reference_type: ReferenceType,
455    ) -> Result<Vc<ProcessResult>> {
456        let this = self.await?;
457        Ok(
458            if let Some(transition) = this
459                .transitions
460                .await?
461                .get_by_rules(source, &reference_type)
462                .await?
463            {
464                transition.process(*source, self, reference_type)
465            } else {
466                self.process_default(source, reference_type).await?
467            },
468        )
469    }
470
471    async fn process_default(
472        self: Vc<Self>,
473        source: ResolvedVc<Box<dyn Source>>,
474        reference_type: ReferenceType,
475    ) -> Result<Vc<ProcessResult>> {
476        process_default(self, source, reference_type, Vec::new()).await
477    }
478}
479
480async fn process_default(
481    module_asset_context: Vc<ModuleAssetContext>,
482    source: ResolvedVc<Box<dyn Source>>,
483    reference_type: ReferenceType,
484    processed_rules: Vec<usize>,
485) -> Result<Vc<ProcessResult>> {
486    let span = tracing::info_span!(
487        "process module",
488        name = %source.ident().to_string().await?,
489        layer = Empty,
490        reference_type = display(&reference_type)
491    );
492    if !span.is_disabled() {
493        // You can't await multiple times in the span macro call parameters.
494        span.record("layer", module_asset_context.await?.layer.name().as_str());
495    }
496
497    process_default_internal(
498        module_asset_context,
499        source,
500        reference_type,
501        processed_rules,
502    )
503    .instrument(span)
504    .await
505}
506
507async fn process_default_internal(
508    module_asset_context: Vc<ModuleAssetContext>,
509    source: ResolvedVc<Box<dyn Source>>,
510    reference_type: ReferenceType,
511    processed_rules: Vec<usize>,
512) -> Result<Vc<ProcessResult>> {
513    let ident = source.ident().to_resolved().await?;
514    let path_ref = ident.path().await?;
515    let options = ModuleOptions::new(
516        path_ref.parent(),
517        module_asset_context.module_options_context(),
518        module_asset_context.resolve_options_context(),
519    );
520
521    let part: Option<ModulePart> = match &reference_type {
522        ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::ImportPart(part)) => {
523            Some(part.clone())
524        }
525        _ => None,
526    };
527    let inner_assets = match &reference_type {
528        ReferenceType::Internal(inner_assets) => Some(*inner_assets),
529        _ => None,
530    };
531
532    let mut has_type_attribute = false;
533
534    let mut current_source = source;
535    let mut current_module_type = match &reference_type {
536        ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::ImportWithType(ty)) => {
537            has_type_attribute = true;
538
539            match ty {
540                ImportWithType::Json => Some(ModuleType::Json),
541                // Reenable this once `import {type: "bytes"}` is stabilized
542                ImportWithType::Bytes => None,
543            }
544        }
545        _ => None,
546    };
547
548    for (i, rule) in options.await?.rules.iter().enumerate() {
549        if has_type_attribute && current_module_type.is_some() {
550            continue;
551        }
552        if processed_rules.contains(&i) {
553            continue;
554        }
555        if rule.matches(source, &path_ref, &reference_type).await? {
556            for effect in rule.effects() {
557                match effect {
558                    ModuleRuleEffect::Ignore => {
559                        return Ok(ProcessResult::Ignore.cell());
560                    }
561                    ModuleRuleEffect::SourceTransforms(transforms) => {
562                        current_source =
563                            transforms.transform(*current_source).to_resolved().await?;
564                        if current_source.ident().to_resolved().await? != ident {
565                            // The ident has been changed, so we need to apply new rules.
566                            if let Some(transition) = module_asset_context
567                                .await?
568                                .transitions
569                                .await?
570                                .get_by_rules(current_source, &reference_type)
571                                .await?
572                            {
573                                return Ok(transition.process(
574                                    *current_source,
575                                    module_asset_context,
576                                    reference_type,
577                                ));
578                            } else {
579                                let mut processed_rules = processed_rules.clone();
580                                processed_rules.push(i);
581                                return Box::pin(process_default(
582                                    module_asset_context,
583                                    current_source,
584                                    reference_type,
585                                    processed_rules,
586                                ))
587                                .await;
588                            }
589                        }
590                    }
591                    ModuleRuleEffect::ModuleType(module) => {
592                        current_module_type = Some(module.clone());
593                    }
594                    ModuleRuleEffect::ExtendEcmascriptTransforms {
595                        preprocess: extend_preprocess,
596                        main: extend_main,
597                        postprocess: extend_postprocess,
598                    } => {
599                        current_module_type = match current_module_type {
600                            Some(ModuleType::Ecmascript {
601                                preprocess,
602                                main,
603                                postprocess,
604                                options,
605                            }) => Some(ModuleType::Ecmascript {
606                                preprocess: extend_preprocess
607                                    .extend(*preprocess)
608                                    .to_resolved()
609                                    .await?,
610                                main: extend_main.extend(*main).to_resolved().await?,
611                                postprocess: postprocess
612                                    .extend(**extend_postprocess)
613                                    .to_resolved()
614                                    .await?,
615                                options,
616                            }),
617                            Some(ModuleType::Typescript {
618                                preprocess,
619                                main,
620                                postprocess,
621                                tsx,
622                                analyze_types,
623                                options,
624                            }) => Some(ModuleType::Typescript {
625                                preprocess: extend_preprocess
626                                    .extend(*preprocess)
627                                    .to_resolved()
628                                    .await?,
629                                main: extend_main.extend(*main).to_resolved().await?,
630                                postprocess: postprocess
631                                    .extend(**extend_postprocess)
632                                    .to_resolved()
633                                    .await?,
634                                tsx,
635                                analyze_types,
636                                options,
637                            }),
638                            Some(ModuleType::Custom(custom_module_type)) => {
639                                match custom_module_type
640                                    .extend_ecmascript_transforms(
641                                        **extend_preprocess,
642                                        **extend_main,
643                                        **extend_postprocess,
644                                    )
645                                    .to_resolved()
646                                    .await
647                                {
648                                    Ok(custom_module_type) => {
649                                        Some(ModuleType::Custom(custom_module_type))
650                                    }
651                                    // TODO ideally this would print the actual error message
652                                    // returned by the CustomModuleType
653                                    Err(_) => {
654                                        ModuleIssue::new(
655                                            *ident,
656                                            rcstr!("Invalid module type"),
657                                            rcstr!(
658                                                "The custom module type didn't accept the \
659                                                 additional Ecmascript transforms"
660                                            ),
661                                            Some(IssueSource::from_source_only(current_source)),
662                                        )
663                                        .to_resolved()
664                                        .await?
665                                        .emit();
666                                        Some(ModuleType::Custom(custom_module_type))
667                                    }
668                                }
669                            }
670                            Some(module_type) => {
671                                ModuleIssue::new(
672                                    *ident,
673                                    rcstr!("Invalid module type"),
674                                    rcstr!(
675                                        "The module type must be Ecmascript or Typescript to add \
676                                         Ecmascript transforms"
677                                    ),
678                                    Some(IssueSource::from_source_only(current_source)),
679                                )
680                                .to_resolved()
681                                .await?
682                                .emit();
683                                Some(module_type)
684                            }
685                            None => {
686                                ModuleIssue::new(
687                                    *ident,
688                                    rcstr!("Missing module type"),
689                                    rcstr!(
690                                        "The module type effect must be applied before adding \
691                                         Ecmascript transforms"
692                                    ),
693                                    Some(IssueSource::from_source_only(current_source)),
694                                )
695                                .to_resolved()
696                                .await?
697                                .emit();
698                                None
699                            }
700                        };
701                    }
702                }
703            }
704        }
705    }
706
707    let Some(module_type) = current_module_type else {
708        return Ok(ProcessResult::Unknown(current_source).cell());
709    };
710
711    apply_module_type(
712        current_source,
713        module_asset_context,
714        module_type.cell(),
715        part,
716        inner_assets,
717        if let ReferenceType::Css(CssReferenceSubType::AtImport(import)) = reference_type {
718            import
719        } else {
720            None
721        },
722        matches!(reference_type, ReferenceType::Runtime),
723    )
724    .await
725}
726
727#[turbo_tasks::function]
728pub async fn externals_tracing_module_context(
729    compile_time_info: Vc<CompileTimeInfo>,
730) -> Result<Vc<ModuleAssetContext>> {
731    let resolve_options = ResolveOptionsContext {
732        enable_node_native_modules: true,
733        emulate_environment: Some(compile_time_info.await?.environment),
734        loose_errors: true,
735        collect_affecting_sources: true,
736        custom_conditions: vec![rcstr!("node")],
737        ..Default::default()
738    };
739
740    Ok(ModuleAssetContext::new_without_replace_externals(
741        Default::default(),
742        compile_time_info,
743        // Keep these options more or less in sync with
744        // turbopack/crates/turbopack/tests/node-file-trace.rs to ensure that the NFT unit tests
745        // are actually representative of what Turbopack does.
746        ModuleOptionsContext {
747            ecmascript: EcmascriptOptionsContext {
748                enable_typescript_transform: Some(
749                    TypescriptTransformOptions::default().resolved_cell(),
750                ),
751                // enable_types should not be enabled here. It gets set automatically when a TS file
752                // is encountered.
753                source_maps: SourceMapsType::None,
754                ..Default::default()
755            },
756            css: CssOptionsContext {
757                source_maps: SourceMapsType::None,
758                enable_raw_css: true,
759                ..Default::default()
760            },
761            // Environment is not passed in order to avoid downleveling JS / CSS for
762            // node-file-trace.
763            environment: None,
764            analyze_mode: AnalyzeMode::Tracing,
765            ..Default::default()
766        }
767        .cell(),
768        resolve_options.cell(),
769        Layer::new(rcstr!("externals-tracing")),
770    ))
771}
772
773#[turbo_tasks::value_impl]
774impl AssetContext for ModuleAssetContext {
775    #[turbo_tasks::function]
776    fn compile_time_info(&self) -> Vc<CompileTimeInfo> {
777        *self.compile_time_info
778    }
779
780    fn layer(&self) -> Layer {
781        self.layer.clone()
782    }
783
784    #[turbo_tasks::function]
785    async fn resolve_options(
786        self: Vc<Self>,
787        origin_path: FileSystemPath,
788        _reference_type: ReferenceType,
789    ) -> Result<Vc<ResolveOptions>> {
790        let this = self.await?;
791        let module_asset_context = if let Some(transition) = this.transition {
792            transition.process_context(self)
793        } else {
794            self
795        };
796        // TODO move `apply_commonjs/esm_resolve_options` etc. to here
797        Ok(resolve_options(
798            origin_path.parent(),
799            *module_asset_context.await?.resolve_options_context,
800        ))
801    }
802
803    #[turbo_tasks::function]
804    async fn resolve_asset(
805        self: Vc<Self>,
806        origin_path: FileSystemPath,
807        request: Vc<Request>,
808        resolve_options: Vc<ResolveOptions>,
809        reference_type: ReferenceType,
810    ) -> Result<Vc<ModuleResolveResult>> {
811        let context_path = origin_path.parent();
812
813        let result = resolve(
814            context_path,
815            reference_type.clone(),
816            request,
817            resolve_options,
818        );
819
820        let mut result = self.process_resolve_result(result.resolve().await?, reference_type);
821
822        if *self.is_types_resolving_enabled().await? {
823            let types_result = type_resolve(
824                Vc::upcast(PlainResolveOrigin::new(Vc::upcast(self), origin_path)),
825                request,
826            );
827
828            result = ModuleResolveResult::alternatives(vec![result, types_result]);
829        }
830
831        Ok(result)
832    }
833
834    #[turbo_tasks::function]
835    async fn process_resolve_result(
836        self: Vc<Self>,
837        result: Vc<ResolveResult>,
838        reference_type: ReferenceType,
839    ) -> Result<Vc<ModuleResolveResult>> {
840        let this = self.await?;
841
842        let replace_externals = this.replace_externals;
843        let import_externals = this
844            .module_options_context
845            .await?
846            .ecmascript
847            .import_externals;
848
849        let result = result.await?;
850
851        let result = result
852            .map_primary_items(|item| {
853                let reference_type = reference_type.clone();
854                async move {
855                    Ok(match item {
856                        ResolveResultItem::Source(source) => {
857                            match &*self.process(*source, reference_type).await? {
858                                ProcessResult::Module(module) => {
859                                    ModuleResolveResultItem::Module(*module)
860                                }
861                                ProcessResult::Unknown(source) => {
862                                    ModuleResolveResultItem::Unknown(*source)
863                                }
864                                ProcessResult::Ignore => ModuleResolveResultItem::Ignore,
865                            }
866                        }
867                        ResolveResultItem::External { name, ty, traced } => {
868                            let replacement = if replace_externals {
869                                let analyze_mode = if traced == ExternalTraced::Traced
870                                    && let Some(options) = &self
871                                        .module_options_context()
872                                        .await?
873                                        .enable_externals_tracing
874                                {
875                                    // result.affecting_sources can be ignored for tracing, as this
876                                    // request will later be resolved relative to tracing_root
877                                    // anyway.
878
879                                    let options = options.await?;
880                                    let origin = PlainResolveOrigin::new(
881                                        Vc::upcast(externals_tracing_module_context(
882                                            *options.compile_time_info,
883                                        )),
884                                        options.tracing_root.join("_")?,
885                                    );
886                                    CachedExternalTracingMode::Traced {
887                                        origin: ResolvedVc::upcast(origin.to_resolved().await?),
888                                    }
889                                } else {
890                                    CachedExternalTracingMode::Untraced
891                                };
892
893                                replace_external(&name, ty, import_externals, analyze_mode).await?
894                            } else {
895                                None
896                            };
897
898                            replacement
899                                .unwrap_or_else(|| ModuleResolveResultItem::External { name, ty })
900                        }
901                        ResolveResultItem::Ignore => ModuleResolveResultItem::Ignore,
902                        ResolveResultItem::Empty => ModuleResolveResultItem::Empty,
903                        ResolveResultItem::Error(e) => ModuleResolveResultItem::Error(e),
904                        ResolveResultItem::Custom(u8) => ModuleResolveResultItem::Custom(u8),
905                    })
906                }
907            })
908            .await?;
909
910        Ok(result.cell())
911    }
912
913    #[turbo_tasks::function]
914    async fn process(
915        self: Vc<Self>,
916        asset: ResolvedVc<Box<dyn Source>>,
917        reference_type: ReferenceType,
918    ) -> Result<Vc<ProcessResult>> {
919        let this = self.await?;
920        if let Some(transition) = this.transition {
921            Ok(transition.process(*asset, self, reference_type))
922        } else {
923            Ok(self
924                .process_with_transition_rules(asset, reference_type)
925                .await?)
926        }
927    }
928
929    #[turbo_tasks::function]
930    async fn with_transition(&self, transition: RcStr) -> Result<Vc<Box<dyn AssetContext>>> {
931        Ok(
932            if let Some(transition) = self.transitions.await?.get_named(transition) {
933                Vc::upcast(ModuleAssetContext::new_transition(
934                    *self.transitions,
935                    *self.compile_time_info,
936                    *self.module_options_context,
937                    *self.resolve_options_context,
938                    self.layer.clone(),
939                    *transition,
940                ))
941            } else {
942                // TODO report issue
943                Vc::upcast(ModuleAssetContext::new(
944                    *self.transitions,
945                    *self.compile_time_info,
946                    *self.module_options_context,
947                    *self.resolve_options_context,
948                    self.layer.clone(),
949                ))
950            },
951        )
952    }
953
954    #[turbo_tasks::function]
955    async fn side_effect_free_packages(&self) -> Result<Vc<Glob>> {
956        let pkgs = &self.module_options_context.await?.side_effect_free_packages;
957
958        let mut globs = String::new();
959        globs.push_str("**/node_modules/{");
960        globs.push_str(&pkgs.join(","));
961        globs.push_str("}/**");
962
963        Ok(Glob::new(globs.into(), GlobOptions::default()))
964    }
965}
966
967#[turbo_tasks::function]
968pub async fn emit_with_completion(
969    asset: Vc<Box<dyn OutputAsset>>,
970    output_dir: FileSystemPath,
971) -> Result<()> {
972    emit_assets_aggregated(asset, output_dir)
973        .as_side_effect()
974        .await
975}
976
977#[turbo_tasks::function(operation)]
978pub fn emit_with_completion_operation(
979    asset: ResolvedVc<Box<dyn OutputAsset>>,
980    output_dir: FileSystemPath,
981) -> Vc<()> {
982    emit_with_completion(*asset, output_dir)
983}
984
985#[turbo_tasks::function]
986async fn emit_assets_aggregated(
987    asset: Vc<Box<dyn OutputAsset>>,
988    output_dir: FileSystemPath,
989) -> Result<()> {
990    let aggregated = aggregate(asset);
991    emit_aggregated_assets(aggregated, output_dir)
992        .as_side_effect()
993        .await
994}
995
996#[turbo_tasks::function]
997async fn emit_aggregated_assets(
998    aggregated: Vc<AggregatedGraph>,
999    output_dir: FileSystemPath,
1000) -> Result<()> {
1001    match &*aggregated.content().await? {
1002        AggregatedGraphNodeContent::Asset(asset) => {
1003            emit_asset_into_dir(**asset, output_dir)
1004                .as_side_effect()
1005                .await?;
1006        }
1007        AggregatedGraphNodeContent::Children(children) => {
1008            for aggregated in children {
1009                emit_aggregated_assets(**aggregated, output_dir.clone())
1010                    .as_side_effect()
1011                    .await?;
1012            }
1013        }
1014    }
1015    Ok(())
1016}
1017
1018#[turbo_tasks::function]
1019pub async fn emit_asset(asset: Vc<Box<dyn OutputAsset>>) -> Result<()> {
1020    asset
1021        .content()
1022        .write(asset.path().owned().await?)
1023        .as_side_effect()
1024        .await?;
1025
1026    Ok(())
1027}
1028
1029#[turbo_tasks::function]
1030pub async fn emit_asset_into_dir(
1031    asset: Vc<Box<dyn OutputAsset>>,
1032    output_dir: FileSystemPath,
1033) -> Result<()> {
1034    let dir = output_dir.clone();
1035    if asset.path().await?.is_inside_ref(&dir) {
1036        emit_asset(asset).as_side_effect().await?;
1037    }
1038    Ok(())
1039}
1040
1041/// Replaces the externals in the result with `ExternalModuleAsset` instances.
1042pub async fn replace_external(
1043    name: &RcStr,
1044    ty: ExternalType,
1045    import_externals: bool,
1046    analyze_mode: CachedExternalTracingMode,
1047) -> Result<Option<ModuleResolveResultItem>> {
1048    let external_type = match ty {
1049        ExternalType::CommonJs => CachedExternalType::CommonJs,
1050        ExternalType::EcmaScriptModule => {
1051            if import_externals {
1052                CachedExternalType::EcmaScriptViaImport
1053            } else {
1054                CachedExternalType::EcmaScriptViaRequire
1055            }
1056        }
1057        ExternalType::Global => CachedExternalType::Global,
1058        ExternalType::Script => CachedExternalType::Script,
1059        ExternalType::Url => {
1060            // we don't want to wrap url externals.
1061            return Ok(None);
1062        }
1063    };
1064
1065    let module = CachedExternalModule::new(name.clone(), external_type, analyze_mode)
1066        .to_resolved()
1067        .await?;
1068
1069    Ok(Some(ModuleResolveResultItem::Module(ResolvedVc::upcast(
1070        module,
1071    ))))
1072}