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, trace::TraceRawVcs};
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, TraceRawVcs, 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        #[turbo_tasks(trace_ignore)]
103        options: ResolvedVc<EcmascriptOptions>,
104    },
105    Typescript {
106        /// Transforms to run first: transpile TypeScript, decorators, ...
107        preprocess: ResolvedVc<EcmascriptInputTransforms>,
108        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
109        main: ResolvedVc<EcmascriptInputTransforms>,
110        /// Transforms to run last: JSX, preset-env, scan for imports, ...
111        postprocess: ResolvedVc<EcmascriptInputTransforms>,
112        // parse JSX syntax.
113        tsx: bool,
114        // follow references to imported types.
115        analyze_types: bool,
116        #[turbo_tasks(trace_ignore)]
117        options: ResolvedVc<EcmascriptOptions>,
118    },
119    TypescriptDeclaration {
120        /// Transforms to run first: transpile TypeScript, decorators, ...
121        preprocess: ResolvedVc<EcmascriptInputTransforms>,
122        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
123        main: ResolvedVc<EcmascriptInputTransforms>,
124        /// Transforms to run last: JSX, preset-env, scan for imports, ...
125        postprocess: ResolvedVc<EcmascriptInputTransforms>,
126        #[turbo_tasks(trace_ignore)]
127        options: ResolvedVc<EcmascriptOptions>,
128    },
129    EcmascriptExtensionless {
130        /// Transforms to run first: transpile TypeScript, decorators, ...
131        preprocess: ResolvedVc<EcmascriptInputTransforms>,
132        /// Transforms to execute on standard EcmaScript (plus JSX): styled-jsx, swc plugins, ...
133        main: ResolvedVc<EcmascriptInputTransforms>,
134        /// Transforms to run last: JSX, preset-env, scan for imports, ...
135        postprocess: ResolvedVc<EcmascriptInputTransforms>,
136        #[turbo_tasks(trace_ignore)]
137        options: ResolvedVc<EcmascriptOptions>,
138    },
139    Raw,
140    NodeAddon,
141    CssModule,
142    Css {
143        ty: CssModuleType,
144        environment: Option<ResolvedVc<Environment>>,
145        lightningcss_features: turbopack_css::LightningCssFeatureFlags,
146        module_css_debuggable_idents: bool,
147    },
148    StaticUrlJs {
149        /// The tag that is passed to ChunkingContext::asset_url
150        tag: Option<RcStr>,
151    },
152    StaticUrlCss {
153        /// The tag that is passed to ChunkingContext::asset_url
154        tag: Option<RcStr>,
155    },
156    WebAssembly {
157        source_ty: WebAssemblySourceType,
158    },
159    Custom(ResolvedVc<Box<dyn CustomModuleType>>),
160}
161
162impl Display for ModuleType {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            ModuleType::Ecmascript { .. } => write!(f, "Ecmascript"),
166            ModuleType::Typescript { .. } => write!(f, "Typescript"),
167            ModuleType::TypescriptDeclaration { .. } => write!(f, "TypescriptDeclaration"),
168            ModuleType::EcmascriptExtensionless { .. } => write!(f, "EcmascriptExtensionless"),
169            ModuleType::Raw => write!(f, "Raw"),
170            ModuleType::NodeAddon => write!(f, "NodeAddon"),
171            ModuleType::CssModule => write!(f, "CssModule"),
172            ModuleType::Css { .. } => write!(f, "Css"),
173            ModuleType::StaticUrlJs { .. } => write!(f, "StaticUrlJs"),
174            ModuleType::StaticUrlCss { .. } => write!(f, "StaticUrlCss"),
175            ModuleType::WebAssembly { .. } => write!(f, "WebAssembly"),
176            ModuleType::Custom(_) => write!(f, "Custom"),
177        }
178    }
179}
180
181/// User-facing module type names used in configuration.
182///
183/// This enum represents the semantic module types that users can specify in their config
184/// (e.g., next.config.js turbopack rules). Some of these map directly to internal `ModuleType`
185/// variants, while others (like `Bytes`) are implemented via source transforms.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub enum ConfiguredModuleType {
188    Asset,
189    Ecmascript,
190    Typescript,
191    Css,
192    CssModule,
193    /// Parses JSON and exports it as an ES module default export.
194    /// Implemented as a source transform, not a ModuleType.
195    Json,
196    Wasm,
197    /// An alias of [`ConfiguredModuleType::Text`].
198    Raw,
199    Node,
200    /// Converts any file to an ES module exporting its contents as a Uint8Array.
201    /// Implemented as a source transform, not a ModuleType.
202    Bytes,
203    /// Converts any file to an ES module exporting its contents as a string.
204    /// Implemented as a source transform, not a ModuleType.
205    ///
206    /// `Raw` is an alias of this.
207    Text,
208}
209
210impl ConfiguredModuleType {
211    /// Parse a module type string from user configuration.
212    pub fn parse(type_str: &str) -> Result<Self> {
213        Ok(match type_str {
214            "asset" => ConfiguredModuleType::Asset,
215            "ecmascript" => ConfiguredModuleType::Ecmascript,
216            "typescript" => ConfiguredModuleType::Typescript,
217            "css" => ConfiguredModuleType::Css,
218            "css-module" => ConfiguredModuleType::CssModule,
219            "json" => ConfiguredModuleType::Json,
220            "wasm" => ConfiguredModuleType::Wasm,
221            "raw" => ConfiguredModuleType::Raw,
222            "node" => ConfiguredModuleType::Node,
223            "bytes" => ConfiguredModuleType::Bytes,
224            "text" => ConfiguredModuleType::Text,
225            _ => bail!(
226                "Unknown module type: {type_str:?}. Valid types are: asset, ecmascript, \
227                 typescript, css, css-module, json, wasm, raw, node, bytes, text"
228            ),
229        })
230    }
231
232    /// Convert this configured module type into module rule effects.
233    ///
234    /// Some module types (like `Bytes`) are implemented as source transforms rather than
235    /// `ModuleType` variants, allowing them to compose with the standard Ecmascript pipeline.
236    pub async fn into_effect(
237        self,
238        preprocess: ResolvedVc<EcmascriptInputTransforms>,
239        main: ResolvedVc<EcmascriptInputTransforms>,
240        postprocess: ResolvedVc<EcmascriptInputTransforms>,
241        options: ResolvedVc<EcmascriptOptions>,
242        environment: Option<ResolvedVc<Environment>>,
243        lightningcss_features: turbopack_css::LightningCssFeatureFlags,
244    ) -> Result<ModuleRuleEffect> {
245        Ok(match self {
246            ConfiguredModuleType::Bytes => {
247                // Use source transform instead of ModuleType - the transform produces .mjs
248                // which gets picked up by the standard Ecmascript rules
249                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
250                    BytesSourceTransform::new().to_resolved().await?,
251                )]))
252            }
253            // `raw` has always been documented as returning the contents as a string, so it
254            // is an alias of `text` rather than a way to get an opaque module.
255            ConfiguredModuleType::Text | ConfiguredModuleType::Raw => {
256                // Same as `Bytes`: a source transform that produces .mjs, which is then
257                // picked up by the standard Ecmascript rules.
258                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
259                    TextSourceTransform::new().to_resolved().await?,
260                )]))
261            }
262            ConfiguredModuleType::Asset => {
263                ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs { tag: None })
264            }
265            ConfiguredModuleType::Ecmascript => {
266                ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
267                    preprocess,
268                    main,
269                    postprocess,
270                    options,
271                })
272            }
273            ConfiguredModuleType::Typescript => {
274                ModuleRuleEffect::ModuleType(ModuleType::Typescript {
275                    preprocess,
276                    main,
277                    postprocess,
278                    tsx: false,
279                    analyze_types: false,
280                    options,
281                })
282            }
283            ConfiguredModuleType::Css => ModuleRuleEffect::ModuleType(ModuleType::Css {
284                ty: CssModuleType::Default,
285                environment,
286                lightningcss_features,
287                // This is global CSS, so the CSS Module naming pattern is unused.
288                module_css_debuggable_idents: false,
289            }),
290            ConfiguredModuleType::CssModule => ModuleRuleEffect::ModuleType(ModuleType::CssModule),
291            ConfiguredModuleType::Json => {
292                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
293                    // TODO: can we switch this to `new_esm`?
294                    JsonSourceTransform::new_cjs().to_resolved().await?,
295                )]))
296            }
297            ConfiguredModuleType::Wasm => ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
298                source_ty: WebAssemblySourceType::Binary,
299            }),
300            ConfiguredModuleType::Node => ModuleRuleEffect::ModuleType(ModuleType::NodeAddon),
301        })
302    }
303}