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