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