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