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,
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    },
147    StaticUrlJs {
148        /// The tag that is passed to ChunkingContext::asset_url
149        tag: Option<RcStr>,
150    },
151    StaticUrlCss {
152        /// The tag that is passed to ChunkingContext::asset_url
153        tag: Option<RcStr>,
154    },
155    WebAssembly {
156        source_ty: WebAssemblySourceType,
157    },
158    Custom(ResolvedVc<Box<dyn CustomModuleType>>),
159}
160
161impl Display for ModuleType {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            ModuleType::Ecmascript { .. } => write!(f, "Ecmascript"),
165            ModuleType::Typescript { .. } => write!(f, "Typescript"),
166            ModuleType::TypescriptDeclaration { .. } => write!(f, "TypescriptDeclaration"),
167            ModuleType::EcmascriptExtensionless { .. } => write!(f, "EcmascriptExtensionless"),
168            ModuleType::Raw => write!(f, "Raw"),
169            ModuleType::NodeAddon => write!(f, "NodeAddon"),
170            ModuleType::CssModule => write!(f, "CssModule"),
171            ModuleType::Css { .. } => write!(f, "Css"),
172            ModuleType::StaticUrlJs { .. } => write!(f, "StaticUrlJs"),
173            ModuleType::StaticUrlCss { .. } => write!(f, "StaticUrlCss"),
174            ModuleType::WebAssembly { .. } => write!(f, "WebAssembly"),
175            ModuleType::Custom(_) => write!(f, "Custom"),
176        }
177    }
178}
179
180/// User-facing module type names used in configuration.
181///
182/// This enum represents the semantic module types that users can specify in their config
183/// (e.g., next.config.js turbopack rules). Some of these map directly to internal `ModuleType`
184/// variants, while others (like `Bytes`) are implemented via source transforms.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum ConfiguredModuleType {
187    Asset,
188    Ecmascript,
189    Typescript,
190    Css,
191    CssModule,
192    /// Parses JSON and exports it as an ES module default export.
193    /// Implemented as a source transform, not a ModuleType.
194    Json,
195    Wasm,
196    Raw,
197    Node,
198    /// Converts any file to an ES module exporting its contents as a Uint8Array.
199    /// Implemented as a source transform, not a ModuleType.
200    Bytes,
201}
202
203impl ConfiguredModuleType {
204    /// Parse a module type string from user configuration.
205    pub fn parse(type_str: &str) -> Result<Self> {
206        Ok(match type_str {
207            "asset" => ConfiguredModuleType::Asset,
208            "ecmascript" => ConfiguredModuleType::Ecmascript,
209            "typescript" => ConfiguredModuleType::Typescript,
210            "css" => ConfiguredModuleType::Css,
211            "css-module" => ConfiguredModuleType::CssModule,
212            "json" => ConfiguredModuleType::Json,
213            "wasm" => ConfiguredModuleType::Wasm,
214            "raw" => ConfiguredModuleType::Raw,
215            "node" => ConfiguredModuleType::Node,
216            "bytes" => ConfiguredModuleType::Bytes,
217            _ => bail!(
218                "Unknown module type: {type_str:?}. Valid types are: asset, ecmascript, \
219                 typescript, css, css-module, json, wasm, raw, node, bytes"
220            ),
221        })
222    }
223
224    /// Convert this configured module type into module rule effects.
225    ///
226    /// Some module types (like `Bytes`) are implemented as source transforms rather than
227    /// `ModuleType` variants, allowing them to compose with the standard Ecmascript pipeline.
228    pub async fn into_effect(
229        self,
230        preprocess: ResolvedVc<EcmascriptInputTransforms>,
231        main: ResolvedVc<EcmascriptInputTransforms>,
232        postprocess: ResolvedVc<EcmascriptInputTransforms>,
233        options: ResolvedVc<EcmascriptOptions>,
234        environment: Option<ResolvedVc<Environment>>,
235        lightningcss_features: turbopack_css::LightningCssFeatureFlags,
236    ) -> Result<ModuleRuleEffect> {
237        Ok(match self {
238            ConfiguredModuleType::Bytes => {
239                // Use source transform instead of ModuleType - the transform produces .mjs
240                // which gets picked up by the standard Ecmascript rules
241                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
242                    BytesSourceTransform::new().to_resolved().await?,
243                )]))
244            }
245            ConfiguredModuleType::Asset => {
246                ModuleRuleEffect::ModuleType(ModuleType::StaticUrlJs { tag: None })
247            }
248            ConfiguredModuleType::Ecmascript => {
249                ModuleRuleEffect::ModuleType(ModuleType::Ecmascript {
250                    preprocess,
251                    main,
252                    postprocess,
253                    options,
254                })
255            }
256            ConfiguredModuleType::Typescript => {
257                ModuleRuleEffect::ModuleType(ModuleType::Typescript {
258                    preprocess,
259                    main,
260                    postprocess,
261                    tsx: false,
262                    analyze_types: false,
263                    options,
264                })
265            }
266            ConfiguredModuleType::Css => ModuleRuleEffect::ModuleType(ModuleType::Css {
267                ty: CssModuleType::Default,
268                environment,
269                lightningcss_features,
270            }),
271            ConfiguredModuleType::CssModule => ModuleRuleEffect::ModuleType(ModuleType::CssModule),
272            ConfiguredModuleType::Json => {
273                ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![ResolvedVc::upcast(
274                    // TODO: can we switch this to `new_esm`?
275                    JsonSourceTransform::new_cjs().to_resolved().await?,
276                )]))
277            }
278            ConfiguredModuleType::Wasm => ModuleRuleEffect::ModuleType(ModuleType::WebAssembly {
279                source_ty: WebAssemblySourceType::Binary,
280            }),
281            ConfiguredModuleType::Raw => ModuleRuleEffect::ModuleType(ModuleType::Raw),
282            ConfiguredModuleType::Node => ModuleRuleEffect::ModuleType(ModuleType::NodeAddon),
283        })
284    }
285}