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