Skip to main content

turbopack/module_options/
module_rule.rs

1use std::fmt::Display;
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use turbo_rcstr::RcStr;
6use turbo_tasks::{NonLocalValue, ResolvedVc};
7use turbo_tasks_fs::FileSystemPath;
8use turbopack_core::{
9    environment::Environment, reference_type::ReferenceType, source::Source,
10    source_transform::SourceTransforms,
11};
12use turbopack_css::CssModuleType;
13use turbopack_ecmascript::{
14    EcmascriptInputTransforms, EcmascriptOptions, bytes_source_transform::BytesSourceTransform,
15    json_source_transform::JsonSourceTransform, text_source_transform::TextSourceTransform,
16};
17use turbopack_wasm::source::WebAssemblySourceType;
18
19use crate::module_options::{CustomModuleType, RuleCondition, match_mode::MatchMode};
20
21#[derive(Debug, Clone, PartialEq, Eq, NonLocalValue, Encode, Decode)]
22pub struct ModuleRule {
23    condition: RuleCondition,
24    effects: Vec<ModuleRuleEffect>,
25    match_mode: MatchMode,
26}
27
28impl ModuleRule {
29    /// Creates a new module rule. Will not match internal references.
30    pub fn new(mut condition: RuleCondition, effects: Vec<ModuleRuleEffect>) -> Self {
31        condition.flatten();
32        ModuleRule {
33            condition,
34            effects,
35            match_mode: MatchMode::NonInternal,
36        }
37    }
38
39    /// Creates a new module rule. Will only match internal references.
40    pub fn new_internal(mut condition: RuleCondition, effects: Vec<ModuleRuleEffect>) -> Self {
41        condition.flatten();
42        ModuleRule {
43            condition,
44            effects,
45            match_mode: MatchMode::Internal,
46        }
47    }
48
49    /// Creates a new module rule. Will match all references.
50    pub fn new_all(mut condition: RuleCondition, effects: Vec<ModuleRuleEffect>) -> Self {
51        condition.flatten();
52        ModuleRule {
53            condition,
54            effects,
55            match_mode: MatchMode::All,
56        }
57    }
58
59    pub fn effects(&self) -> impl Iterator<Item = &ModuleRuleEffect> {
60        self.effects.iter()
61    }
62
63    pub async fn matches(
64        &self,
65        source: ResolvedVc<Box<dyn Source>>,
66        path: &FileSystemPath,
67        reference_type: &ReferenceType,
68    ) -> Result<bool> {
69        Ok(self.match_mode.matches(reference_type)
70            && self.condition.matches(source, path, reference_type).await?)
71    }
72}
73
74#[turbo_tasks::value(shared)]
75#[derive(Debug, Clone)]
76pub enum ModuleRuleEffect {
77    ModuleType(ModuleType),
78    /// Allow to extend an existing Ecmascript module rules for the additional
79    /// transforms
80    ExtendEcmascriptTransforms {
81        /// Transforms to run first: transpile TypeScript, decorators, ...
82        preprocess: ResolvedVc<EcmascriptInputTransforms>,
83        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
84        main: ResolvedVc<EcmascriptInputTransforms>,
85        /// Transforms to run last: JSX, preset-env, scan for imports, ...
86        postprocess: ResolvedVc<EcmascriptInputTransforms>,
87    },
88    SourceTransforms(ResolvedVc<SourceTransforms>),
89    Ignore,
90}
91
92#[turbo_tasks::value(shared)]
93#[derive(Hash, Debug, Clone)]
94pub enum ModuleType {
95    Ecmascript {
96        /// Transforms to run first: transpile TypeScript, decorators, ...
97        preprocess: ResolvedVc<EcmascriptInputTransforms>,
98        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
99        main: ResolvedVc<EcmascriptInputTransforms>,
100        /// Transforms to run last: JSX, preset-env, scan for imports, ...
101        postprocess: ResolvedVc<EcmascriptInputTransforms>,
102        options: ResolvedVc<EcmascriptOptions>,
103    },
104    Typescript {
105        /// Transforms to run first: transpile TypeScript, decorators, ...
106        preprocess: ResolvedVc<EcmascriptInputTransforms>,
107        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
108        main: ResolvedVc<EcmascriptInputTransforms>,
109        /// Transforms to run last: JSX, preset-env, scan for imports, ...
110        postprocess: ResolvedVc<EcmascriptInputTransforms>,
111        // parse JSX syntax.
112        tsx: bool,
113        // follow references to imported types.
114        analyze_types: bool,
115        options: ResolvedVc<EcmascriptOptions>,
116    },
117    TypescriptDeclaration {
118        /// Transforms to run first: transpile TypeScript, decorators, ...
119        preprocess: ResolvedVc<EcmascriptInputTransforms>,
120        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
121        main: ResolvedVc<EcmascriptInputTransforms>,
122        /// Transforms to run last: JSX, preset-env, scan for imports, ...
123        postprocess: ResolvedVc<EcmascriptInputTransforms>,
124        options: ResolvedVc<EcmascriptOptions>,
125    },
126    EcmascriptExtensionless {
127        /// Transforms to run first: transpile TypeScript, decorators, ...
128        preprocess: ResolvedVc<EcmascriptInputTransforms>,
129        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
130        main: ResolvedVc<EcmascriptInputTransforms>,
131        /// Transforms to run last: JSX, preset-env, scan for imports, ...
132        postprocess: ResolvedVc<EcmascriptInputTransforms>,
133        options: ResolvedVc<EcmascriptOptions>,
134    },
135    Raw,
136    NodeAddon,
137    CssModule,
138    Css {
139        ty: CssModuleType,
140        environment: Option<ResolvedVc<Environment>>,
141        lightningcss_features: turbopack_css::LightningCssFeatureFlags,
142        module_css_debuggable_idents: bool,
143    },
144    StaticUrlJs {
145        /// The tag that is passed to ChunkingContext::asset_url
146        tag: Option<RcStr>,
147    },
148    StaticUrlCss {
149        /// The tag that is passed to ChunkingContext::asset_url
150        tag: Option<RcStr>,
151    },
152    WebAssembly {
153        source_ty: WebAssemblySourceType,
154    },
155    Custom(ResolvedVc<Box<dyn CustomModuleType>>),
156}
157
158impl Display for ModuleType {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        match self {
161            ModuleType::Ecmascript { .. } => write!(f, "Ecmascript"),
162            ModuleType::Typescript { .. } => write!(f, "Typescript"),
163            ModuleType::TypescriptDeclaration { .. } => write!(f, "TypescriptDeclaration"),
164            ModuleType::EcmascriptExtensionless { .. } => write!(f, "EcmascriptExtensionless"),
165            ModuleType::Raw => write!(f, "Raw"),
166            ModuleType::NodeAddon => write!(f, "NodeAddon"),
167            ModuleType::CssModule => write!(f, "CssModule"),
168            ModuleType::Css { .. } => write!(f, "Css"),
169            ModuleType::StaticUrlJs { .. } => write!(f, "StaticUrlJs"),
170            ModuleType::StaticUrlCss { .. } => write!(f, "StaticUrlCss"),
171            ModuleType::WebAssembly { .. } => write!(f, "WebAssembly"),
172            ModuleType::Custom(_) => write!(f, "Custom"),
173        }
174    }
175}
176
177/// User-facing module type names used in configuration.
178///
179/// This enum represents the semantic module types that users can specify in their config
180/// (e.g., next.config.js turbopack rules). Some of these map directly to internal `ModuleType`
181/// variants, while others (like `Bytes`) are implemented via source transforms.
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub enum ConfiguredModuleType {
184    Asset,
185    Ecmascript,
186    Typescript,
187    Css,
188    CssModule,
189    /// Parses JSON and exports it as an ES module default export.
190    /// Implemented as a source transform, not a ModuleType.
191    Json,
192    Wasm,
193    /// An alias of [`ConfiguredModuleType::Text`].
194    Raw,
195    Node,
196    /// Converts any file to an ES module exporting its contents as a Uint8Array.
197    /// Implemented as a source transform, not a ModuleType.
198    Bytes,
199    /// Converts any file to an ES module exporting its contents as a string.
200    /// Implemented as a source transform, not a ModuleType.
201    ///
202    /// `Raw` is an alias of this.
203    Text,
204}
205
206impl ConfiguredModuleType {
207    /// Parse a module type string from user configuration.
208    pub fn parse(type_str: &str) -> Result<Self> {
209        Ok(match type_str {
210            "asset" => ConfiguredModuleType::Asset,
211            "ecmascript" => ConfiguredModuleType::Ecmascript,
212            "typescript" => ConfiguredModuleType::Typescript,
213            "css" => ConfiguredModuleType::Css,
214            "css-module" => ConfiguredModuleType::CssModule,
215            "json" => ConfiguredModuleType::Json,
216            "wasm" => ConfiguredModuleType::Wasm,
217            "raw" => ConfiguredModuleType::Raw,
218            "node" => ConfiguredModuleType::Node,
219            "bytes" => ConfiguredModuleType::Bytes,
220            "text" => ConfiguredModuleType::Text,
221            _ => bail!(
222                "Unknown module type: {type_str:?}. Valid types are: asset, ecmascript, \
223                 typescript, css, css-module, json, wasm, raw, node, bytes, text"
224            ),
225        })
226    }
227
228    /// Convert this configured module type into module rule effects.
229    ///
230    /// Some module types (like `Bytes`) are implemented as source transforms rather than
231    /// `ModuleType` variants, allowing them to compose with the standard Ecmascript pipeline.
232    pub async fn into_effect(
233        self,
234        preprocess: ResolvedVc<EcmascriptInputTransforms>,
235        main: ResolvedVc<EcmascriptInputTransforms>,
236        postprocess: ResolvedVc<EcmascriptInputTransforms>,
237        options: ResolvedVc<EcmascriptOptions>,
238        environment: Option<ResolvedVc<Environment>>,
239        lightningcss_features: turbopack_css::LightningCssFeatureFlags,
240    ) -> Result<ModuleRuleEffect> {
241        Ok(match self {
242            ConfiguredModuleType::Bytes => {
243                // Use source transform instead of ModuleType - the transform produces .mjs
244                // which gets picked up by the standard Ecmascript rules
245                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
246                    BytesSourceTransform::new().to_resolved().await?,
247                )]))
248            }
249            // `raw` has always been documented as returning the contents as a string, so it
250            // is an alias of `text` rather than a way to get an opaque module.
251            ConfiguredModuleType::Text | ConfiguredModuleType::Raw => {
252                // Same as `Bytes`: a source transform that produces .mjs, which is then
253                // picked up by the standard Ecmascript rules.
254                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
255                    TextSourceTransform::new().to_resolved().await?,
256                )]))
257            }
258            ConfiguredModuleType::Asset => {
259                ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs { tag: None })
260            }
261            ConfiguredModuleType::Ecmascript => {
262                ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
263                    preprocess,
264                    main,
265                    postprocess,
266                    options,
267                })
268            }
269            ConfiguredModuleType::Typescript => {
270                ModuleRuleEffect::ModuleType(ModuleType::Typescript {
271                    preprocess,
272                    main,
273                    postprocess,
274                    tsx: false,
275                    analyze_types: false,
276                    options,
277                })
278            }
279            ConfiguredModuleType::Css => ModuleRuleEffect::ModuleType(ModuleType::Css {
280                ty: CssModuleType::Default,
281                environment,
282                lightningcss_features,
283                // This is global CSS, so the CSS Module naming pattern is unused.
284                module_css_debuggable_idents: false,
285            }),
286            ConfiguredModuleType::CssModule => ModuleRuleEffect::ModuleType(ModuleType::CssModule),
287            ConfiguredModuleType::Json => {
288                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
289                    // TODO: can we switch this to `new_esm`?
290                    JsonSourceTransform::new_cjs().to_resolved().await?,
291                )]))
292            }
293            ConfiguredModuleType::Wasm => ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
294                source_ty: WebAssemblySourceType::Binary,
295            }),
296            ConfiguredModuleType::Node => ModuleRuleEffect::ModuleType(ModuleType::NodeAddon),
297        })
298    }
299}