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