Skip to main content

turbopack/module_options/
mod.rs

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