Skip to main content

turbopack/module_options/
mod.rs

1pub(crate) mod custom_module_type;
2pub mod match_mode;
3pub mod module_options_context;
4pub mod module_rule;
5pub mod rule_condition;
6pub mod transition_rule;
7
8use anyhow::{Context, Result};
9pub use custom_module_type::CustomModuleType;
10pub use module_options_context::*;
11pub use module_rule::*;
12pub use rule_condition::*;
13use turbo_rcstr::{RcStr, rcstr};
14use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
15use turbo_tasks_fs::{
16    FileSystemPath,
17    glob::{Glob, GlobOptions},
18};
19use turbopack_core::{
20    chunk::SourceMapsType,
21    ident::Layer,
22    reference_type::{
23        CssReferenceSubType, EcmaScriptModulesReferenceSubType, ReferenceTypeCondition,
24        UrlReferenceSubType,
25    },
26    resolve::options::{ImportMap, ImportMapping},
27};
28use turbopack_css::CssModuleType;
29use turbopack_ecmascript::{
30    AnalyzeMode, EcmascriptInputTransform, EcmascriptInputTransforms, EcmascriptOptions,
31    SpecifiedModuleType, bytes_source_transform::BytesSourceTransform,
32    json_source_transform::JsonSourceTransform, text_source_transform::TextSourceTransform,
33    transform::PresetEnvConfig,
34};
35use turbopack_mdx::MdxTransform;
36use turbopack_node::{
37    execution_context::ExecutionContext,
38    transforms::{postcss::PostCssTransform, webpack::WebpackLoaders},
39};
40use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
41use turbopack_wasm::source::WebAssemblySourceType;
42
43use crate::evaluate_context::{config_tracing_module_context, node_evaluate_asset_context};
44
45#[turbo_tasks::function]
46pub(crate) fn package_import_map_from_import_mapping(
47    package_name: RcStr,
48    package_mapping: ResolvedVc<ImportMapping>,
49) -> Vc<ImportMap> {
50    let mut import_map = ImportMap::default();
51    import_map.insert_exact_alias(
52        RcStr::from(format!("@vercel/turbopack/{package_name}")),
53        package_mapping,
54    );
55    import_map.cell()
56}
57
58#[turbo_tasks::function]
59pub(crate) fn package_import_map_from_context(
60    package_name: RcStr,
61    context_path: FileSystemPath,
62) -> Vc<ImportMap> {
63    let mut import_map = ImportMap::default();
64    import_map.insert_exact_alias(
65        RcStr::from(format!("@vercel/turbopack/{package_name}")),
66        ImportMapping::PrimaryAlternative(package_name, Some(context_path)).resolved_cell(),
67    );
68    import_map.cell()
69}
70
71async fn rule_condition_from_webpack_condition_glob(
72    execution_context: ResolvedVc<ExecutionContext>,
73    glob: &RcStr,
74) -> Result<RuleCondition> {
75    Ok(if glob.contains('/') {
76        RuleCondition::ResourcePathGlob {
77            base: execution_context.project_path().owned().await?,
78            glob: Glob::new(glob.clone(), GlobOptions::default()).await?,
79        }
80    } else {
81        RuleCondition::ResourceBasePathGlob(Glob::new(glob.clone(), GlobOptions::default()).await?)
82    })
83}
84
85async fn rule_condition_from_webpack_condition(
86    execution_context: ResolvedVc<ExecutionContext>,
87    builtin_conditions: &dyn WebpackLoaderBuiltinConditionSet,
88    webpack_loader_condition: &ConditionItem,
89) -> Result<RuleCondition> {
90    Ok(match webpack_loader_condition {
91        ConditionItem::All(conds) => RuleCondition::All(
92            conds
93                .iter()
94                .map(|c| {
95                    rule_condition_from_webpack_condition(execution_context, builtin_conditions, c)
96                })
97                .try_join()
98                .await?,
99        ),
100        ConditionItem::Any(conds) => RuleCondition::Any(
101            conds
102                .iter()
103                .map(|c| {
104                    rule_condition_from_webpack_condition(execution_context, builtin_conditions, c)
105                })
106                .try_join()
107                .await?,
108        ),
109        ConditionItem::Not(cond) => RuleCondition::Not(Box::new(
110            Box::pin(rule_condition_from_webpack_condition(
111                execution_context,
112                builtin_conditions,
113                cond,
114            ))
115            .await?,
116        )),
117        ConditionItem::Builtin(name) => match builtin_conditions.match_condition(name) {
118            WebpackLoaderBuiltinConditionSetMatch::Matched => RuleCondition::True,
119            WebpackLoaderBuiltinConditionSetMatch::Unmatched => RuleCondition::False,
120            WebpackLoaderBuiltinConditionSetMatch::Invalid => {
121                // We don't expect the user to hit this because whatever deserailizes the user
122                // configuration should validate conditions itself
123                anyhow::bail!("{name:?} is not a valid built-in condition")
124            }
125        },
126        ConditionItem::Base {
127            path,
128            content,
129            query,
130            content_type,
131        } => {
132            let mut rule_conditions = Vec::new();
133            match &path {
134                Some(ConditionPath::Glob(glob)) => rule_conditions.push(
135                    rule_condition_from_webpack_condition_glob(execution_context, glob).await?,
136                ),
137                Some(ConditionPath::Regex(regex)) => {
138                    rule_conditions.push(RuleCondition::ResourcePathEsRegex(regex.await?));
139                }
140                None => {}
141            }
142            match &query {
143                Some(ConditionQuery::Constant(value)) => {
144                    rule_conditions.push(RuleCondition::ResourceQueryEquals(value.clone().into()));
145                }
146                Some(ConditionQuery::Regex(regex)) => {
147                    rule_conditions.push(RuleCondition::ResourceQueryEsRegex(regex.await?));
148                }
149                None => {}
150            }
151            match &content_type {
152                Some(ConditionContentType::Glob(glob)) => {
153                    rule_conditions.push(RuleCondition::ContentTypeGlob(
154                        Glob::new(glob.clone(), GlobOptions::default()).await?,
155                    ));
156                }
157                Some(ConditionContentType::Regex(regex)) => {
158                    rule_conditions.push(RuleCondition::ContentTypeEsRegex(regex.await?));
159                }
160                None => {}
161            }
162            // Add the content condition last since matching requires a more expensive file read.
163            if let Some(content) = content {
164                rule_conditions.push(RuleCondition::ResourceContentEsRegex(content.await?));
165            }
166            RuleCondition::All(rule_conditions)
167        }
168    })
169}
170
171#[turbo_tasks::value(cell = "new", eq = "manual")]
172pub struct ModuleOptions {
173    pub rules: Vec<ModuleRule>,
174}
175
176#[turbo_tasks::value_impl]
177impl ModuleOptions {
178    #[turbo_tasks::function]
179    pub async fn new(
180        path: FileSystemPath,
181        module_options_context: Vc<ModuleOptionsContext>,
182        resolve_options_context: Vc<ResolveOptionsContext>,
183    ) -> Result<Vc<ModuleOptions>> {
184        let ModuleOptionsContext {
185            css: CssOptionsContext { enable_raw_css, .. },
186            ref enable_postcss_transform,
187            ref enable_webpack_loaders,
188            ref rules,
189            ..
190        } = *module_options_context.await?;
191
192        if !rules.is_empty() {
193            for (condition, new_context) in rules.iter() {
194                if condition.matches(&path) {
195                    return Ok(ModuleOptions::new(
196                        path,
197                        **new_context,
198                        resolve_options_context,
199                    ));
200                }
201            }
202        }
203
204        let need_path = (!enable_raw_css
205            && if let Some(options) = enable_postcss_transform {
206                let options = options.await?;
207                options.postcss_package.is_none()
208            } else {
209                false
210            })
211            || if let Some(options) = enable_webpack_loaders {
212                let options = options.await?;
213                options.loader_runner_package.is_none()
214            } else {
215                false
216            };
217
218        Ok(Self::new_internal(
219            need_path.then_some(path),
220            module_options_context,
221            resolve_options_context,
222        ))
223    }
224
225    #[turbo_tasks::function]
226    async fn new_internal(
227        path: Option<FileSystemPath>,
228        module_options_context: Vc<ModuleOptionsContext>,
229        resolve_options_context: Vc<ResolveOptionsContext>,
230    ) -> Result<Vc<ModuleOptions>> {
231        let ModuleOptionsContext {
232            ecmascript:
233                EcmascriptOptionsContext {
234                    enable_jsx,
235                    enable_rust_react_compiler,
236                    rust_react_compiler_target,
237                    enable_types,
238                    ref enable_typescript_transform,
239                    ref enable_decorators,
240                    ignore_dynamic_requests,
241                    import_externals,
242                    esm_url_rewrite_behavior,
243                    enable_typeof_window_inlining,
244                    enable_exports_info_inlining,
245                    enable_import_as_bytes,
246                    source_maps: ecmascript_source_maps,
247                    inline_helpers,
248                    infer_module_side_effects,
249                    cjs_tree_shaking,
250                    mangle_export_names,
251                    cjs_scope_hoisting,
252                    cross_module_constants,
253                    ref preset_env_config,
254                    ..
255                },
256            enable_mdx,
257            enable_mdx_rs,
258            css:
259                CssOptionsContext {
260                    enable_raw_css,
261                    source_maps: css_source_maps,
262                    ref module_css_condition,
263                    lightningcss_features,
264                    module_css_debuggable_idents,
265                    ..
266                },
267            ref static_url_tag,
268            ref enable_postcss_transform,
269            ref enable_webpack_loaders,
270            environment,
271            ref module_rules,
272            execution_context,
273            follow_reexports,
274            module_fragments_enabled,
275            keep_last_successful_parse,
276            analyze_mode,
277            ..
278        } = *module_options_context.await?;
279
280        let module_css_condition = module_css_condition.clone().unwrap_or_else(|| {
281            RuleCondition::any(vec![
282                RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
283                RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
284            ])
285        });
286
287        // For React Client References, the CSS Module "facade" module lives in the parent (server)
288        // module context, but the facade's references should be transitioned to the client (and
289        // only then be processed with Webpack/PostCSS).
290        //
291        // Note that this is not an exhaustive condition for PostCSS/Webpack, but excludes certain
292        // cases, so it should be added conjunctively together with CSS Module rule.
293        //
294        // If module css, then only when (Inner or Analyze or Compose)
295        // <=> (not (module css)) or (Inner or Analyzer or Compose)
296        //
297        // So only if this is not a CSS module, or one of the special reference type constraints.
298        let module_css_external_transform_conditions = RuleCondition::Any(vec![
299            RuleCondition::not(module_css_condition.clone()),
300            RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
301                CssReferenceSubType::Inner,
302            ))),
303            RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
304                CssReferenceSubType::Analyze,
305            ))),
306        ]);
307
308        let mut ecma_preprocess = vec![];
309        let mut postprocess = vec![];
310
311        if let Some(compilation_mode) = enable_rust_react_compiler {
312            ecma_preprocess.push(EcmascriptInputTransform::ReactCompilerRust {
313                compilation_mode,
314                target: rust_react_compiler_target,
315            });
316        }
317
318        // Order of transforms is important. e.g. if the React transform occurs before
319        // Styled JSX, there won't be JSX nodes for Styled JSX to transform.
320        // If a custom plugin requires specific order _before_ core transform kicks in,
321        // should use `before_transform_plugins`.
322        if let Some(enable_jsx) = enable_jsx {
323            let jsx = enable_jsx.await?;
324
325            postprocess.push(EcmascriptInputTransform::React {
326                development: jsx.development,
327                refresh: jsx.react_refresh,
328                import_source: ResolvedVc::cell(jsx.import_source.clone()),
329                runtime: ResolvedVc::cell(jsx.runtime.clone()),
330            });
331        }
332
333        let ecmascript_options = EcmascriptOptions {
334            follow_reexports,
335            module_fragments_enabled,
336            url_rewrite_behavior: esm_url_rewrite_behavior,
337            import_externals,
338            ignore_dynamic_requests,
339            extract_source_map: matches!(ecmascript_source_maps, SourceMapsType::Full),
340            keep_last_successful_parse,
341            analyze_mode,
342            enable_typeof_window_inlining,
343            enable_exports_info_inlining,
344            inline_helpers,
345            infer_module_side_effects,
346            cjs_tree_shaking,
347            mangle_export_names,
348            cjs_scope_hoisting,
349            cross_module_constants,
350            ..Default::default()
351        };
352        let ecmascript_options_vc = ecmascript_options.resolved_cell();
353
354        if let Some(environment) = environment {
355            let env_config = match preset_env_config {
356                Some(c) => *c,
357                None => PresetEnvConfig::default().resolved_cell(),
358            };
359            postprocess.push(EcmascriptInputTransform::PresetEnv(environment, env_config));
360        }
361
362        let decorators_transform = if let Some(options) = &enable_decorators {
363            let options = options.await?;
364            options
365                .decorators_kind
366                .as_ref()
367                .map(|kind| EcmascriptInputTransform::Decorators {
368                    is_legacy: kind == &DecoratorsKind::Legacy,
369                    is_ecma: kind == &DecoratorsKind::Ecma,
370                    emit_decorators_metadata: options.emit_decorators_metadata,
371                    use_define_for_class_fields: options.use_define_for_class_fields,
372                })
373        } else {
374            None
375        };
376
377        // Snapshot before decorators so the TypeScript chain also includes e.g. ReactCompilerRust.
378        let extra_preprocess = ecma_preprocess.clone();
379
380        if let Some(decorators_transform) = &decorators_transform {
381            // Apply decorators transform for the ModuleType::Ecmascript as well after
382            // constructing ts_app_transforms. Ecmascript can have decorators for
383            // the cases of 1. using jsconfig, to enable ts-specific runtime
384            // decorators (i.e legacy) 2. ecma spec decorators
385            //
386            // Since typescript transform (`ts_app_transforms`) needs to apply decorators
387            // _before_ stripping types, we create ts_app_transforms first in a
388            // specific order with typescript, then apply decorators to app_transforms.
389            //
390            // Append so ReactCompilerRust (needs original source text) runs before decorators.
391            ecma_preprocess.push(decorators_transform.clone());
392        }
393
394        let ecma_preprocess = ResolvedVc::cell(ecma_preprocess);
395        let main = ResolvedVc::<EcmascriptInputTransforms>::cell(vec![]);
396        let postprocess = ResolvedVc::cell(postprocess);
397        let empty = ResolvedVc::<EcmascriptInputTransforms>::cell(vec![]);
398
399        let mut rules = vec![];
400
401        // In tracing mode, we only need to record file dependencies — not transform them.
402        // Source transforms rename the file identity (e.g., foo.json -> foo.json.[json].cjs),
403        // which produces virtual paths that don't exist on disk. This breaks NFT file tracing
404        // and standalone build file copying. Use Raw module type instead so the original
405        // filesystem path is preserved in the trace.
406        let is_tracing = analyze_mode == AnalyzeMode::Tracing;
407
408        // Import attribute rules (bytes/text) must come BEFORE config rules.
409        // Import attributes have a stronger API contract - they're explicit in the source code
410        // and should override any file-pattern-based config rules.
411        if enable_import_as_bytes {
412            rules.push(ModuleRule::new(
413                RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
414                    EcmaScriptModulesReferenceSubType::ImportWithType("bytes".into()),
415                ))),
416                if is_tracing {
417                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
418                } else {
419                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
420                        ResolvedVc::upcast(BytesSourceTransform::new().to_resolved().await?),
421                    ]))]
422                },
423            ));
424        }
425
426        rules.push(ModuleRule::new(
427            RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
428                EcmaScriptModulesReferenceSubType::ImportWithType("text".into()),
429            ))),
430            if is_tracing {
431                vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
432            } else {
433                vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
434                    ResolvedVc::upcast(TextSourceTransform::new().to_resolved().await?),
435                ]))]
436            },
437        ));
438
439        if let Some(webpack_loaders_options) = enable_webpack_loaders {
440            let webpack_loaders_options = webpack_loaders_options.await?;
441            let execution_context =
442                execution_context.context("execution_context is required for webpack_loaders")?;
443            let import_map = if let Some(loader_runner_package) =
444                webpack_loaders_options.loader_runner_package
445            {
446                package_import_map_from_import_mapping(
447                    rcstr!("loader-runner"),
448                    *loader_runner_package,
449                )
450            } else {
451                package_import_map_from_context(
452                    rcstr!("loader-runner"),
453                    path.clone()
454                        .context("need_path in ModuleOptions::new is incorrect")?,
455                )
456            };
457            let builtin_conditions = webpack_loaders_options
458                .builtin_conditions
459                .into_trait_ref()
460                .await?;
461            for (key, rule) in webpack_loaders_options.rules.await?.iter() {
462                let mut rule_conditions = Vec::new();
463
464                // prefer to add the glob condition ahead of the user-defined `condition` field,
465                // because we know it's cheap to check
466                rule_conditions.push(
467                    rule_condition_from_webpack_condition_glob(execution_context, key).await?,
468                );
469
470                if let Some(condition) = &rule.condition {
471                    rule_conditions.push(
472                        rule_condition_from_webpack_condition(
473                            execution_context,
474                            &*builtin_conditions,
475                            condition,
476                        )
477                        .await?,
478                    )
479                }
480
481                rule_conditions.push(RuleCondition::not(RuleCondition::ResourceIsVirtualSource));
482                rule_conditions.push(module_css_external_transform_conditions.clone());
483
484                let mut all_rule_condition = RuleCondition::All(rule_conditions);
485                all_rule_condition.flatten();
486                if !matches!(all_rule_condition, RuleCondition::False) {
487                    let mut effects = Vec::new();
488
489                    // Add source transforms if loaders are specified
490                    if !rule.loaders.await?.is_empty() {
491                        effects.push(ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
492                            ResolvedVc::upcast(
493                                WebpackLoaders::new(
494                                    node_evaluate_asset_context(
495                                        *execution_context,
496                                        Some(import_map),
497                                        None,
498                                        Layer::new(rcstr!("webpack_loaders")),
499                                        false,
500                                    ),
501                                    *execution_context,
502                                    *rule.loaders,
503                                    *webpack_loaders_options.target,
504                                    rule.rename_as.clone(),
505                                    resolve_options_context,
506                                    matches!(ecmascript_source_maps, SourceMapsType::Full),
507                                )
508                                .to_resolved()
509                                .await?,
510                            ),
511                        ])));
512                    }
513
514                    // Add module type if specified
515                    if let Some(type_str) = rule.module_type.as_ref() {
516                        effects.push(
517                            ConfiguredModuleType::parse(type_str)?
518                                .into_effect(
519                                    ecma_preprocess,
520                                    main,
521                                    postprocess,
522                                    ecmascript_options_vc,
523                                    environment,
524                                    lightningcss_features,
525                                )
526                                .await?,
527                        )
528                    }
529
530                    if !effects.is_empty() {
531                        rules.push(ModuleRule::new(all_rule_condition, effects));
532                    }
533                }
534            }
535        }
536
537        rules.extend(module_rules.iter().cloned());
538
539        if enable_mdx || enable_mdx_rs.is_some() {
540            let (jsx_runtime, jsx_import_source, development) = if let Some(enable_jsx) = enable_jsx
541            {
542                let jsx = enable_jsx.await?;
543                (
544                    jsx.runtime.clone(),
545                    jsx.import_source.clone(),
546                    jsx.development,
547                )
548            } else {
549                (None, None, false)
550            };
551
552            let mdx_options = &*enable_mdx_rs
553                .unwrap_or_else(|| MdxTransformOptions::default().resolved_cell())
554                .await?;
555
556            let mdx_transform_options = (MdxTransformOptions {
557                development: Some(development),
558                jsx: Some(false),
559                jsx_runtime,
560                jsx_import_source,
561                ..(mdx_options.clone())
562            })
563            .cell();
564
565            rules.push(ModuleRule::new(
566                RuleCondition::any(vec![
567                    RuleCondition::ResourcePathEndsWith(".md".to_string()),
568                    RuleCondition::ResourcePathEndsWith(".mdx".to_string()),
569                    RuleCondition::ContentTypeStartsWith("text/markdown".to_string()),
570                ]),
571                vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
572                    ResolvedVc::upcast(
573                        MdxTransform::new(mdx_transform_options)
574                            .to_resolved()
575                            .await?,
576                    ),
577                ]))],
578            ));
579        }
580
581        // Rules that apply for certains references
582        rules.extend([
583            ModuleRule::new(
584                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
585                    UrlReferenceSubType::CssUrl,
586                ))),
587                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlCss {
588                    tag: static_url_tag.clone(),
589                })],
590            ),
591            ModuleRule::new(
592                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
593                    UrlReferenceSubType::Undefined,
594                ))),
595                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
596                    tag: static_url_tag.clone(),
597                })],
598            ),
599            ModuleRule::new(
600                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
601                    UrlReferenceSubType::EcmaScriptNewUrl,
602                ))),
603                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
604                    tag: static_url_tag.clone(),
605                })],
606            ),
607            ModuleRule::new(
608                RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
609                    EcmaScriptModulesReferenceSubType::ImportWithType("json".into()),
610                ))),
611                if is_tracing {
612                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
613                } else {
614                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
615                        // Use spec-compliant ESM for import attributes
616                        ResolvedVc::upcast(JsonSourceTransform::new_esm().to_resolved().await?),
617                    ]))]
618                },
619            ),
620        ]);
621
622        // Rules that apply based on file extension or content type
623        rules.extend([
624            ModuleRule::new_all(
625                RuleCondition::any(vec![
626                    RuleCondition::ResourcePathEndsWith(".json".to_string()),
627                    RuleCondition::ContentTypeStartsWith("application/json".to_string()),
628                ]),
629                if is_tracing {
630                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
631                } else {
632                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
633                        // For backcompat with webpack we generate a cjs style export
634                        ResolvedVc::upcast(JsonSourceTransform::new_cjs().to_resolved().await?),
635                    ]))]
636                },
637            ),
638            ModuleRule::new_all(
639                RuleCondition::any(vec![
640                    RuleCondition::ResourcePathEndsWith(".js".to_string()),
641                    RuleCondition::ResourcePathEndsWith(".jsx".to_string()),
642                    RuleCondition::ContentTypeStartsWith("application/javascript".to_string()),
643                    RuleCondition::ContentTypeStartsWith("text/javascript".to_string()),
644                ]),
645                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
646                    preprocess: ecma_preprocess,
647                    main,
648                    postprocess,
649                    options: ecmascript_options_vc,
650                })],
651            ),
652            ModuleRule::new_all(
653                RuleCondition::ResourcePathEndsWith(".mjs".to_string()),
654                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
655                    preprocess: ecma_preprocess,
656                    main,
657                    postprocess,
658                    options: EcmascriptOptions {
659                        specified_module_type: SpecifiedModuleType::EcmaScript,
660                        ..ecmascript_options
661                    }
662                    .resolved_cell(),
663                })],
664            ),
665            ModuleRule::new_all(
666                RuleCondition::ResourcePathEndsWith(".cjs".to_string()),
667                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
668                    preprocess: ecma_preprocess,
669                    main,
670                    postprocess,
671                    options: EcmascriptOptions {
672                        specified_module_type: SpecifiedModuleType::CommonJs,
673                        ..ecmascript_options
674                    }
675                    .resolved_cell(),
676                })],
677            ),
678            ModuleRule::new(
679                RuleCondition::ResourcePathEndsWith(".d.ts".to_string()),
680                vec![ModuleRuleEffect::ModuleType(
681                    ModuleType::TypescriptDeclaration {
682                        preprocess: empty,
683                        main: empty,
684                        postprocess: empty,
685                        options: ecmascript_options_vc,
686                    },
687                )],
688            ),
689            ModuleRule::new(
690                RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
691                    ".node".to_string(),
692                )]),
693                vec![ModuleRuleEffect::ModuleType(ModuleType::NodeAddon)],
694            ),
695            // WebAssembly
696            // In tracing mode these are `Raw` modules: a WebAssembly module is loaded through a
697            // generated JS loader that references an embedded runtime helper, which is compiled
698            // into the output and has no path on disk, so tracing it would produce a file
699            // reference that cannot be resolved to a real file.
700            ModuleRule::new(
701                RuleCondition::any(vec![
702                    RuleCondition::ResourcePathEndsWith(".wasm".to_string()),
703                    RuleCondition::ContentTypeStartsWith("application/wasm".to_string()),
704                ]),
705                if is_tracing {
706                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
707                } else {
708                    vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
709                        source_ty: WebAssemblySourceType::Binary,
710                    })]
711                },
712            ),
713            ModuleRule::new(
714                RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
715                    ".wat".to_string(),
716                )]),
717                if is_tracing {
718                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
719                } else {
720                    vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
721                        source_ty: WebAssemblySourceType::Text,
722                    })]
723                },
724            ),
725            ModuleRule::new(
726                RuleCondition::any(vec![
727                    RuleCondition::ResourcePathEndsWith(".apng".to_string()),
728                    RuleCondition::ResourcePathEndsWith(".avif".to_string()),
729                    RuleCondition::ResourcePathEndsWith(".gif".to_string()),
730                    RuleCondition::ResourcePathEndsWith(".ico".to_string()),
731                    RuleCondition::ResourcePathEndsWith(".jpg".to_string()),
732                    RuleCondition::ResourcePathEndsWith(".jpeg".to_string()),
733                    RuleCondition::ResourcePathEndsWith(".png".to_string()),
734                    RuleCondition::ResourcePathEndsWith(".svg".to_string()),
735                    RuleCondition::ResourcePathEndsWith(".webp".to_string()),
736                    RuleCondition::ResourcePathEndsWith(".woff2".to_string()),
737                ]),
738                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
739                    tag: static_url_tag.clone(),
740                })],
741            ),
742            ModuleRule::new(
743                RuleCondition::all(vec![
744                    // Fallback to ecmascript without extension (this is node.js behavior)
745                    RuleCondition::ResourcePathHasNoExtension,
746                    RuleCondition::ContentTypeEmpty,
747                ]),
748                vec![ModuleRuleEffect::ModuleType(
749                    ModuleType::EcmascriptExtensionless {
750                        preprocess: empty,
751                        main: empty,
752                        postprocess: empty,
753                        options: ecmascript_options_vc,
754                    },
755                )],
756            ),
757        ]);
758
759        if let Some(options) = enable_typescript_transform {
760            let options = options.await?;
761            // Prepend extra_preprocess (e.g. ReactCompilerRust) so it runs before decorators and
762            // TypeScript.
763            let ts_preprocess = ResolvedVc::cell(
764                extra_preprocess
765                    .into_iter()
766                    .chain(decorators_transform.clone())
767                    .chain(std::iter::once(EcmascriptInputTransform::TypeScript {
768                        use_define_for_class_fields: options.use_define_for_class_fields,
769                        verbatim_module_syntax: options.verbatim_module_syntax,
770                    }))
771                    .collect(),
772            );
773
774            rules.extend([
775                ModuleRule::new_all(
776                    RuleCondition::ResourcePathEndsWith(".ts".to_string()),
777                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
778                        preprocess: ts_preprocess,
779                        main,
780                        postprocess,
781                        tsx: false,
782                        analyze_types: enable_types,
783                        options: ecmascript_options_vc,
784                    })],
785                ),
786                ModuleRule::new_all(
787                    RuleCondition::ResourcePathEndsWith(".tsx".to_string()),
788                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
789                        preprocess: ts_preprocess,
790                        main,
791                        postprocess,
792                        tsx: true,
793                        analyze_types: enable_types,
794                        options: ecmascript_options_vc,
795                    })],
796                ),
797                ModuleRule::new_all(
798                    RuleCondition::ResourcePathEndsWith(".mts".to_string()),
799                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
800                        preprocess: ts_preprocess,
801                        main,
802                        postprocess,
803                        tsx: false,
804                        analyze_types: enable_types,
805                        options: EcmascriptOptions {
806                            specified_module_type: SpecifiedModuleType::EcmaScript,
807                            ..ecmascript_options
808                        }
809                        .resolved_cell(),
810                    })],
811                ),
812                ModuleRule::new_all(
813                    RuleCondition::ResourcePathEndsWith(".mtsx".to_string()),
814                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
815                        preprocess: ts_preprocess,
816                        main,
817                        postprocess,
818                        tsx: true,
819                        analyze_types: enable_types,
820                        options: EcmascriptOptions {
821                            specified_module_type: SpecifiedModuleType::EcmaScript,
822                            ..ecmascript_options
823                        }
824                        .resolved_cell(),
825                    })],
826                ),
827                ModuleRule::new_all(
828                    RuleCondition::ResourcePathEndsWith(".cts".to_string()),
829                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
830                        preprocess: ts_preprocess,
831                        main,
832                        postprocess,
833                        tsx: false,
834                        analyze_types: enable_types,
835                        options: EcmascriptOptions {
836                            specified_module_type: SpecifiedModuleType::CommonJs,
837                            ..ecmascript_options
838                        }
839                        .resolved_cell(),
840                    })],
841                ),
842                ModuleRule::new_all(
843                    RuleCondition::ResourcePathEndsWith(".ctsx".to_string()),
844                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
845                        preprocess: ts_preprocess,
846                        main,
847                        postprocess,
848                        tsx: true,
849                        analyze_types: enable_types,
850                        options: EcmascriptOptions {
851                            specified_module_type: SpecifiedModuleType::CommonJs,
852                            ..ecmascript_options
853                        }
854                        .resolved_cell(),
855                    })],
856                ),
857            ]);
858        }
859
860        if enable_raw_css {
861            rules.extend([
862                ModuleRule::new(
863                    module_css_condition.clone(),
864                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
865                        ty: CssModuleType::Module,
866                        environment,
867                        lightningcss_features,
868                        module_css_debuggable_idents,
869                    })],
870                ),
871                ModuleRule::new(
872                    RuleCondition::any(vec![
873                        RuleCondition::ResourcePathEndsWith(".css".to_string()),
874                        RuleCondition::ContentTypeStartsWith("text/css".to_string()),
875                    ]),
876                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
877                        ty: CssModuleType::Default,
878                        environment,
879                        lightningcss_features,
880                        module_css_debuggable_idents,
881                    })],
882                ),
883            ]);
884        } else {
885            if let Some(options) = enable_postcss_transform {
886                let options = options.await?;
887                let execution_context = execution_context
888                    .context("execution_context is required for the postcss_transform")?;
889
890                let import_map = if let Some(postcss_package) = options.postcss_package {
891                    package_import_map_from_import_mapping(rcstr!("postcss"), *postcss_package)
892                } else {
893                    package_import_map_from_context(
894                        rcstr!("postcss"),
895                        path.clone()
896                            .context("need_path in ModuleOptions::new is incorrect")?,
897                    )
898                };
899
900                rules.push(ModuleRule::new(
901                    RuleCondition::All(vec![
902                        RuleCondition::Any(vec![
903                            // Both CSS and CSS Modules
904                            RuleCondition::ResourcePathEndsWith(".css".to_string()),
905                            RuleCondition::ContentTypeStartsWith("text/css".to_string()),
906                            module_css_condition.clone(),
907                        ]),
908                        module_css_external_transform_conditions.clone(),
909                    ]),
910                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
911                        ResolvedVc::upcast(
912                            PostCssTransform::new(
913                                node_evaluate_asset_context(
914                                    *execution_context,
915                                    Some(import_map),
916                                    None,
917                                    Layer::new(rcstr!("postcss")),
918                                    true,
919                                ),
920                                config_tracing_module_context(*execution_context),
921                                *execution_context,
922                                options.config_location,
923                                matches!(css_source_maps, SourceMapsType::Full),
924                            )
925                            .to_resolved()
926                            .await?,
927                        ),
928                    ]))],
929                ));
930            }
931
932            rules.extend([
933                ModuleRule::new(
934                    RuleCondition::all(vec![
935                        module_css_condition.clone(),
936                        // Create a normal CSS asset if `@import`ed from CSS already.
937                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
938                            CssReferenceSubType::AtImport(None),
939                        ))),
940                    ]),
941                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
942                        ty: CssModuleType::Module,
943                        environment,
944                        lightningcss_features,
945                        module_css_debuggable_idents,
946                    })],
947                ),
948                // Ecmascript CSS Modules referencing the actual CSS module to include it
949                ModuleRule::new(
950                    RuleCondition::all(vec![
951                        module_css_condition.clone(),
952                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
953                            CssReferenceSubType::Inner,
954                        ))),
955                    ]),
956                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
957                        ty: CssModuleType::Module,
958                        environment,
959                        lightningcss_features,
960                        module_css_debuggable_idents,
961                    })],
962                ),
963                // Ecmascript CSS Modules referencing the actual CSS module to list the classes
964                ModuleRule::new(
965                    RuleCondition::all(vec![
966                        module_css_condition.clone(),
967                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
968                            CssReferenceSubType::Analyze,
969                        ))),
970                    ]),
971                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
972                        ty: CssModuleType::Module,
973                        environment,
974                        lightningcss_features,
975                        module_css_debuggable_idents,
976                    })],
977                ),
978                ModuleRule::new(
979                    RuleCondition::all(vec![module_css_condition.clone()]),
980                    vec![ModuleRuleEffect::ModuleType(ModuleType::CssModule)],
981                ),
982                ModuleRule::new_all(
983                    RuleCondition::Any(vec![
984                        RuleCondition::ResourcePathEndsWith(".css".to_string()),
985                        RuleCondition::ContentTypeStartsWith("text/css".to_string()),
986                    ]),
987                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
988                        ty: CssModuleType::Default,
989                        environment,
990                        lightningcss_features,
991                        module_css_debuggable_idents,
992                    })],
993                ),
994            ]);
995        }
996
997        Ok(ModuleOptions::cell(ModuleOptions { rules }))
998    }
999}