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