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