turbopack/module_options/
module_options_context.rs

1use std::fmt::Debug;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use turbo_esregex::EsRegex;
6use turbo_rcstr::{RcStr, rcstr};
7use turbo_tasks::{NonLocalValue, ResolvedVc, ValueDefault, Vc, trace::TraceRawVcs};
8use turbo_tasks_fs::{
9    FileSystemPath,
10    glob::{Glob, GlobOptions},
11};
12use turbopack_core::{
13    chunk::SourceMapsType, compile_time_info::CompileTimeInfo, condition::ContextCondition,
14    environment::Environment, resolve::options::ImportMapping,
15};
16use turbopack_ecmascript::{
17    AnalyzeMode, TreeShakingMode, TypeofWindow, references::esm::UrlRewriteBehavior,
18};
19pub use turbopack_mdx::MdxTransformOptions;
20use turbopack_node::{
21    execution_context::ExecutionContext,
22    transforms::{postcss::PostCssTransformOptions, webpack::WebpackLoaderItems},
23};
24
25use super::ModuleRule;
26use crate::module_options::RuleCondition;
27
28#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
29pub struct LoaderRuleItem {
30    pub loaders: ResolvedVc<WebpackLoaderItems>,
31    pub rename_as: Option<RcStr>,
32    pub condition: Option<ConditionItem>,
33    pub module_type: Option<RcStr>,
34}
35
36/// This is a list of instructions for the rule engine to process. The first element in each tuple
37/// is a glob to match against, and the second is a rule to execute if that glob matches.
38///
39/// This is not a map, since multiple rules can be configured for the same glob, and since execution
40/// order matters.
41#[derive(Default)]
42#[turbo_tasks::value(transparent)]
43pub struct WebpackRules(Vec<(RcStr, LoaderRuleItem)>);
44
45#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
46pub enum ConditionPath {
47    Glob(RcStr),
48    Regex(ResolvedVc<EsRegex>),
49}
50
51#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
52pub enum ConditionQuery {
53    Constant(RcStr),
54    Regex(ResolvedVc<EsRegex>),
55}
56
57#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
58pub enum ConditionContentType {
59    Glob(RcStr),
60    Regex(ResolvedVc<EsRegex>),
61}
62
63#[turbo_tasks::value(shared)]
64#[derive(Clone, Debug)]
65pub enum ConditionItem {
66    All(Box<[ConditionItem]>),
67    Any(Box<[ConditionItem]>),
68    Not(Box<ConditionItem>),
69    Builtin(RcStr),
70    Base {
71        path: Option<ConditionPath>,
72        content: Option<ResolvedVc<EsRegex>>,
73        query: Option<ConditionQuery>,
74        content_type: Option<ConditionContentType>,
75    },
76}
77
78#[turbo_tasks::value(shared)]
79#[derive(Clone, Debug)]
80pub struct WebpackLoadersOptions {
81    pub rules: ResolvedVc<WebpackRules>,
82    pub builtin_conditions: ResolvedVc<Box<dyn WebpackLoaderBuiltinConditionSet>>,
83    pub loader_runner_package: Option<ResolvedVc<ImportMapping>>,
84}
85
86pub enum WebpackLoaderBuiltinConditionSetMatch {
87    Matched,
88    Unmatched,
89    /// The given condition is not supported by the framework.
90    Invalid,
91}
92
93/// A collection of framework-provided conditions for user (or framework) specified loader rules
94/// ([`WebpackRules`]) to match against.
95#[turbo_tasks::value_trait]
96pub trait WebpackLoaderBuiltinConditionSet {
97    /// Determines if the string representation of this condition is in the set. If it's not valid,
98    /// an issue will be emitted as a collectible.
99    fn match_condition(&self, condition: &str) -> WebpackLoaderBuiltinConditionSetMatch;
100}
101
102/// A no-op implementation of `WebpackLoaderBuiltinConditionSet` that always returns
103/// `WebpackLoaderBuiltinConditionSetMatch::Invalid`.
104#[turbo_tasks::value]
105pub struct EmptyWebpackLoaderBuiltinConditionSet;
106
107#[turbo_tasks::value_impl]
108impl EmptyWebpackLoaderBuiltinConditionSet {
109    #[turbo_tasks::function]
110    fn new() -> Vc<Box<dyn WebpackLoaderBuiltinConditionSet>> {
111        Vc::upcast::<Box<dyn WebpackLoaderBuiltinConditionSet>>(
112            EmptyWebpackLoaderBuiltinConditionSet.cell(),
113        )
114    }
115}
116
117#[turbo_tasks::value_impl]
118impl WebpackLoaderBuiltinConditionSet for EmptyWebpackLoaderBuiltinConditionSet {
119    fn match_condition(&self, _condition: &str) -> WebpackLoaderBuiltinConditionSetMatch {
120        WebpackLoaderBuiltinConditionSetMatch::Invalid
121    }
122}
123
124/// The kind of decorators transform to use.
125/// [TODO]: might need bikeshed for the name (Ecma)
126#[derive(Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
127pub enum DecoratorsKind {
128    Legacy,
129    Ecma,
130}
131
132/// Configuration options for the decorators transform.
133///
134/// This is not part of Typescript transform: while there are typescript
135/// specific transforms (legay decorators), there is an ecma decorator transform
136/// as well for the JS.
137#[turbo_tasks::value(shared)]
138#[derive(Default, Clone, Debug)]
139pub struct DecoratorsOptions {
140    pub decorators_kind: Option<DecoratorsKind>,
141    /// Option to control whether to emit decorator metadata.
142    /// (https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata)
143    /// This'll be applied only if `decorators_type` and
144    /// `enable_typescript_transform` is enabled.
145    pub emit_decorators_metadata: bool,
146    /// Mimic babel's `decorators.decoratorsBeforeExport` option.
147    /// This'll be applied only if `decorators_type` is enabled.
148    /// ref: https://github.com/swc-project/swc/blob/d4ebb5e6efbed0758f25e46e8f74d7c47ec6cb8f/crates/swc_ecma_parser/src/lib.rs#L327
149    /// [TODO]: this option is not actively being used currently.
150    pub decorators_before_export: bool,
151    pub use_define_for_class_fields: bool,
152}
153
154#[turbo_tasks::value_impl]
155impl ValueDefault for DecoratorsOptions {
156    #[turbo_tasks::function]
157    fn value_default() -> Vc<Self> {
158        Self::default().cell()
159    }
160}
161
162/// Subset of Typescript options configured via tsconfig.json or jsconfig.json,
163/// which affects the runtime transform output.
164#[turbo_tasks::value(shared)]
165#[derive(Default, Clone, Debug)]
166pub struct TypescriptTransformOptions {
167    pub use_define_for_class_fields: bool,
168}
169
170#[turbo_tasks::value_impl]
171impl ValueDefault for TypescriptTransformOptions {
172    #[turbo_tasks::function]
173    fn value_default() -> Vc<Self> {
174        Self::default().cell()
175    }
176}
177
178#[turbo_tasks::value(shared)]
179#[derive(Default, Clone, Debug)]
180pub struct JsxTransformOptions {
181    pub development: bool,
182    pub react_refresh: bool,
183    pub import_source: Option<RcStr>,
184    pub runtime: Option<RcStr>,
185}
186
187#[turbo_tasks::value(shared)]
188#[derive(Clone, Debug)]
189pub struct ExternalsTracingOptions {
190    /// The directory from which the bundled files will require the externals at runtime.
191    pub tracing_root: FileSystemPath,
192    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
193}
194
195#[turbo_tasks::value(shared)]
196#[derive(Clone, Default)]
197pub struct ModuleOptionsContext {
198    pub ecmascript: EcmascriptOptionsContext,
199    pub css: CssOptionsContext,
200
201    pub enable_postcss_transform: Option<ResolvedVc<PostCssTransformOptions>>,
202    pub enable_webpack_loaders: Option<ResolvedVc<WebpackLoadersOptions>>,
203    // [Note]: currently mdx, and mdx_rs have different configuration entrypoint from next.config.js,
204    // however we might want to unify them in the future.
205    pub enable_mdx: bool,
206    pub enable_mdx_rs: Option<ResolvedVc<MdxTransformOptions>>,
207
208    pub environment: Option<ResolvedVc<Environment>>,
209    pub execution_context: Option<ResolvedVc<ExecutionContext>>,
210    pub side_effect_free_packages: Option<ResolvedVc<Glob>>,
211    pub tree_shaking_mode: Option<TreeShakingMode>,
212
213    pub static_url_tag: Option<RcStr>,
214
215    /// Generate (non-emitted) output assets for static assets and externals, to facilitate
216    /// generating a list of all non-bundled files that will be required at runtime.
217    pub enable_externals_tracing: Option<ResolvedVc<ExternalsTracingOptions>>,
218
219    /// If true, it stores the last successful parse result in state and keeps using it when
220    /// parsing fails. This is useful to keep the module graph structure intact when syntax errors
221    /// are temporarily introduced.
222    pub keep_last_successful_parse: bool,
223
224    /// Custom rules to be applied after all default rules.
225    pub module_rules: Vec<ModuleRule>,
226    /// A list of rules to use a different module option context for certain
227    /// context paths. The first matching is used.
228    pub rules: Vec<(ContextCondition, ResolvedVc<ModuleOptionsContext>)>,
229
230    /// Whether the modules in this context are never chunked/codegen-ed, but only used for
231    /// tracing.
232    pub analyze_mode: AnalyzeMode,
233
234    pub placeholder_for_future_extensions: (),
235}
236
237#[turbo_tasks::value(shared)]
238#[derive(Clone, Default)]
239pub struct EcmascriptOptionsContext {
240    // TODO this should just be handled via CompileTimeInfo FreeVarReferences, but then it
241    // (currently) wouldn't be possible to have different replacement values in user code vs
242    // node_modules.
243    pub enable_typeof_window_inlining: Option<TypeofWindow>,
244    pub enable_jsx: Option<ResolvedVc<JsxTransformOptions>>,
245    /// Follow type references and resolve declaration files in additional to
246    /// normal resolution.
247    pub enable_types: bool,
248    pub enable_typescript_transform: Option<ResolvedVc<TypescriptTransformOptions>>,
249    pub enable_decorators: Option<ResolvedVc<DecoratorsOptions>>,
250    pub esm_url_rewrite_behavior: Option<UrlRewriteBehavior>,
251    /// References to externals from ESM imports should use `import()` and make
252    /// async modules.
253    pub import_externals: bool,
254    /// Ignore very dynamic requests which doesn't have any static known part.
255    /// If false, they will reference the whole directory. If true, they won't
256    /// reference anything and lead to an runtime error instead.
257    pub ignore_dynamic_requests: bool,
258    /// Specifies how Source Maps are handled.
259    pub source_maps: SourceMapsType,
260
261    /// Whether to allow accessing exports info via `__webpack_exports_info__`.
262    pub enable_exports_info_inlining: bool,
263
264    /// Whether to enable `import bytes from 'module' as { type: "bytes }` syntax.
265    pub enable_import_as_bytes: bool,
266
267    // TODO should this be a part of Environment instead?
268    pub inline_helpers: bool,
269
270    /// Whether to infer side effect free modules via local analysis. Defaults to true.
271    pub infer_module_side_effects: bool,
272
273    pub placeholder_for_future_extensions: (),
274}
275
276#[turbo_tasks::value(shared)]
277#[derive(Clone, Default)]
278pub struct CssOptionsContext {
279    /// This skips `GlobalCss` and `ModuleCss` module assets from being
280    /// generated in the module graph, generating only `Css` module assets.
281    ///
282    /// This is useful for node-file-trace, which tries to emit all assets in
283    /// the module graph, but neither asset types can be emitted directly.
284    pub enable_raw_css: bool,
285
286    /// Specifies how Source Maps are handled.
287    pub source_maps: SourceMapsType,
288
289    /// Override the conditions for module CSS (doesn't have any effect if `enable_raw_css` is
290    /// true). By default (for `None`), it uses
291    /// `Any(ResourcePathEndsWith(".module.css"), ContentTypeStartsWith("text/css+module"))`
292    pub module_css_condition: Option<RuleCondition>,
293
294    pub placeholder_for_future_extensions: (),
295}
296
297#[turbo_tasks::value_impl]
298impl ValueDefault for ModuleOptionsContext {
299    #[turbo_tasks::function]
300    fn value_default() -> Vc<Self> {
301        Self::cell(Default::default())
302    }
303}
304
305#[turbo_tasks::function]
306pub async fn side_effect_free_packages_glob(
307    side_effect_free_packages: ResolvedVc<Vec<RcStr>>,
308) -> Result<Vc<Glob>> {
309    let side_effect_free_packages = &*side_effect_free_packages.await?;
310    if side_effect_free_packages.is_empty() {
311        return Ok(Glob::new(rcstr!(""), GlobOptions::default()));
312    }
313
314    let mut globs = String::new();
315    globs.push_str("**/node_modules/{");
316    globs.push_str(&side_effect_free_packages.join(","));
317    globs.push_str("}/**");
318
319    Ok(Glob::new(globs.into(), GlobOptions::default()))
320}