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                    ref preset_env_config,
250                    ..
251                },
252            enable_mdx,
253            enable_mdx_rs,
254            css:
255                CssOptionsContext {
256                    enable_raw_css,
257                    source_maps: css_source_maps,
258                    ref module_css_condition,
259                    lightningcss_features,
260                    ..
261                },
262            ref static_url_tag,
263            ref enable_postcss_transform,
264            ref enable_webpack_loaders,
265            environment,
266            ref module_rules,
267            execution_context,
268            tree_shaking_mode,
269            keep_last_successful_parse,
270            analyze_mode,
271            ..
272        } = *module_options_context.await?;
273
274        let module_css_condition = module_css_condition.clone().unwrap_or_else(|| {
275            RuleCondition::any(vec![
276                RuleCondition::ResourcePathEndsWith(".module.css".to_string()),
277                RuleCondition::ContentTypeStartsWith("text/css+module".to_string()),
278            ])
279        });
280
281        // For React Client References, the CSS Module "facade" module lives in the parent (server)
282        // module context, but the facade's references should be transitioned to the client (and
283        // only then be processed with Webpack/PostCSS).
284        //
285        // Note that this is not an exhaustive condition for PostCSS/Webpack, but excludes certain
286        // cases, so it should be added conjunctively together with CSS Module rule.
287        //
288        // If module css, then only when (Inner or Analyze or Compose)
289        // <=> (not (module css)) or (Inner or Analyzer or Compose)
290        //
291        // So only if this is not a CSS module, or one of the special reference type constraints.
292        let module_css_external_transform_conditions = RuleCondition::Any(vec![
293            RuleCondition::not(module_css_condition.clone()),
294            RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
295                CssReferenceSubType::Inner,
296            ))),
297            RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
298                CssReferenceSubType::Analyze,
299            ))),
300        ]);
301
302        let mut ecma_preprocess = vec![];
303        let mut postprocess = vec![];
304
305        if let Some(compilation_mode) = enable_rust_react_compiler {
306            ecma_preprocess.push(EcmascriptInputTransform::ReactCompilerRust {
307                compilation_mode,
308                target: rust_react_compiler_target,
309            });
310        }
311
312        // Order of transforms is important. e.g. if the React transform occurs before
313        // Styled JSX, there won't be JSX nodes for Styled JSX to transform.
314        // If a custom plugin requires specific order _before_ core transform kicks in,
315        // should use `before_transform_plugins`.
316        if let Some(enable_jsx) = enable_jsx {
317            let jsx = enable_jsx.await?;
318
319            postprocess.push(EcmascriptInputTransform::React {
320                development: jsx.development,
321                refresh: jsx.react_refresh,
322                import_source: ResolvedVc::cell(jsx.import_source.clone()),
323                runtime: ResolvedVc::cell(jsx.runtime.clone()),
324            });
325        }
326
327        let ecmascript_options = EcmascriptOptions {
328            tree_shaking_mode,
329            url_rewrite_behavior: esm_url_rewrite_behavior,
330            import_externals,
331            ignore_dynamic_requests,
332            extract_source_map: matches!(ecmascript_source_maps, SourceMapsType::Full),
333            keep_last_successful_parse,
334            analyze_mode,
335            enable_typeof_window_inlining,
336            enable_exports_info_inlining,
337            inline_helpers,
338            infer_module_side_effects,
339            ..Default::default()
340        };
341        let ecmascript_options_vc = ecmascript_options.resolved_cell();
342
343        if let Some(environment) = environment {
344            let env_config = match preset_env_config {
345                Some(c) => *c,
346                None => PresetEnvConfig::default().resolved_cell(),
347            };
348            postprocess.push(EcmascriptInputTransform::PresetEnv(environment, env_config));
349        }
350
351        let decorators_transform = if let Some(options) = &enable_decorators {
352            let options = options.await?;
353            options
354                .decorators_kind
355                .as_ref()
356                .map(|kind| EcmascriptInputTransform::Decorators {
357                    is_legacy: kind == &DecoratorsKind::Legacy,
358                    is_ecma: kind == &DecoratorsKind::Ecma,
359                    emit_decorators_metadata: options.emit_decorators_metadata,
360                    use_define_for_class_fields: options.use_define_for_class_fields,
361                })
362        } else {
363            None
364        };
365
366        // Snapshot before decorators so the TypeScript chain also includes e.g. ReactCompilerRust.
367        let extra_preprocess = ecma_preprocess.clone();
368
369        if let Some(decorators_transform) = &decorators_transform {
370            // Apply decorators transform for the ModuleType::Ecmascript as well after
371            // constructing ts_app_transforms. Ecmascript can have decorators for
372            // the cases of 1. using jsconfig, to enable ts-specific runtime
373            // decorators (i.e legacy) 2. ecma spec decorators
374            //
375            // Since typescript transform (`ts_app_transforms`) needs to apply decorators
376            // _before_ stripping types, we create ts_app_transforms first in a
377            // specific order with typescript, then apply decorators to app_transforms.
378            //
379            // Append so ReactCompilerRust (needs original source text) runs before decorators.
380            ecma_preprocess.push(decorators_transform.clone());
381        }
382
383        let ecma_preprocess = ResolvedVc::cell(ecma_preprocess);
384        let main = ResolvedVc::<EcmascriptInputTransforms>::cell(vec![]);
385        let postprocess = ResolvedVc::cell(postprocess);
386        let empty = ResolvedVc::<EcmascriptInputTransforms>::cell(vec![]);
387
388        let mut rules = vec![];
389
390        // In tracing mode, we only need to record file dependencies — not transform them.
391        // Source transforms rename the file identity (e.g., foo.json -> foo.json.[json].cjs),
392        // which produces virtual paths that don't exist on disk. This breaks NFT file tracing
393        // and standalone build file copying. Use Raw module type instead so the original
394        // filesystem path is preserved in the trace.
395        let is_tracing = analyze_mode == AnalyzeMode::Tracing;
396
397        // Import attribute rules (bytes/text) must come BEFORE config rules.
398        // Import attributes have a stronger API contract - they're explicit in the source code
399        // and should override any file-pattern-based config rules.
400        if enable_import_as_bytes {
401            rules.push(ModuleRule::new(
402                RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
403                    EcmaScriptModulesReferenceSubType::ImportWithType("bytes".into()),
404                ))),
405                if is_tracing {
406                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
407                } else {
408                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
409                        ResolvedVc::upcast(BytesSourceTransform::new().to_resolved().await?),
410                    ]))]
411                },
412            ));
413        }
414
415        rules.push(ModuleRule::new(
416            RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
417                EcmaScriptModulesReferenceSubType::ImportWithType("text".into()),
418            ))),
419            if is_tracing {
420                vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
421            } else {
422                vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
423                    ResolvedVc::upcast(TextSourceTransform::new().to_resolved().await?),
424                ]))]
425            },
426        ));
427
428        if let Some(webpack_loaders_options) = enable_webpack_loaders {
429            let webpack_loaders_options = webpack_loaders_options.await?;
430            let execution_context =
431                execution_context.context("execution_context is required for webpack_loaders")?;
432            let import_map = if let Some(loader_runner_package) =
433                webpack_loaders_options.loader_runner_package
434            {
435                package_import_map_from_import_mapping(
436                    rcstr!("loader-runner"),
437                    *loader_runner_package,
438                )
439            } else {
440                package_import_map_from_context(
441                    rcstr!("loader-runner"),
442                    path.clone()
443                        .context("need_path in ModuleOptions::new is incorrect")?,
444                )
445            };
446            let builtin_conditions = webpack_loaders_options
447                .builtin_conditions
448                .into_trait_ref()
449                .await?;
450            for (key, rule) in webpack_loaders_options.rules.await?.iter() {
451                let mut rule_conditions = Vec::new();
452
453                // prefer to add the glob condition ahead of the user-defined `condition` field,
454                // because we know it's cheap to check
455                rule_conditions.push(
456                    rule_condition_from_webpack_condition_glob(execution_context, key).await?,
457                );
458
459                if let Some(condition) = &rule.condition {
460                    rule_conditions.push(
461                        rule_condition_from_webpack_condition(
462                            execution_context,
463                            &*builtin_conditions,
464                            condition,
465                        )
466                        .await?,
467                    )
468                }
469
470                rule_conditions.push(RuleCondition::not(RuleCondition::ResourceIsVirtualSource));
471                rule_conditions.push(module_css_external_transform_conditions.clone());
472
473                let mut all_rule_condition = RuleCondition::All(rule_conditions);
474                all_rule_condition.flatten();
475                if !matches!(all_rule_condition, RuleCondition::False) {
476                    let mut effects = Vec::new();
477
478                    // Add source transforms if loaders are specified
479                    if !rule.loaders.await?.is_empty() {
480                        effects.push(ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
481                            ResolvedVc::upcast(
482                                WebpackLoaders::new(
483                                    node_evaluate_asset_context(
484                                        *execution_context,
485                                        Some(import_map),
486                                        None,
487                                        Layer::new(rcstr!("webpack_loaders")),
488                                        false,
489                                    ),
490                                    *execution_context,
491                                    *rule.loaders,
492                                    rule.rename_as.clone(),
493                                    resolve_options_context,
494                                    matches!(ecmascript_source_maps, SourceMapsType::Full),
495                                )
496                                .to_resolved()
497                                .await?,
498                            ),
499                        ])));
500                    }
501
502                    // Add module type if specified
503                    if let Some(type_str) = rule.module_type.as_ref() {
504                        effects.push(
505                            ConfiguredModuleType::parse(type_str)?
506                                .into_effect(
507                                    ecma_preprocess,
508                                    main,
509                                    postprocess,
510                                    ecmascript_options_vc,
511                                    environment,
512                                    lightningcss_features,
513                                )
514                                .await?,
515                        )
516                    }
517
518                    if !effects.is_empty() {
519                        rules.push(ModuleRule::new(all_rule_condition, effects));
520                    }
521                }
522            }
523        }
524
525        rules.extend(module_rules.iter().cloned());
526
527        if enable_mdx || enable_mdx_rs.is_some() {
528            let (jsx_runtime, jsx_import_source, development) = if let Some(enable_jsx) = enable_jsx
529            {
530                let jsx = enable_jsx.await?;
531                (
532                    jsx.runtime.clone(),
533                    jsx.import_source.clone(),
534                    jsx.development,
535                )
536            } else {
537                (None, None, false)
538            };
539
540            let mdx_options = &*enable_mdx_rs
541                .unwrap_or_else(|| MdxTransformOptions::default().resolved_cell())
542                .await?;
543
544            let mdx_transform_options = (MdxTransformOptions {
545                development: Some(development),
546                jsx: Some(false),
547                jsx_runtime,
548                jsx_import_source,
549                ..(mdx_options.clone())
550            })
551            .cell();
552
553            rules.push(ModuleRule::new(
554                RuleCondition::any(vec![
555                    RuleCondition::ResourcePathEndsWith(".md".to_string()),
556                    RuleCondition::ResourcePathEndsWith(".mdx".to_string()),
557                    RuleCondition::ContentTypeStartsWith("text/markdown".to_string()),
558                ]),
559                vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
560                    ResolvedVc::upcast(
561                        MdxTransform::new(mdx_transform_options)
562                            .to_resolved()
563                            .await?,
564                    ),
565                ]))],
566            ));
567        }
568
569        // Rules that apply for certains references
570        rules.extend([
571            ModuleRule::new(
572                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
573                    UrlReferenceSubType::CssUrl,
574                ))),
575                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlCss {
576                    tag: static_url_tag.clone(),
577                })],
578            ),
579            ModuleRule::new(
580                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
581                    UrlReferenceSubType::Undefined,
582                ))),
583                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
584                    tag: static_url_tag.clone(),
585                })],
586            ),
587            ModuleRule::new(
588                RuleCondition::ReferenceType(ReferenceTypeCondition::Url(Some(
589                    UrlReferenceSubType::EcmaScriptNewUrl,
590                ))),
591                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
592                    tag: static_url_tag.clone(),
593                })],
594            ),
595            ModuleRule::new(
596                RuleCondition::ReferenceType(ReferenceTypeCondition::EcmaScriptModules(Some(
597                    EcmaScriptModulesReferenceSubType::ImportWithType("json".into()),
598                ))),
599                if is_tracing {
600                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
601                } else {
602                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
603                        // Use spec-compliant ESM for import attributes
604                        ResolvedVc::upcast(JsonSourceTransform::new_esm().to_resolved().await?),
605                    ]))]
606                },
607            ),
608        ]);
609
610        // Rules that apply based on file extension or content type
611        rules.extend([
612            ModuleRule::new_all(
613                RuleCondition::any(vec![
614                    RuleCondition::ResourcePathEndsWith(".json".to_string()),
615                    RuleCondition::ContentTypeStartsWith("application/json".to_string()),
616                ]),
617                if is_tracing {
618                    vec![ModuleRuleEffect::ModuleType(ModuleType::Raw)]
619                } else {
620                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
621                        // For backcompat with webpack we generate a cjs style export
622                        ResolvedVc::upcast(JsonSourceTransform::new_cjs().to_resolved().await?),
623                    ]))]
624                },
625            ),
626            ModuleRule::new_all(
627                RuleCondition::any(vec![
628                    RuleCondition::ResourcePathEndsWith(".js".to_string()),
629                    RuleCondition::ResourcePathEndsWith(".jsx".to_string()),
630                    RuleCondition::ContentTypeStartsWith("application/javascript".to_string()),
631                    RuleCondition::ContentTypeStartsWith("text/javascript".to_string()),
632                ]),
633                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
634                    preprocess: ecma_preprocess,
635                    main,
636                    postprocess,
637                    options: ecmascript_options_vc,
638                })],
639            ),
640            ModuleRule::new_all(
641                RuleCondition::ResourcePathEndsWith(".mjs".to_string()),
642                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
643                    preprocess: ecma_preprocess,
644                    main,
645                    postprocess,
646                    options: EcmascriptOptions {
647                        specified_module_type: SpecifiedModuleType::EcmaScript,
648                        ..ecmascript_options
649                    }
650                    .resolved_cell(),
651                })],
652            ),
653            ModuleRule::new_all(
654                RuleCondition::ResourcePathEndsWith(".cjs".to_string()),
655                vec![ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
656                    preprocess: ecma_preprocess,
657                    main,
658                    postprocess,
659                    options: EcmascriptOptions {
660                        specified_module_type: SpecifiedModuleType::CommonJs,
661                        ..ecmascript_options
662                    }
663                    .resolved_cell(),
664                })],
665            ),
666            ModuleRule::new(
667                RuleCondition::ResourcePathEndsWith(".d.ts".to_string()),
668                vec![ModuleRuleEffect::ModuleType(
669                    ModuleType::TypescriptDeclaration {
670                        preprocess: empty,
671                        main: empty,
672                        postprocess: empty,
673                        options: ecmascript_options_vc,
674                    },
675                )],
676            ),
677            ModuleRule::new(
678                RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
679                    ".node".to_string(),
680                )]),
681                vec![ModuleRuleEffect::ModuleType(ModuleType::NodeAddon)],
682            ),
683            // WebAssembly
684            ModuleRule::new(
685                RuleCondition::any(vec![
686                    RuleCondition::ResourcePathEndsWith(".wasm".to_string()),
687                    RuleCondition::ContentTypeStartsWith("application/wasm".to_string()),
688                ]),
689                vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
690                    source_ty: WebAssemblySourceType::Binary,
691                })],
692            ),
693            ModuleRule::new(
694                RuleCondition::any(vec![RuleCondition::ResourcePathEndsWith(
695                    ".wat".to_string(),
696                )]),
697                vec![ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
698                    source_ty: WebAssemblySourceType::Text,
699                })],
700            ),
701            ModuleRule::new(
702                RuleCondition::any(vec![
703                    RuleCondition::ResourcePathEndsWith(".apng".to_string()),
704                    RuleCondition::ResourcePathEndsWith(".avif".to_string()),
705                    RuleCondition::ResourcePathEndsWith(".gif".to_string()),
706                    RuleCondition::ResourcePathEndsWith(".ico".to_string()),
707                    RuleCondition::ResourcePathEndsWith(".jpg".to_string()),
708                    RuleCondition::ResourcePathEndsWith(".jpeg".to_string()),
709                    RuleCondition::ResourcePathEndsWith(".png".to_string()),
710                    RuleCondition::ResourcePathEndsWith(".svg".to_string()),
711                    RuleCondition::ResourcePathEndsWith(".webp".to_string()),
712                    RuleCondition::ResourcePathEndsWith(".woff2".to_string()),
713                ]),
714                vec![ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs {
715                    tag: static_url_tag.clone(),
716                })],
717            ),
718            ModuleRule::new(
719                RuleCondition::all(vec![
720                    // Fallback to ecmascript without extension (this is node.js behavior)
721                    RuleCondition::ResourcePathHasNoExtension,
722                    RuleCondition::ContentTypeEmpty,
723                ]),
724                vec![ModuleRuleEffect::ModuleType(
725                    ModuleType::EcmascriptExtensionless {
726                        preprocess: empty,
727                        main: empty,
728                        postprocess: empty,
729                        options: ecmascript_options_vc,
730                    },
731                )],
732            ),
733        ]);
734
735        if let Some(options) = enable_typescript_transform {
736            let options = options.await?;
737            // Prepend extra_preprocess (e.g. ReactCompilerRust) so it runs before decorators and
738            // TypeScript.
739            let ts_preprocess = ResolvedVc::cell(
740                extra_preprocess
741                    .into_iter()
742                    .chain(decorators_transform.clone())
743                    .chain(std::iter::once(EcmascriptInputTransform::TypeScript {
744                        use_define_for_class_fields: options.use_define_for_class_fields,
745                        verbatim_module_syntax: options.verbatim_module_syntax,
746                    }))
747                    .collect(),
748            );
749
750            rules.extend([
751                ModuleRule::new_all(
752                    RuleCondition::ResourcePathEndsWith(".ts".to_string()),
753                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
754                        preprocess: ts_preprocess,
755                        main,
756                        postprocess,
757                        tsx: false,
758                        analyze_types: enable_types,
759                        options: ecmascript_options_vc,
760                    })],
761                ),
762                ModuleRule::new_all(
763                    RuleCondition::ResourcePathEndsWith(".tsx".to_string()),
764                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
765                        preprocess: ts_preprocess,
766                        main,
767                        postprocess,
768                        tsx: true,
769                        analyze_types: enable_types,
770                        options: ecmascript_options_vc,
771                    })],
772                ),
773                ModuleRule::new_all(
774                    RuleCondition::ResourcePathEndsWith(".mts".to_string()),
775                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
776                        preprocess: ts_preprocess,
777                        main,
778                        postprocess,
779                        tsx: false,
780                        analyze_types: enable_types,
781                        options: EcmascriptOptions {
782                            specified_module_type: SpecifiedModuleType::EcmaScript,
783                            ..ecmascript_options
784                        }
785                        .resolved_cell(),
786                    })],
787                ),
788                ModuleRule::new_all(
789                    RuleCondition::ResourcePathEndsWith(".mtsx".to_string()),
790                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
791                        preprocess: ts_preprocess,
792                        main,
793                        postprocess,
794                        tsx: true,
795                        analyze_types: enable_types,
796                        options: EcmascriptOptions {
797                            specified_module_type: SpecifiedModuleType::EcmaScript,
798                            ..ecmascript_options
799                        }
800                        .resolved_cell(),
801                    })],
802                ),
803                ModuleRule::new_all(
804                    RuleCondition::ResourcePathEndsWith(".cts".to_string()),
805                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
806                        preprocess: ts_preprocess,
807                        main,
808                        postprocess,
809                        tsx: false,
810                        analyze_types: enable_types,
811                        options: EcmascriptOptions {
812                            specified_module_type: SpecifiedModuleType::CommonJs,
813                            ..ecmascript_options
814                        }
815                        .resolved_cell(),
816                    })],
817                ),
818                ModuleRule::new_all(
819                    RuleCondition::ResourcePathEndsWith(".ctsx".to_string()),
820                    vec![ModuleRuleEffect::ModuleType(ModuleType::Typescript {
821                        preprocess: ts_preprocess,
822                        main,
823                        postprocess,
824                        tsx: true,
825                        analyze_types: enable_types,
826                        options: EcmascriptOptions {
827                            specified_module_type: SpecifiedModuleType::CommonJs,
828                            ..ecmascript_options
829                        }
830                        .resolved_cell(),
831                    })],
832                ),
833            ]);
834        }
835
836        if enable_raw_css {
837            rules.extend([
838                ModuleRule::new(
839                    module_css_condition.clone(),
840                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
841                        ty: CssModuleType::Module,
842                        environment,
843                        lightningcss_features,
844                    })],
845                ),
846                ModuleRule::new(
847                    RuleCondition::any(vec![
848                        RuleCondition::ResourcePathEndsWith(".css".to_string()),
849                        RuleCondition::ContentTypeStartsWith("text/css".to_string()),
850                    ]),
851                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
852                        ty: CssModuleType::Default,
853                        environment,
854                        lightningcss_features,
855                    })],
856                ),
857            ]);
858        } else {
859            if let Some(options) = enable_postcss_transform {
860                let options = options.await?;
861                let execution_context = execution_context
862                    .context("execution_context is required for the postcss_transform")?;
863
864                let import_map = if let Some(postcss_package) = options.postcss_package {
865                    package_import_map_from_import_mapping(rcstr!("postcss"), *postcss_package)
866                } else {
867                    package_import_map_from_context(
868                        rcstr!("postcss"),
869                        path.clone()
870                            .context("need_path in ModuleOptions::new is incorrect")?,
871                    )
872                };
873
874                rules.push(ModuleRule::new(
875                    RuleCondition::All(vec![
876                        RuleCondition::Any(vec![
877                            // Both CSS and CSS Modules
878                            RuleCondition::ResourcePathEndsWith(".css".to_string()),
879                            RuleCondition::ContentTypeStartsWith("text/css".to_string()),
880                            module_css_condition.clone(),
881                        ]),
882                        module_css_external_transform_conditions.clone(),
883                    ]),
884                    vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
885                        ResolvedVc::upcast(
886                            PostCssTransform::new(
887                                node_evaluate_asset_context(
888                                    *execution_context,
889                                    Some(import_map),
890                                    None,
891                                    Layer::new(rcstr!("postcss")),
892                                    true,
893                                ),
894                                config_tracing_module_context(*execution_context),
895                                *execution_context,
896                                options.config_location,
897                                matches!(css_source_maps, SourceMapsType::Full),
898                            )
899                            .to_resolved()
900                            .await?,
901                        ),
902                    ]))],
903                ));
904            }
905
906            rules.extend([
907                ModuleRule::new(
908                    RuleCondition::all(vec![
909                        module_css_condition.clone(),
910                        // Create a normal CSS asset if `@import`ed from CSS already.
911                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
912                            CssReferenceSubType::AtImport(None),
913                        ))),
914                    ]),
915                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
916                        ty: CssModuleType::Module,
917                        environment,
918                        lightningcss_features,
919                    })],
920                ),
921                // Ecmascript CSS Modules referencing the actual CSS module to include it
922                ModuleRule::new(
923                    RuleCondition::all(vec![
924                        module_css_condition.clone(),
925                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
926                            CssReferenceSubType::Inner,
927                        ))),
928                    ]),
929                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
930                        ty: CssModuleType::Module,
931                        environment,
932                        lightningcss_features,
933                    })],
934                ),
935                // Ecmascript CSS Modules referencing the actual CSS module to list the classes
936                ModuleRule::new(
937                    RuleCondition::all(vec![
938                        module_css_condition.clone(),
939                        RuleCondition::ReferenceType(ReferenceTypeCondition::Css(Some(
940                            CssReferenceSubType::Analyze,
941                        ))),
942                    ]),
943                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
944                        ty: CssModuleType::Module,
945                        environment,
946                        lightningcss_features,
947                    })],
948                ),
949                ModuleRule::new(
950                    RuleCondition::all(vec![module_css_condition.clone()]),
951                    vec![ModuleRuleEffect::ModuleType(ModuleType::CssModule)],
952                ),
953                ModuleRule::new_all(
954                    RuleCondition::Any(vec![
955                        RuleCondition::ResourcePathEndsWith(".css".to_string()),
956                        RuleCondition::ContentTypeStartsWith("text/css".to_string()),
957                    ]),
958                    vec![ModuleRuleEffect::ModuleType(ModuleType::Css {
959                        ty: CssModuleType::Default,
960                        environment,
961                        lightningcss_features,
962                    })],
963                ),
964            ]);
965        }
966
967        Ok(ModuleOptions::cell(ModuleOptions { rules }))
968    }
969}