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