Skip to main content

next_core/
next_config.rs

1use anyhow::{Context, Result, bail};
2use async_trait::async_trait;
3use bincode::{Decode, Encode};
4use either::Either;
5use rustc_hash::FxHashSet;
6use serde::{Deserialize, Deserializer, Serialize};
7use serde_json::Value as JsonValue;
8use turbo_esregex::EsRegex;
9use turbo_rcstr::{RcStr, rcstr};
10use turbo_tasks::{
11    FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TryJoinIterExt, Vc,
12    debug::ValueDebugFormat, trace::TraceRawVcs,
13};
14use turbo_tasks_env::EnvMap;
15use turbo_tasks_fetch::FetchClientConfig;
16use turbo_tasks_fs::{
17    FileSystemPath,
18    glob::{Glob, GlobOptions},
19};
20use turbopack::module_options::{
21    ConditionContentType, ConditionItem, ConditionPath, ConditionQuery, LoaderRuleItem,
22    WebpackRules, module_options_context::MdxTransformOptions,
23};
24use turbopack_core::{
25    chunk::{CrossOrigin, SourceMapsType},
26    issue::{
27        IgnoreIssue, IgnoreIssuePattern, Issue, IssueExt, IssueSeverity, IssueStage, StyledString,
28    },
29    module_graph::style_groups::StyleGroupsAlgorithm,
30    resolve::ResolveAliasMap,
31};
32use turbopack_ecmascript::{
33    OptionTreeShaking, TreeShakingMode,
34    transform::{
35        OptionReactCompilerCompilationMode, ReactCompilerCompilationMode, ReactCompilerTarget,
36    },
37};
38use turbopack_ecmascript_plugins::transform::{
39    emotion::EmotionTransformConfig, relay::RelayConfig,
40    styled_components::StyledComponentsTransformConfig,
41};
42use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
43
44use crate::{
45    app_structure::FileSystemPathVec,
46    mode::NextMode,
47    next_import_map::mdx_import_source_file,
48    next_shared::{
49        transforms::ModularizeImportPackageConfig, webpack_rules::WebpackLoaderBuiltinCondition,
50    },
51    util::relativize_glob,
52};
53
54/// Name of the directory at the project root where CPU profiles are written when profiling is
55/// enabled (see the `--cpu-prof` CLI flag). It is a fixed-name sibling of `distDir`, not
56/// configurable. Kept here so the bundler and the napi bindings agree on the path.
57pub const DIST_PROFILES_DIR_NAME: &str = ".next-profiles";
58
59#[turbo_tasks::value(transparent)]
60pub struct ModularizeImports(
61    #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<String, ModularizeImportPackageConfig>,
62);
63
64#[turbo_tasks::value(transparent)]
65#[derive(Clone, Debug)]
66pub struct CacheKinds(FxHashSet<RcStr>);
67
68impl CacheKinds {
69    pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
70        self.0.extend(iter);
71    }
72}
73
74impl Default for CacheKinds {
75    fn default() -> Self {
76        CacheKinds(
77            ["default", "remote", "private"]
78                .iter()
79                .map(|&s| s.into())
80                .collect(),
81        )
82    }
83}
84
85#[turbo_tasks::value(transparent)]
86pub struct CacheHandlersMap(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, RcStr>);
87
88#[turbo_tasks::value(eq = "manual")]
89#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
90#[serde(default, rename_all = "camelCase")]
91pub struct NextConfig {
92    // IMPORTANT: all fields should be private and access should be wrapped within a turbo-tasks
93    // function. Otherwise changing NextConfig will lead to invalidating all tasks accessing it.
94    config_file: Option<RcStr>,
95    config_file_name: RcStr,
96
97    /// In-memory cache size in bytes.
98    ///
99    /// If `cache_max_memory_size: 0` disables in-memory caching.
100    cache_max_memory_size: Option<f64>,
101    /// custom path to a cache handler to use
102    cache_handler: Option<RcStr>,
103    #[bincode(with_serde)]
104    cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
105    #[bincode(with = "turbo_bincode::serde_self_describing")]
106    env: FxIndexMap<String, JsonValue>,
107    experimental: ExperimentalConfig,
108    images: ImageConfig,
109    page_extensions: Vec<RcStr>,
110    instrumentation_client_inject: Option<Vec<RcStr>>,
111    react_compiler: Option<ReactCompilerOptionsOrBoolean>,
112    react_production_profiling: Option<bool>,
113    react_strict_mode: Option<bool>,
114    transpile_packages: Option<Vec<RcStr>>,
115    #[bincode(with = "turbo_bincode::serde_self_describing")]
116    modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
117    dist_dir: RcStr,
118    dist_dir_root: RcStr,
119    deployment_id: Option<RcStr>,
120    #[bincode(with = "turbo_bincode::serde_self_describing")]
121    sass_options: Option<serde_json::Value>,
122    trailing_slash: Option<bool>,
123    asset_prefix: Option<RcStr>,
124    base_path: Option<RcStr>,
125    skip_proxy_url_normalize: Option<bool>,
126    skip_trailing_slash_redirect: Option<bool>,
127    i18n: Option<I18NConfig>,
128    cross_origin: CrossOrigin,
129    dev_indicators: Option<DevIndicatorsConfig>,
130    output: Option<OutputType>,
131    turbopack: Option<TurbopackConfig>,
132    production_browser_source_maps: bool,
133    #[bincode(with = "turbo_bincode::serde_self_describing")]
134    output_file_tracing_includes: Option<serde_json::Value>,
135    #[bincode(with = "turbo_bincode::serde_self_describing")]
136    output_file_tracing_excludes: Option<serde_json::Value>,
137    // TODO: This option is not respected, it uses Turbopack's root instead.
138    output_file_tracing_root: Option<RcStr>,
139
140    /// Enables the bundling of node_modules packages (externals) for pages
141    /// server-side bundles.
142    ///
143    /// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies)
144    bundle_pages_router_dependencies: Option<bool>,
145
146    /// A list of packages that should be treated as external on the server
147    /// build.
148    ///
149    /// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages)
150    server_external_packages: Option<Vec<RcStr>>,
151
152    #[serde(rename = "_originalRedirects")]
153    original_redirects: Option<Vec<Redirect>>,
154
155    // Partially supported
156    compiler: Option<CompilerConfig>,
157
158    optimize_fonts: Option<bool>,
159
160    clean_dist_dir: bool,
161    compress: bool,
162    eslint: EslintConfig,
163    exclude_default_moment_locales: bool,
164    generate_etags: bool,
165    http_agent_options: HttpAgentConfig,
166    on_demand_entries: OnDemandEntriesConfig,
167    powered_by_header: bool,
168    #[bincode(with = "turbo_bincode::serde_self_describing")]
169    public_runtime_config: FxIndexMap<String, serde_json::Value>,
170    #[bincode(with = "turbo_bincode::serde_self_describing")]
171    server_runtime_config: FxIndexMap<String, serde_json::Value>,
172    static_page_generation_timeout: f64,
173    target: Option<String>,
174    typescript: TypeScriptConfig,
175    use_file_system_public_routes: bool,
176    cache_components: Option<bool>,
177
178    adapter_path: Option<RcStr>,
179    //
180    // These are never used by Turbopack, and potentially non-serializable anyway:
181    // cache_life: (),
182    // export_path_map: Option<serde_json::Value>,
183    // generate_build_id: Option<serde_json::Value>,
184    // webpack: Option<serde_json::Value>,
185}
186
187#[turbo_tasks::value_impl]
188impl NextConfig {
189    #[turbo_tasks::function]
190    pub fn with_analyze_config(&self) -> Vc<Self> {
191        let mut new = self.clone();
192        new.experimental.turbopack_source_maps = Some(true);
193        new.experimental.turbopack_input_source_maps = Some(false);
194        new.cell()
195    }
196}
197
198#[derive(
199    Clone,
200    Debug,
201    Default,
202    PartialEq,
203    Deserialize,
204    TraceRawVcs,
205    NonLocalValue,
206    OperationValue,
207    Encode,
208    Decode,
209)]
210#[serde(rename_all = "camelCase")]
211struct EslintConfig {
212    dirs: Option<Vec<String>>,
213    ignore_during_builds: Option<bool>,
214}
215
216#[derive(
217    Clone,
218    Debug,
219    Default,
220    PartialEq,
221    Deserialize,
222    TraceRawVcs,
223    NonLocalValue,
224    OperationValue,
225    Encode,
226    Decode,
227)]
228#[serde(rename_all = "kebab-case")]
229pub enum BuildActivityPositions {
230    #[default]
231    BottomRight,
232    BottomLeft,
233    TopRight,
234    TopLeft,
235}
236
237#[derive(
238    Clone,
239    Debug,
240    Default,
241    PartialEq,
242    Deserialize,
243    TraceRawVcs,
244    NonLocalValue,
245    OperationValue,
246    Encode,
247    Decode,
248)]
249#[serde(rename_all = "camelCase")]
250pub struct DevIndicatorsOptions {
251    pub build_activity_position: Option<BuildActivityPositions>,
252    pub position: Option<BuildActivityPositions>,
253}
254
255#[derive(
256    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
257)]
258#[serde(untagged)]
259pub enum DevIndicatorsConfig {
260    WithOptions(DevIndicatorsOptions),
261    Boolean(bool),
262}
263
264#[derive(
265    Clone,
266    Debug,
267    Default,
268    PartialEq,
269    Deserialize,
270    TraceRawVcs,
271    NonLocalValue,
272    OperationValue,
273    Encode,
274    Decode,
275)]
276#[serde(rename_all = "camelCase")]
277struct OnDemandEntriesConfig {
278    max_inactive_age: f64,
279    pages_buffer_length: f64,
280}
281
282#[derive(
283    Clone,
284    Debug,
285    Default,
286    PartialEq,
287    Deserialize,
288    TraceRawVcs,
289    NonLocalValue,
290    OperationValue,
291    Encode,
292    Decode,
293)]
294#[serde(rename_all = "camelCase")]
295struct HttpAgentConfig {
296    keep_alive: bool,
297}
298
299#[derive(
300    Clone,
301    Debug,
302    PartialEq,
303    Eq,
304    Deserialize,
305    TraceRawVcs,
306    NonLocalValue,
307    OperationValue,
308    Encode,
309    Decode,
310)]
311#[serde(rename_all = "camelCase")]
312pub struct DomainLocale {
313    pub default_locale: String,
314    pub domain: String,
315    pub http: Option<bool>,
316    pub locales: Option<Vec<String>>,
317}
318
319#[derive(
320    Clone,
321    Debug,
322    PartialEq,
323    Eq,
324    Deserialize,
325    TraceRawVcs,
326    NonLocalValue,
327    OperationValue,
328    Encode,
329    Decode,
330)]
331#[serde(rename_all = "camelCase")]
332pub struct I18NConfig {
333    pub default_locale: String,
334    pub domains: Option<Vec<DomainLocale>>,
335    pub locale_detection: Option<bool>,
336    pub locales: Vec<String>,
337}
338
339#[turbo_tasks::value(transparent)]
340pub struct OptionI18NConfig(Option<I18NConfig>);
341
342#[derive(
343    Clone,
344    Debug,
345    PartialEq,
346    Eq,
347    Deserialize,
348    TraceRawVcs,
349    NonLocalValue,
350    OperationValue,
351    Encode,
352    Decode,
353)]
354#[serde(rename_all = "kebab-case")]
355pub enum OutputType {
356    Standalone,
357    Export,
358}
359
360#[turbo_tasks::value(transparent)]
361pub struct OptionOutputType(Option<OutputType>);
362
363#[turbo_tasks::task_input]
364#[derive(
365    Debug,
366    Clone,
367    Hash,
368    Eq,
369    PartialEq,
370    Ord,
371    PartialOrd,
372    TraceRawVcs,
373    Serialize,
374    Deserialize,
375    OperationValue,
376    Encode,
377    Decode,
378)]
379#[serde(tag = "type", rename_all = "kebab-case")]
380pub enum RouteHas {
381    Header {
382        key: RcStr,
383        #[serde(skip_serializing_if = "Option::is_none")]
384        value: Option<RcStr>,
385    },
386    Cookie {
387        key: RcStr,
388        #[serde(skip_serializing_if = "Option::is_none")]
389        value: Option<RcStr>,
390    },
391    Query {
392        key: RcStr,
393        #[serde(skip_serializing_if = "Option::is_none")]
394        value: Option<RcStr>,
395    },
396    Host {
397        value: RcStr,
398    },
399}
400
401#[derive(Clone, Debug, Default, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
402#[serde(rename_all = "camelCase")]
403pub struct HeaderValue {
404    pub key: RcStr,
405    pub value: RcStr,
406}
407
408#[derive(Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
409#[serde(rename_all = "camelCase")]
410pub struct Header {
411    pub source: String,
412    pub base_path: Option<bool>,
413    pub locale: Option<bool>,
414    pub headers: Vec<HeaderValue>,
415    pub has: Option<Vec<RouteHas>>,
416    pub missing: Option<Vec<RouteHas>>,
417}
418
419#[derive(
420    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
421)]
422#[serde(rename_all = "camelCase")]
423pub enum RedirectStatus {
424    StatusCode(f64),
425    Permanent(bool),
426}
427
428#[derive(
429    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
430)]
431#[serde(rename_all = "camelCase")]
432pub struct Redirect {
433    pub source: String,
434    pub destination: String,
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub base_path: Option<bool>,
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub locale: Option<bool>,
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub has: Option<Vec<RouteHas>>,
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub missing: Option<Vec<RouteHas>>,
443
444    #[serde(flatten)]
445    pub status: RedirectStatus,
446}
447
448#[derive(Clone, Debug)]
449pub struct Rewrite {
450    pub source: String,
451    pub destination: String,
452    pub base_path: Option<bool>,
453    pub locale: Option<bool>,
454    pub has: Option<Vec<RouteHas>>,
455    pub missing: Option<Vec<RouteHas>>,
456}
457
458#[derive(Clone, Debug)]
459pub struct Rewrites {
460    pub before_files: Vec<Rewrite>,
461    pub after_files: Vec<Rewrite>,
462    pub fallback: Vec<Rewrite>,
463}
464
465#[derive(
466    Clone,
467    Debug,
468    Default,
469    PartialEq,
470    Deserialize,
471    TraceRawVcs,
472    NonLocalValue,
473    OperationValue,
474    Encode,
475    Decode,
476)]
477#[serde(rename_all = "camelCase")]
478pub struct TypeScriptConfig {
479    pub ignore_build_errors: Option<bool>,
480    pub tsconfig_path: Option<String>,
481}
482
483#[turbo_tasks::value(eq = "manual", operation)]
484#[derive(Clone, Debug, PartialEq, Deserialize)]
485#[serde(rename_all = "camelCase")]
486pub struct ImageConfig {
487    pub device_sizes: Vec<u16>,
488    pub image_sizes: Vec<u16>,
489    pub path: String,
490    pub loader: ImageLoader,
491    #[serde(deserialize_with = "empty_string_is_none")]
492    pub loader_file: Option<String>,
493    pub domains: Vec<String>,
494    pub disable_static_images: bool,
495    #[serde(rename = "minimumCacheTTL")]
496    pub minimum_cache_ttl: u64,
497    pub formats: Vec<ImageFormat>,
498    #[serde(rename = "dangerouslyAllowSVG")]
499    pub dangerously_allow_svg: bool,
500    pub content_security_policy: String,
501    pub remote_patterns: Vec<RemotePattern>,
502    pub unoptimized: bool,
503}
504
505fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
506where
507    D: Deserializer<'de>,
508{
509    let o = Option::<String>::deserialize(deserializer)?;
510    Ok(o.filter(|s| !s.is_empty()))
511}
512
513impl Default for ImageConfig {
514    fn default() -> Self {
515        // https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114
516        Self {
517            device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
518            image_sizes: vec![32, 48, 64, 96, 128, 256, 384],
519            path: "/_next/image".to_string(),
520            loader: ImageLoader::Default,
521            loader_file: None,
522            domains: vec![],
523            disable_static_images: false,
524            minimum_cache_ttl: 60,
525            formats: vec![ImageFormat::Webp],
526            dangerously_allow_svg: false,
527            content_security_policy: "".to_string(),
528            remote_patterns: vec![],
529            unoptimized: false,
530        }
531    }
532}
533
534#[derive(
535    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
536)]
537#[serde(rename_all = "kebab-case")]
538pub enum ImageLoader {
539    Default,
540    Imgix,
541    Cloudinary,
542    Akamai,
543    Custom,
544}
545
546#[derive(
547    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
548)]
549pub enum ImageFormat {
550    #[serde(rename = "image/webp")]
551    Webp,
552    #[serde(rename = "image/avif")]
553    Avif,
554}
555
556#[derive(
557    Clone,
558    Debug,
559    Default,
560    PartialEq,
561    Deserialize,
562    TraceRawVcs,
563    NonLocalValue,
564    OperationValue,
565    Encode,
566    Decode,
567)]
568#[serde(rename_all = "camelCase")]
569pub struct RemotePattern {
570    pub hostname: String,
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub protocol: Option<RemotePatternProtocol>,
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub port: Option<String>,
575    #[serde(skip_serializing_if = "Option::is_none")]
576    pub pathname: Option<String>,
577}
578
579#[derive(
580    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
581)]
582#[serde(rename_all = "kebab-case")]
583pub enum RemotePatternProtocol {
584    Http,
585    Https,
586}
587
588#[derive(
589    Clone,
590    Debug,
591    Default,
592    PartialEq,
593    Deserialize,
594    TraceRawVcs,
595    NonLocalValue,
596    OperationValue,
597    Encode,
598    Decode,
599)]
600#[serde(rename_all = "camelCase")]
601pub struct TurbopackConfig {
602    #[serde(default)]
603    #[bincode(with = "turbo_bincode::indexmap")]
604    pub rules: FxIndexMap<RcStr, RuleConfigCollection>,
605    #[bincode(with = "turbo_bincode::serde_self_describing")]
606    pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
607    pub resolve_extensions: Option<Vec<RcStr>>,
608    pub debug_ids: Option<bool>,
609    pub chunk_loading_global: Option<RcStr>,
610    /// Issue patterns to ignore (suppress) from Turbopack output.
611    #[serde(default)]
612    pub ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
613}
614
615#[derive(
616    Deserialize,
617    Clone,
618    PartialEq,
619    Eq,
620    Debug,
621    TraceRawVcs,
622    NonLocalValue,
623    OperationValue,
624    Encode,
625    Decode,
626)]
627#[serde(deny_unknown_fields)]
628pub struct RegexComponents {
629    source: RcStr,
630    flags: RcStr,
631}
632
633/// This type should not be hand-written, but instead `packages/next/src/build/swc/index.ts` will
634/// transform a JS `RegExp` to a `RegexComponents` or a string to a `Glob` before passing it to us.
635///
636/// This is needed because `RegExp` objects are not otherwise serializable.
637#[derive(
638    Clone,
639    PartialEq,
640    Eq,
641    Debug,
642    Deserialize,
643    TraceRawVcs,
644    NonLocalValue,
645    OperationValue,
646    Encode,
647    Decode,
648)]
649#[serde(
650    tag = "type",
651    content = "value",
652    rename_all = "camelCase",
653    deny_unknown_fields
654)]
655pub enum ConfigConditionPath {
656    Glob(RcStr),
657    Regex(RegexComponents),
658}
659
660impl TryFrom<ConfigConditionPath> for ConditionPath {
661    type Error = anyhow::Error;
662
663    fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
664        Ok(match config {
665            ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
666            ConfigConditionPath::Regex(path) => {
667                ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
668            }
669        })
670    }
671}
672
673impl TryFrom<RegexComponents> for EsRegex {
674    type Error = anyhow::Error;
675
676    fn try_from(components: RegexComponents) -> Result<EsRegex> {
677        EsRegex::new(&components.source, &components.flags)
678    }
679}
680
681#[derive(
682    Clone,
683    PartialEq,
684    Eq,
685    Debug,
686    Deserialize,
687    TraceRawVcs,
688    NonLocalValue,
689    OperationValue,
690    Encode,
691    Decode,
692)]
693#[serde(
694    tag = "type",
695    content = "value",
696    rename_all = "camelCase",
697    deny_unknown_fields
698)]
699pub enum ConfigConditionQuery {
700    Constant(RcStr),
701    Regex(RegexComponents),
702}
703
704impl TryFrom<ConfigConditionQuery> for ConditionQuery {
705    type Error = anyhow::Error;
706
707    fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
708        Ok(match config {
709            ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
710            ConfigConditionQuery::Regex(regex) => {
711                ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
712            }
713        })
714    }
715}
716
717#[derive(
718    Clone,
719    PartialEq,
720    Eq,
721    Debug,
722    Deserialize,
723    TraceRawVcs,
724    NonLocalValue,
725    OperationValue,
726    Encode,
727    Decode,
728)]
729#[serde(
730    tag = "type",
731    content = "value",
732    rename_all = "camelCase",
733    deny_unknown_fields
734)]
735pub enum ConfigConditionContentType {
736    Glob(RcStr),
737    Regex(RegexComponents),
738}
739
740impl TryFrom<ConfigConditionContentType> for ConditionContentType {
741    type Error = anyhow::Error;
742
743    fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
744        Ok(match config {
745            ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
746            ConfigConditionContentType::Regex(regex) => {
747                ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
748            }
749        })
750    }
751}
752
753#[derive(
754    Deserialize,
755    Clone,
756    PartialEq,
757    Eq,
758    Debug,
759    TraceRawVcs,
760    NonLocalValue,
761    OperationValue,
762    Encode,
763    Decode,
764)]
765// We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
766// match nearly every object, since it has no required field.
767#[serde(deny_unknown_fields)]
768pub enum ConfigConditionItem {
769    #[serde(rename = "all")]
770    All(Box<[ConfigConditionItem]>),
771    #[serde(rename = "any")]
772    Any(Box<[ConfigConditionItem]>),
773    #[serde(rename = "not")]
774    Not(Box<ConfigConditionItem>),
775    #[serde(untagged)]
776    Builtin(WebpackLoaderBuiltinCondition),
777    #[serde(untagged)]
778    Base {
779        #[serde(default)]
780        path: Option<ConfigConditionPath>,
781        #[serde(default)]
782        content: Option<RegexComponents>,
783        #[serde(default)]
784        query: Option<ConfigConditionQuery>,
785        #[serde(default, rename = "contentType")]
786        content_type: Option<ConfigConditionContentType>,
787    },
788}
789
790impl TryFrom<ConfigConditionItem> for ConditionItem {
791    type Error = anyhow::Error;
792
793    fn try_from(config: ConfigConditionItem) -> Result<Self> {
794        let try_from_vec = |conds: Box<[_]>| {
795            conds
796                .into_iter()
797                .map(ConditionItem::try_from)
798                .collect::<Result<_>>()
799        };
800        Ok(match config {
801            ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
802            ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
803            ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
804            ConfigConditionItem::Builtin(cond) => {
805                ConditionItem::Builtin(RcStr::from(cond.as_str()))
806            }
807            ConfigConditionItem::Base {
808                path,
809                content,
810                query,
811                content_type,
812            } => ConditionItem::Base {
813                path: path.map(ConditionPath::try_from).transpose()?,
814                content: content
815                    .map(EsRegex::try_from)
816                    .transpose()?
817                    .map(EsRegex::resolved_cell),
818                query: query.map(ConditionQuery::try_from).transpose()?,
819                content_type: content_type
820                    .map(ConditionContentType::try_from)
821                    .transpose()?,
822            },
823        })
824    }
825}
826
827#[derive(
828    Clone,
829    Debug,
830    PartialEq,
831    Eq,
832    Deserialize,
833    TraceRawVcs,
834    NonLocalValue,
835    OperationValue,
836    Encode,
837    Decode,
838)]
839#[serde(rename_all = "camelCase")]
840pub struct RuleConfigItem {
841    #[serde(default)]
842    pub loaders: Vec<LoaderItem>,
843    #[serde(default, alias = "as")]
844    pub rename_as: Option<RcStr>,
845    #[serde(default)]
846    pub condition: Option<ConfigConditionItem>,
847    #[serde(default, alias = "type")]
848    pub module_type: Option<RcStr>,
849}
850
851#[derive(
852    Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
853)]
854pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
855
856impl<'de> Deserialize<'de> for RuleConfigCollection {
857    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
858    where
859        D: Deserializer<'de>,
860    {
861        match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
862            deserializer,
863        )? {
864            Either::Left(collection) => Ok(RuleConfigCollection(collection)),
865            Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
866                item,
867            )])),
868        }
869    }
870}
871
872#[derive(
873    Clone,
874    Debug,
875    PartialEq,
876    Eq,
877    Deserialize,
878    TraceRawVcs,
879    NonLocalValue,
880    OperationValue,
881    Encode,
882    Decode,
883)]
884#[serde(untagged)]
885pub enum RuleConfigCollectionItem {
886    Shorthand(LoaderItem),
887    Full(RuleConfigItem),
888}
889
890#[derive(
891    Clone,
892    Debug,
893    PartialEq,
894    Eq,
895    Deserialize,
896    TraceRawVcs,
897    NonLocalValue,
898    OperationValue,
899    Encode,
900    Decode,
901)]
902#[serde(untagged)]
903pub enum LoaderItem {
904    LoaderName(RcStr),
905    LoaderOptions(WebpackLoaderItem),
906}
907
908#[turbo_tasks::value(operation)]
909#[derive(Copy, Clone, Debug, Deserialize)]
910#[serde(rename_all = "camelCase")]
911pub enum ModuleIds {
912    Named,
913    Deterministic,
914}
915
916#[turbo_tasks::value(operation)]
917#[derive(Copy, Clone, Debug, Deserialize)]
918#[serde(rename_all = "camelCase")]
919pub enum TurbopackPluginRuntimeStrategy {
920    #[cfg(feature = "worker_pool")]
921    WorkerThreads,
922    #[cfg(feature = "process_pool")]
923    ChildProcesses,
924}
925
926#[derive(
927    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
928)]
929#[serde(untagged)]
930pub enum MdxRsOptions {
931    Boolean(bool),
932    Option(MdxTransformOptions),
933}
934
935#[turbo_tasks::value(shared, operation)]
936#[derive(Clone, Debug, Default, Serialize, Deserialize)]
937#[serde(rename_all = "snake_case")]
938pub enum ReactCompilerPanicThreshold {
939    #[default]
940    None,
941    CriticalErrors,
942    AllErrors,
943}
944
945/// Subset of react compiler options, we pass these options through to the webpack loader, so it
946/// must be serializable
947#[turbo_tasks::value(shared, operation)]
948#[derive(Clone, Debug, Default, Serialize, Deserialize)]
949#[serde(rename_all = "camelCase")]
950pub struct ReactCompilerOptions {
951    #[serde(default)]
952    pub compilation_mode: ReactCompilerCompilationMode,
953    #[serde(default)]
954    pub panic_threshold: ReactCompilerPanicThreshold,
955    #[serde(default, skip_deserializing, skip_serializing_if = "Option::is_none")]
956    pub target: Option<ReactCompilerTarget>,
957}
958
959#[derive(
960    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
961)]
962#[serde(untagged)]
963pub enum ReactCompilerOptionsOrBoolean {
964    Boolean(bool),
965    Option(ReactCompilerOptions),
966}
967
968#[turbo_tasks::value(transparent)]
969pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
970
971/// Serialized representation of a path pattern for `turbopack.ignoreIssue`.
972/// Strings are serialized as `{ "type": "glob", "value": "..." }` and
973/// RegExp as `{ "type": "regex", "source": "...", "flags": "..." }`.
974#[derive(
975    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
976)]
977#[serde(tag = "type")]
978pub enum TurbopackIgnoreIssuePathPattern {
979    #[serde(rename = "glob")]
980    Glob { value: RcStr },
981    #[serde(rename = "regex")]
982    Regex { source: RcStr, flags: RcStr },
983}
984
985impl TurbopackIgnoreIssuePathPattern {
986    fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
987        match self {
988            TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
989                Glob::parse(value.clone(), GlobOptions::default())?,
990            )),
991            TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
992                Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
993            }
994        }
995    }
996}
997
998/// Serialized representation of a text pattern (title/description) for
999/// `turbopack.ignoreIssue`. Strings are serialized as
1000/// `{ "type": "string", "value": "..." }` and RegExp as
1001/// `{ "type": "regex", "source": "...", "flags": "..." }`.
1002#[derive(
1003    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1004)]
1005#[serde(tag = "type")]
1006pub enum TurbopackIgnoreIssueTextPattern {
1007    #[serde(rename = "string")]
1008    String { value: RcStr },
1009    #[serde(rename = "regex")]
1010    Regex { source: RcStr, flags: RcStr },
1011}
1012
1013impl TurbopackIgnoreIssueTextPattern {
1014    fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
1015        match self {
1016            TurbopackIgnoreIssueTextPattern::String { value } => {
1017                Ok(IgnoreIssuePattern::ExactString(value.clone()))
1018            }
1019            TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
1020                Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
1021            }
1022        }
1023    }
1024}
1025
1026/// A single rule in `turbopack.ignoreIssue`.
1027#[derive(
1028    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1029)]
1030pub struct TurbopackIgnoreIssueRule {
1031    pub path: TurbopackIgnoreIssuePathPattern,
1032    #[serde(default)]
1033    pub title: Option<TurbopackIgnoreIssueTextPattern>,
1034    #[serde(default)]
1035    pub description: Option<TurbopackIgnoreIssueTextPattern>,
1036}
1037
1038/// `experimental.cssChunking` accepts the following shapes (all normalized to a single canonical
1039/// object form via [`CssChunkingConfig::normalize`]):
1040///
1041/// * `true` — equivalent to `{ type: "loose" }` (default loose behaviour).
1042/// * `false` — disabled chunking.
1043/// * `"strict"` / `"loose"` / `"graph"` — string shorthands.
1044/// * `{ type: "strict" }` / `{ type: "loose" }` — object form for the legacy modes.
1045/// * `{ type: "graph", requestCost?, moduleFactorCost? }` — object form for the graph algorithm.
1046#[derive(
1047    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1048)]
1049#[serde(untagged)]
1050pub enum CssChunkingConfig {
1051    Bool(bool),
1052    String(CssChunkingMode),
1053    Object(CssChunkingObject),
1054}
1055
1056/// String shorthand variants for [`CssChunkingConfig`].
1057#[derive(
1058    Clone,
1059    Copy,
1060    Debug,
1061    PartialEq,
1062    Eq,
1063    Deserialize,
1064    TraceRawVcs,
1065    NonLocalValue,
1066    OperationValue,
1067    Encode,
1068    Decode,
1069)]
1070#[serde(rename_all = "lowercase")]
1071pub enum CssChunkingMode {
1072    Strict,
1073    Loose,
1074    Graph,
1075}
1076
1077/// Object form of `experimental.cssChunking`.
1078///
1079/// `None` is the normalized representation of `false` ("CSS chunking is disabled"). It is not
1080/// reachable through deserialization — users write `false`, not `{ type: "none" }`.
1081#[derive(
1082    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1083)]
1084#[serde(tag = "type", rename_all = "lowercase")]
1085pub enum CssChunkingObject {
1086    #[serde(skip)]
1087    None,
1088    Strict,
1089    Loose,
1090    Graph(CssChunkingGraphOptions),
1091}
1092
1093/// Cost parameters for the graph algorithm. See [`CssChunkingConfig`] for details.
1094#[derive(
1095    Clone,
1096    Debug,
1097    Default,
1098    PartialEq,
1099    Deserialize,
1100    TraceRawVcs,
1101    NonLocalValue,
1102    OperationValue,
1103    Encode,
1104    Decode,
1105)]
1106#[serde(rename_all = "camelCase")]
1107pub struct CssChunkingGraphOptions {
1108    pub request_cost: Option<f32>,
1109    pub module_factor_cost: Option<f32>,
1110}
1111
1112impl CssChunkingConfig {
1113    /// Normalize all input shapes (booleans, strings, object form) to the canonical object form.
1114    /// `false` maps to [`CssChunkingObject::None`]; `true` is equivalent to `'loose'`.
1115    pub fn normalize(&self) -> CssChunkingObject {
1116        match self {
1117            CssChunkingConfig::Bool(false) => CssChunkingObject::None,
1118            CssChunkingConfig::Bool(true) => CssChunkingObject::Loose,
1119            CssChunkingConfig::String(CssChunkingMode::Strict) => CssChunkingObject::Strict,
1120            CssChunkingConfig::String(CssChunkingMode::Loose) => CssChunkingObject::Loose,
1121            CssChunkingConfig::String(CssChunkingMode::Graph) => {
1122                CssChunkingObject::Graph(CssChunkingGraphOptions::default())
1123            }
1124            CssChunkingConfig::Object(obj) => obj.clone(),
1125        }
1126    }
1127}
1128
1129/// Default `requestCost` for the graph algorithm (in bytes).
1130const DEFAULT_REQUEST_COST: f32 = 20_000.0;
1131/// Default `moduleFactorCost` for the graph algorithm.
1132const DEFAULT_MODULE_FACTOR_COST: f32 = 1.0;
1133
1134/// Resolve `experimental.cssChunking` to the [`StyleGroupsAlgorithm`] Turbopack should use.
1135///
1136/// `strict` and `false` (`CssChunkingObject::None`) are bundler-incompatible with Turbopack and
1137/// are rejected at config-validation time on the JS side; if one slips through, we bail rather
1138/// than silently falling back. `loose` and `true` map to [`StyleGroupsAlgorithm::Default`].
1139fn resolve_css_chunking_algorithm(
1140    config: Option<&CssChunkingConfig>,
1141) -> Result<StyleGroupsAlgorithm> {
1142    let Some(config) = config else {
1143        return Ok(StyleGroupsAlgorithm::Default);
1144    };
1145    Ok(match config.normalize() {
1146        CssChunkingObject::None => {
1147            anyhow::bail!(
1148                "`experimental.cssChunking: false` is not supported by Turbopack; this should \
1149                 have been rejected at config validation time"
1150            )
1151        }
1152        CssChunkingObject::Strict => {
1153            anyhow::bail!(
1154                "`experimental.cssChunking: \"strict\"` is not supported by Turbopack; this \
1155                 should have been rejected at config validation time"
1156            )
1157        }
1158        CssChunkingObject::Loose => StyleGroupsAlgorithm::Default,
1159        CssChunkingObject::Graph(opts) => StyleGroupsAlgorithm::graph(
1160            opts.request_cost.unwrap_or(DEFAULT_REQUEST_COST),
1161            opts.module_factor_cost
1162                .unwrap_or(DEFAULT_MODULE_FACTOR_COST),
1163        ),
1164    })
1165}
1166
1167#[derive(
1168    Clone,
1169    Debug,
1170    Default,
1171    PartialEq,
1172    Deserialize,
1173    TraceRawVcs,
1174    ValueDebugFormat,
1175    NonLocalValue,
1176    OperationValue,
1177    Encode,
1178    Decode,
1179)]
1180#[serde(rename_all = "camelCase")]
1181pub struct ExperimentalConfig {
1182    // all fields should be private and access should be wrapped within a turbo-tasks function
1183    // Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
1184    allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1185    client_router_filter: Option<bool>,
1186    /// decimal for percent for possible false positives e.g. 0.01 for 10%
1187    /// potential false matches lower percent increases size of the filter
1188    client_router_filter_allowed_rate: Option<f64>,
1189    client_router_filter_redirects: Option<bool>,
1190    fetch_cache_key_prefix: Option<RcStr>,
1191    isr_flush_to_disk: Option<bool>,
1192    /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
1193    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
1194    mdx_rs: Option<MdxRsOptions>,
1195    strict_next_head: Option<bool>,
1196    #[bincode(with = "turbo_bincode::serde_self_describing")]
1197    swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
1198    swc_env_options: Option<SwcEnvOptions>,
1199    external_middleware_rewrites_resolve: Option<bool>,
1200    scroll_restoration: Option<bool>,
1201    manual_client_base_path: Option<bool>,
1202    optimistic_client_cache: Option<bool>,
1203    middleware_prefetch: Option<MiddlewarePrefetchType>,
1204    /// optimizeCss can be boolean or critters' option object
1205    /// Use Record<string, unknown> as critters doesn't export its Option type ([link](https://github.com/GoogleChromeLabs/critters/blob/a590c05f9197b656d2aeaae9369df2483c26b072/packages/critters/src/index.d.ts))
1206    #[bincode(with = "turbo_bincode::serde_self_describing")]
1207    optimize_css: Option<serde_json::Value>,
1208    next_script_workers: Option<bool>,
1209    web_vitals_attribution: Option<Vec<RcStr>>,
1210    server_actions: Option<ServerActionsOrLegacyBool>,
1211    sri: Option<SubResourceIntegrity>,
1212    /// @deprecated - use top-level cache_components instead.
1213    /// This field is kept for backwards compatibility during migration.
1214    cache_components: Option<bool>,
1215    use_cache: Option<bool>,
1216    runtime_server_deployment_id: Option<bool>,
1217    supports_immutable_assets: Option<bool>,
1218
1219    /// A salt to mix into chunk and asset content hashes. Empty string means
1220    /// no salt.
1221    output_hash_salt: Option<RcStr>,
1222
1223    /// CSS chunking strategy. See [`CssChunkingConfig`] for the accepted shapes.
1224    css_chunking: Option<CssChunkingConfig>,
1225
1226    // ---
1227    // UNSUPPORTED
1228    // ---
1229    adjust_font_fallbacks: Option<bool>,
1230    adjust_font_fallbacks_with_size_adjust: Option<bool>,
1231    after: Option<bool>,
1232    app_document_preloading: Option<bool>,
1233    app_new_scroll_handler: Option<bool>,
1234    case_sensitive_routes: Option<bool>,
1235    cpus: Option<f64>,
1236    cra_compat: Option<bool>,
1237    disable_optimized_loading: Option<bool>,
1238    disable_postcss_preset_env: Option<bool>,
1239    esm_externals: Option<EsmExternals>,
1240    #[bincode(with = "turbo_bincode::serde_self_describing")]
1241    extension_alias: Option<serde_json::Value>,
1242    external_dir: Option<bool>,
1243    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
1244    /// in the browser Full list of old polyfills is accessible here:
1245    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
1246    fallback_node_polyfills: Option<bool>, // false
1247    force_swc_transforms: Option<bool>,
1248    fully_specified: Option<bool>,
1249    gzip_size: Option<bool>,
1250
1251    inline_css: Option<bool>,
1252    instrumentation_hook: Option<bool>,
1253    client_trace_metadata: Option<Vec<String>>,
1254    large_page_data_bytes: Option<f64>,
1255    #[bincode(with = "turbo_bincode::serde_self_describing")]
1256    logging: Option<serde_json::Value>,
1257    memory_based_workers_count: Option<bool>,
1258    /// Optimize React APIs for server builds.
1259    optimize_server_react: Option<bool>,
1260    /// Automatically apply the "modularize_imports" optimization to imports of
1261    /// the specified packages.
1262    optimize_package_imports: Option<Vec<RcStr>>,
1263    taint: Option<bool>,
1264    proxy_timeout: Option<f64>,
1265    /// enables the minification of server code.
1266    server_minification: Option<bool>,
1267    /// Enables source maps generation for the server production bundle.
1268    server_source_maps: Option<bool>,
1269    swc_trace_profiling: Option<bool>,
1270    transition_indicator: Option<bool>,
1271    gesture_transition: Option<bool>,
1272    // `rename_all = "camelCase"` would lowercase the acronym to `blockingSsr`;
1273    // rename explicitly so it deserializes from the public `blockingSSR` field.
1274    #[serde(rename = "blockingSSR")]
1275    blocking_ssr: Option<bool>,
1276    /// @internal Used by the Next.js internals only.
1277    trust_host_header: Option<bool>,
1278
1279    #[bincode(with = "turbo_bincode::serde_self_describing")]
1280    url_imports: Option<serde_json::Value>,
1281    /// This option is to enable running the Webpack build in a worker thread
1282    /// (doesn't apply to Turbopack).
1283    webpack_build_worker: Option<bool>,
1284    worker_threads: Option<bool>,
1285
1286    turbopack_minify: Option<bool>,
1287    turbopack_module_ids: Option<ModuleIds>,
1288    turbopack_plugin_runtime_strategy: Option<TurbopackPluginRuntimeStrategy>,
1289    turbopack_source_maps: Option<bool>,
1290    turbopack_input_source_maps: Option<bool>,
1291    turbopack_tree_shaking: Option<bool>,
1292    turbopack_scope_hoisting: Option<bool>,
1293    /// Custom URL prefix for Web Worker URLs (the entrypoint and the module
1294    /// chunks loaded inside the worker) produced by
1295    /// `new Worker(new URL(..., import.meta.url))`. Mirrors webpack's
1296    /// `output.workerPublicPath`. When unset, Worker URLs use the regular
1297    /// chunk base path (i.e. `assetPrefix` + `/_next/`).
1298    ///
1299    /// Like `assetPrefix`, the value is a prefix without a trailing slash
1300    /// and without `/_next` — `/_next/` is appended automatically. An empty
1301    /// string is a literal empty prefix; only `None` falls back to
1302    /// `assetPrefix`.
1303    turbopack_worker_asset_prefix: Option<RcStr>,
1304    turbopack_client_side_nested_async_chunking: Option<bool>,
1305    turbopack_server_side_nested_async_chunking: Option<bool>,
1306    turbopack_import_type_bytes: Option<bool>,
1307    turbopack_import_type_text: Option<bool>,
1308    /// Disable automatic configuration of the sass loader.
1309    #[serde(default)]
1310    turbopack_use_builtin_sass: Option<bool>,
1311    /// Disable automatic configuration of the babel loader when a babel configuration file is
1312    /// present.
1313    #[serde(default)]
1314    turbopack_use_builtin_babel: Option<bool>,
1315    /// Enable per-directory PostCSS config resolution. When true, Turbopack
1316    /// searches for postcss.config.js starting from the CSS file's parent
1317    /// directory first, then falls back to the project root.
1318    #[serde(default)]
1319    turbopack_local_postcss_config: Option<bool>,
1320    // Whether to enable the global-not-found convention
1321    global_not_found: Option<bool>,
1322    /// Experimental Rust React compiler (Turbopack only); requires `reactCompiler`.
1323    turbopack_rust_react_compiler: Option<bool>,
1324    /// Defaults to false in development mode, true in production mode.
1325    turbopack_remove_unused_imports: Option<bool>,
1326    /// Defaults to false in development mode, true in production mode.
1327    turbopack_remove_unused_exports: Option<bool>,
1328    /// Enable local analysis to infer side effect free modules. Defaults to true.
1329    turbopack_infer_module_side_effects: Option<bool>,
1330    /// Devtool option for the segment explorer.
1331    devtool_segment_explorer: Option<bool>,
1332    /// Whether to report inlined system environment variables as warnings or errors.
1333    report_system_env_inlining: Option<String>,
1334    // Use project.is_persistent_caching() instead
1335    // turbopack_file_system_cache_for_dev: Option<bool>,
1336    // turbopack_file_system_cache_for_build: Option<bool>,
1337    lightning_css_features: Option<LightningCssFeatures>,
1338}
1339
1340#[derive(
1341    Clone,
1342    Debug,
1343    PartialEq,
1344    Eq,
1345    Deserialize,
1346    TraceRawVcs,
1347    NonLocalValue,
1348    OperationValue,
1349    Encode,
1350    Decode,
1351)]
1352#[serde(rename_all = "camelCase")]
1353pub struct SubResourceIntegrity {
1354    pub algorithm: Option<RcStr>,
1355}
1356
1357#[derive(
1358    Clone,
1359    Debug,
1360    Default,
1361    PartialEq,
1362    Eq,
1363    Deserialize,
1364    TraceRawVcs,
1365    NonLocalValue,
1366    OperationValue,
1367    Encode,
1368    Decode,
1369)]
1370#[serde(rename_all = "camelCase")]
1371pub struct LightningCssFeatures {
1372    pub include: Option<Vec<RcStr>>,
1373    pub exclude: Option<Vec<RcStr>>,
1374}
1375
1376#[derive(
1377    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1378)]
1379#[serde(untagged)]
1380pub enum ServerActionsOrLegacyBool {
1381    /// The current way to configure server actions sub behaviors.
1382    ServerActionsConfig(ServerActions),
1383
1384    /// The legacy way to disable server actions. This is no longer used, server
1385    /// actions is always enabled.
1386    LegacyBool(bool),
1387}
1388
1389#[derive(
1390    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1391)]
1392#[serde(rename_all = "kebab-case")]
1393pub enum EsmExternalsValue {
1394    Loose,
1395}
1396
1397#[derive(
1398    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1399)]
1400#[serde(untagged)]
1401pub enum EsmExternals {
1402    Loose(EsmExternalsValue),
1403    Bool(bool),
1404}
1405
1406// Test for esm externals deserialization.
1407#[test]
1408fn test_esm_externals_deserialization() {
1409    let json = serde_json::json!({
1410        "esmExternals": true
1411    });
1412    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1413    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1414
1415    let json = serde_json::json!({
1416        "esmExternals": "loose"
1417    });
1418    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1419    assert_eq!(
1420        config.esm_externals,
1421        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1422    );
1423}
1424
1425#[derive(
1426    Clone,
1427    Debug,
1428    Default,
1429    PartialEq,
1430    Eq,
1431    Deserialize,
1432    TraceRawVcs,
1433    NonLocalValue,
1434    OperationValue,
1435    Encode,
1436    Decode,
1437)]
1438#[serde(rename_all = "camelCase")]
1439pub struct ServerActions {
1440    /// Allows adjusting body parser size limit for server actions.
1441    pub body_size_limit: Option<SizeLimit>,
1442}
1443
1444#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1445#[serde(untagged)]
1446pub enum SizeLimit {
1447    Number(f64),
1448    WithUnit(String),
1449}
1450
1451// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
1452// implement Eq.
1453impl PartialEq for SizeLimit {
1454    fn eq(&self, other: &Self) -> bool {
1455        match (self, other) {
1456            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1457            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1458            _ => false,
1459        }
1460    }
1461}
1462
1463impl Eq for SizeLimit {}
1464
1465#[derive(
1466    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1467)]
1468#[serde(rename_all = "kebab-case")]
1469pub enum MiddlewarePrefetchType {
1470    Strict,
1471    Flexible,
1472}
1473
1474#[derive(
1475    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1476)]
1477#[serde(untagged)]
1478pub enum EmotionTransformOptionsOrBoolean {
1479    Boolean(bool),
1480    Options(EmotionTransformConfig),
1481}
1482
1483impl EmotionTransformOptionsOrBoolean {
1484    pub fn is_enabled(&self) -> bool {
1485        match self {
1486            Self::Boolean(enabled) => *enabled,
1487            _ => true,
1488        }
1489    }
1490}
1491
1492#[derive(
1493    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1494)]
1495#[serde(untagged)]
1496pub enum StyledComponentsTransformOptionsOrBoolean {
1497    Boolean(bool),
1498    Options(StyledComponentsTransformConfig),
1499}
1500
1501impl StyledComponentsTransformOptionsOrBoolean {
1502    pub fn is_enabled(&self) -> bool {
1503        match self {
1504            Self::Boolean(enabled) => *enabled,
1505            _ => true,
1506        }
1507    }
1508}
1509
1510#[turbo_tasks::value(eq = "manual")]
1511#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1512#[serde(rename_all = "camelCase")]
1513pub struct CompilerConfig {
1514    pub react_remove_properties: Option<ReactRemoveProperties>,
1515    pub relay: Option<RelayConfig>,
1516    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1517    pub remove_console: Option<RemoveConsoleConfig>,
1518    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1519}
1520
1521#[derive(
1522    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1523)]
1524#[serde(untagged, rename_all = "camelCase")]
1525pub enum ReactRemoveProperties {
1526    Boolean(bool),
1527    Config { properties: Option<Vec<String>> },
1528}
1529
1530impl ReactRemoveProperties {
1531    pub fn is_enabled(&self) -> bool {
1532        match self {
1533            Self::Boolean(enabled) => *enabled,
1534            _ => true,
1535        }
1536    }
1537}
1538
1539#[derive(
1540    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1541)]
1542#[serde(untagged)]
1543pub enum RemoveConsoleConfig {
1544    Boolean(bool),
1545    Config { exclude: Option<Vec<String>> },
1546}
1547
1548impl RemoveConsoleConfig {
1549    pub fn is_enabled(&self) -> bool {
1550        match self {
1551            Self::Boolean(enabled) => *enabled,
1552            _ => true,
1553        }
1554    }
1555}
1556
1557#[turbo_tasks::value(transparent)]
1558pub struct ResolveExtensions(Option<Vec<RcStr>>);
1559
1560#[turbo_tasks::value(transparent)]
1561pub struct SwcPlugins(
1562    #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1563);
1564
1565/// Options for SWC's preset-env, exposed via `experimental.swcEnvOptions`.
1566#[derive(
1567    Clone,
1568    Debug,
1569    Default,
1570    PartialEq,
1571    Eq,
1572    Serialize,
1573    Deserialize,
1574    TraceRawVcs,
1575    NonLocalValue,
1576    OperationValue,
1577    Encode,
1578    Decode,
1579)]
1580#[serde(rename_all = "camelCase")]
1581pub struct SwcEnvOptions {
1582    pub mode: Option<RcStr>,
1583    pub core_js: Option<RcStr>,
1584    pub skip: Option<Vec<RcStr>>,
1585    pub include: Option<Vec<RcStr>>,
1586    pub exclude: Option<Vec<RcStr>>,
1587    pub shipped_proposals: Option<bool>,
1588    pub force_all_transforms: Option<bool>,
1589    pub debug: Option<bool>,
1590    pub loose: Option<bool>,
1591}
1592
1593#[turbo_tasks::value(transparent)]
1594pub struct OptionSwcEnvOptions(Option<SwcEnvOptions>);
1595
1596#[turbo_tasks::value(transparent)]
1597pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1598
1599#[turbo_tasks::value(transparent)]
1600
1601pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1602
1603#[turbo_tasks::value(transparent)]
1604pub struct OptionFileSystemPath(Option<FileSystemPath>);
1605
1606#[turbo_tasks::value(transparent)]
1607pub struct IgnoreIssues(Box<[IgnoreIssue]>);
1608
1609#[turbo_tasks::value(transparent)]
1610pub struct OptionJsonValue(
1611    #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1612);
1613
1614fn turbopack_config_documentation_link() -> RcStr {
1615    rcstr!(
1616        "https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders"
1617    )
1618}
1619
1620#[turbo_tasks::value(shared)]
1621struct InvalidLoaderRuleRenameAsIssue {
1622    glob: RcStr,
1623    rename_as: RcStr,
1624    config_file_path: FileSystemPath,
1625}
1626
1627#[async_trait]
1628#[turbo_tasks::value_impl]
1629impl Issue for InvalidLoaderRuleRenameAsIssue {
1630    async fn file_path(&self) -> Result<FileSystemPath> {
1631        Ok(self.config_file_path.clone())
1632    }
1633
1634    fn stage(&self) -> IssueStage {
1635        IssueStage::Config
1636    }
1637
1638    async fn title(&self) -> Result<StyledString> {
1639        Ok(StyledString::Text(
1640            format!("Invalid loader rule for extension: {}", self.glob).into(),
1641        ))
1642    }
1643
1644    async fn description(&self) -> Result<Option<StyledString>> {
1645        Ok(Some(StyledString::Text(RcStr::from(format!(
1646            "The extension {} contains a wildcard, but the `as` option does not: {}",
1647            self.glob, self.rename_as,
1648        )))))
1649    }
1650
1651    fn documentation_link(&self) -> RcStr {
1652        turbopack_config_documentation_link()
1653    }
1654}
1655
1656#[turbo_tasks::value(shared)]
1657struct InvalidLoaderRuleConditionIssue {
1658    error_string: RcStr,
1659    condition: ConfigConditionItem,
1660    config_file_path: FileSystemPath,
1661}
1662
1663#[async_trait]
1664#[turbo_tasks::value_impl]
1665impl Issue for InvalidLoaderRuleConditionIssue {
1666    async fn file_path(&self) -> Result<FileSystemPath> {
1667        Ok(self.config_file_path.clone())
1668    }
1669
1670    fn stage(&self) -> IssueStage {
1671        IssueStage::Config
1672    }
1673
1674    async fn title(&self) -> Result<StyledString> {
1675        Ok(StyledString::Text(rcstr!(
1676            "Invalid condition for Turbopack loader rule"
1677        )))
1678    }
1679
1680    async fn description(&self) -> Result<Option<StyledString>> {
1681        Ok(Some(StyledString::Stack(vec![
1682            StyledString::Line(vec![
1683                StyledString::Text(rcstr!("Encountered the following error: ")),
1684                StyledString::Code(self.error_string.clone()),
1685            ]),
1686            StyledString::Text(rcstr!("While processing the condition:")),
1687            StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1688        ])))
1689    }
1690
1691    fn documentation_link(&self) -> RcStr {
1692        turbopack_config_documentation_link()
1693    }
1694}
1695
1696#[turbo_tasks::value(transparent)]
1697pub struct OutputFileTracingIncludesExcludes(
1698    #[bincode(with = "turbo_bincode::indexmap")]
1699    FxIndexMap<ResolvedVc<Glob>, Vec<(RcStr, FileSystemPath)>>,
1700);
1701
1702impl OutputFileTracingIncludesExcludes {
1703    pub async fn parse(
1704        project_path: FileSystemPath,
1705        value: &Option<serde_json::Value>,
1706    ) -> Result<OutputFileTracingIncludesExcludes> {
1707        if let Some(value) = value
1708            && let Some(map) = value.as_object()
1709        {
1710            Ok(OutputFileTracingIncludesExcludes(
1711                map.iter()
1712                    .map(async |(route_pattern, file_patterns)| {
1713                        let route_pattern = Glob::new(
1714                            RcStr::from(route_pattern.clone()),
1715                            GlobOptions { contains: true },
1716                        )
1717                        .to_resolved()
1718                        .await?;
1719                        let file_patterns = file_patterns
1720                            .as_array()
1721                            .iter()
1722                            .flat_map(|pattern| pattern.iter())
1723                            .filter_map(|pattern| pattern.as_str())
1724                            .map(async |pattern_str| {
1725                                let (glob, root) = relativize_glob(pattern_str, &project_path)?;
1726                                Ok((RcStr::from(glob), root))
1727                            })
1728                            .try_join()
1729                            .await?;
1730                        Ok((route_pattern, file_patterns))
1731                    })
1732                    .try_join()
1733                    .await?
1734                    .into_iter()
1735                    .collect(),
1736            ))
1737        } else {
1738            Ok(OutputFileTracingIncludesExcludes(FxIndexMap::default()))
1739        }
1740    }
1741}
1742
1743#[turbo_tasks::value_impl]
1744impl NextConfig {
1745    #[turbo_tasks::function]
1746    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1747        let string = string.await?;
1748        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1749        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1750            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1751        Ok(config.cell())
1752    }
1753
1754    #[turbo_tasks::function]
1755    pub async fn config_file_path(
1756        &self,
1757        project_path: FileSystemPath,
1758    ) -> Result<Vc<FileSystemPath>> {
1759        Ok(project_path.join(&self.config_file_name)?.cell())
1760    }
1761
1762    #[turbo_tasks::function]
1763    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1764        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1765    }
1766
1767    #[turbo_tasks::function]
1768    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1769        Vc::cell(self.react_production_profiling.unwrap_or_default())
1770    }
1771
1772    #[turbo_tasks::function]
1773    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1774        Vc::cell(
1775            self.server_external_packages
1776                .as_ref()
1777                .cloned()
1778                .unwrap_or_default(),
1779        )
1780    }
1781
1782    #[turbo_tasks::function]
1783    pub fn is_standalone(&self) -> Vc<bool> {
1784        Vc::cell(self.output == Some(OutputType::Standalone))
1785    }
1786
1787    #[turbo_tasks::function]
1788    pub fn base_path(&self) -> Vc<Option<RcStr>> {
1789        Vc::cell(self.base_path.clone())
1790    }
1791
1792    #[turbo_tasks::function]
1793    pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1794        if let Some(handler) = &self.cache_handler {
1795            Ok(Vc::cell(Some(project_path.join(handler)?)))
1796        } else {
1797            Ok(Vc::cell(None))
1798        }
1799    }
1800
1801    #[turbo_tasks::function]
1802    pub fn compiler(&self) -> Vc<CompilerConfig> {
1803        self.compiler.clone().unwrap_or_default().cell()
1804    }
1805
1806    #[turbo_tasks::function]
1807    pub fn env(&self) -> Vc<EnvMap> {
1808        // The value expected for env is Record<String, String>, but config itself
1809        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1810        // then stringifies it. We do the interop here as well.
1811        let env = self
1812            .env
1813            .iter()
1814            .map(|(k, v)| {
1815                (
1816                    k.as_str().into(),
1817                    if let JsonValue::String(s) = v {
1818                        // A string value is kept, calling `to_string` would wrap in to quotes.
1819                        s.as_str().into()
1820                    } else {
1821                        v.to_string().into()
1822                    },
1823                )
1824            })
1825            .collect();
1826
1827        Vc::cell(env)
1828    }
1829
1830    #[turbo_tasks::function]
1831    pub fn image_config(&self) -> Vc<ImageConfig> {
1832        self.images.clone().cell()
1833    }
1834
1835    #[turbo_tasks::function]
1836    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1837        // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
1838        // which just builds a regex alternative, which greedily matches the longest
1839        // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
1840        let mut extensions = self.page_extensions.clone();
1841        extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1842        Vc::cell(extensions)
1843    }
1844
1845    #[turbo_tasks::function]
1846    pub fn instrumentation_client_inject(&self) -> Vc<Vec<RcStr>> {
1847        Vc::cell(
1848            self.instrumentation_client_inject
1849                .clone()
1850                .unwrap_or_default(),
1851        )
1852    }
1853
1854    #[turbo_tasks::function]
1855    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1856        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1857    }
1858
1859    #[turbo_tasks::function]
1860    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1861        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1862    }
1863
1864    #[turbo_tasks::function]
1865    pub async fn webpack_rules(
1866        self: Vc<Self>,
1867        project_path: FileSystemPath,
1868    ) -> Result<Vc<WebpackRules>> {
1869        let this = self.await?;
1870        let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1871            return Ok(Vc::cell(Vec::new()));
1872        };
1873        if turbo_rules.is_empty() {
1874            return Ok(Vc::cell(Vec::new()));
1875        }
1876        let mut rules = Vec::new();
1877        for (glob, rule_collection) in turbo_rules.iter() {
1878            fn transform_loaders(
1879                loaders: &mut dyn Iterator<Item = &LoaderItem>,
1880            ) -> ResolvedVc<WebpackLoaderItems> {
1881                ResolvedVc::cell(
1882                    loaders
1883                        .map(|item| match item {
1884                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1885                                loader: name.clone(),
1886                                options: Default::default(),
1887                            },
1888                            LoaderItem::LoaderOptions(options) => options.clone(),
1889                        })
1890                        .collect(),
1891                )
1892            }
1893            for item in &rule_collection.0 {
1894                match item {
1895                    RuleConfigCollectionItem::Shorthand(loaders) => {
1896                        rules.push((
1897                            glob.clone(),
1898                            LoaderRuleItem {
1899                                loaders: transform_loaders(&mut [loaders].into_iter()),
1900                                rename_as: None,
1901                                condition: None,
1902                                module_type: None,
1903                            },
1904                        ));
1905                    }
1906                    RuleConfigCollectionItem::Full(RuleConfigItem {
1907                        loaders,
1908                        rename_as,
1909                        condition,
1910                        module_type,
1911                    }) => {
1912                        // If the extension contains a wildcard, and the rename_as does not,
1913                        // emit an issue to prevent users from encountering duplicate module
1914                        // names.
1915                        if glob.contains("*")
1916                            && let Some(rename_as) = rename_as.as_ref()
1917                            && !rename_as.contains("*")
1918                        {
1919                            InvalidLoaderRuleRenameAsIssue {
1920                                glob: glob.clone(),
1921                                config_file_path: self
1922                                    .config_file_path(project_path.clone())
1923                                    .owned()
1924                                    .await?,
1925                                rename_as: rename_as.clone(),
1926                            }
1927                            .resolved_cell()
1928                            .emit();
1929                        }
1930
1931                        // convert from Next.js-specific condition type to internal Turbopack
1932                        // condition type
1933                        let condition = if let Some(condition) = condition {
1934                            match ConditionItem::try_from(condition.clone()) {
1935                                Ok(cond) => Some(cond),
1936                                Err(err) => {
1937                                    InvalidLoaderRuleConditionIssue {
1938                                        error_string: RcStr::from(err.to_string()),
1939                                        condition: condition.clone(),
1940                                        config_file_path: self
1941                                            .config_file_path(project_path.clone())
1942                                            .owned()
1943                                            .await?,
1944                                    }
1945                                    .resolved_cell()
1946                                    .emit();
1947                                    None
1948                                }
1949                            }
1950                        } else {
1951                            None
1952                        };
1953                        rules.push((
1954                            glob.clone(),
1955                            LoaderRuleItem {
1956                                loaders: transform_loaders(&mut loaders.iter()),
1957                                rename_as: rename_as.clone(),
1958                                condition,
1959                                module_type: module_type.clone(),
1960                            },
1961                        ));
1962                    }
1963                }
1964            }
1965        }
1966        Ok(Vc::cell(rules))
1967    }
1968
1969    #[turbo_tasks::function]
1970    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1971        let Some(resolve_alias) = self
1972            .turbopack
1973            .as_ref()
1974            .and_then(|t| t.resolve_alias.as_ref())
1975        else {
1976            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1977        };
1978        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1979        Ok(alias_map.cell())
1980    }
1981
1982    #[turbo_tasks::function]
1983    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1984        let Some(resolve_extensions) = self
1985            .turbopack
1986            .as_ref()
1987            .and_then(|t| t.resolve_extensions.as_ref())
1988        else {
1989            return Vc::cell(None);
1990        };
1991        Vc::cell(Some(resolve_extensions.clone()))
1992    }
1993
1994    #[turbo_tasks::function]
1995    pub fn import_externals(&self) -> Result<Vc<bool>> {
1996        Ok(Vc::cell(match self.experimental.esm_externals {
1997            Some(EsmExternals::Bool(b)) => b,
1998            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1999            None => true,
2000        }))
2001    }
2002
2003    #[turbo_tasks::function]
2004    pub fn inline_css(&self) -> Vc<bool> {
2005        Vc::cell(self.experimental.inline_css.unwrap_or(false))
2006    }
2007
2008    /// Resolve `experimental.cssChunking` to a [`StyleGroupsAlgorithm`] (with defaults applied
2009    /// for the cost parameters of the graph algorithm).
2010    #[turbo_tasks::function]
2011    pub fn css_chunking(&self) -> Result<Vc<StyleGroupsAlgorithm>> {
2012        Ok(resolve_css_chunking_algorithm(self.experimental.css_chunking.as_ref())?.cell())
2013    }
2014
2015    #[turbo_tasks::function]
2016    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
2017        let options = &self.experimental.mdx_rs;
2018
2019        let options = match options {
2020            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
2021                MdxTransformOptions {
2022                    provider_import_source: Some(mdx_import_source_file()),
2023                    ..Default::default()
2024                }
2025                .resolved_cell(),
2026            )),
2027            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
2028                MdxTransformOptions {
2029                    provider_import_source: Some(
2030                        options
2031                            .provider_import_source
2032                            .clone()
2033                            .unwrap_or(mdx_import_source_file()),
2034                    ),
2035                    ..options.clone()
2036                }
2037                .resolved_cell(),
2038            )),
2039            _ => OptionalMdxTransformOptions(None),
2040        };
2041
2042        options.cell()
2043    }
2044
2045    #[turbo_tasks::function]
2046    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
2047        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
2048    }
2049
2050    #[turbo_tasks::function]
2051    pub fn dist_dir(&self) -> Vc<RcStr> {
2052        Vc::cell(self.dist_dir.clone())
2053    }
2054    #[turbo_tasks::function]
2055    pub fn dist_dir_root(&self) -> Vc<RcStr> {
2056        Vc::cell(self.dist_dir_root.clone())
2057    }
2058
2059    #[turbo_tasks::function]
2060    pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
2061        if let Some(handlers) = &self.cache_handlers {
2062            Ok(Vc::cell(
2063                handlers
2064                    .values()
2065                    .map(|h| project_path.join(h))
2066                    .collect::<Result<Vec<_>>>()?,
2067            ))
2068        } else {
2069            Ok(Vc::cell(vec![]))
2070        }
2071    }
2072
2073    #[turbo_tasks::function]
2074    pub fn cache_handlers_map(&self) -> Vc<CacheHandlersMap> {
2075        Vc::cell(self.cache_handlers.clone().unwrap_or_default())
2076    }
2077
2078    #[turbo_tasks::function]
2079    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
2080        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
2081    }
2082
2083    #[turbo_tasks::function]
2084    pub fn experimental_swc_env_options(&self) -> Vc<OptionSwcEnvOptions> {
2085        Vc::cell(self.experimental.swc_env_options.clone())
2086    }
2087
2088    #[turbo_tasks::function]
2089    pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
2090        Vc::cell(self.experimental.sri.clone())
2091    }
2092
2093    #[turbo_tasks::function]
2094    pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
2095        Vc::cell(self.experimental.turbopack_use_builtin_babel)
2096    }
2097
2098    #[turbo_tasks::function]
2099    pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
2100        Vc::cell(self.experimental.turbopack_use_builtin_sass)
2101    }
2102
2103    #[turbo_tasks::function]
2104    pub fn experimental_turbopack_local_postcss_config(&self) -> Vc<Option<bool>> {
2105        Vc::cell(self.experimental.turbopack_local_postcss_config)
2106    }
2107
2108    #[turbo_tasks::function]
2109    pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
2110        let options = &self.react_compiler;
2111
2112        let options = match options {
2113            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
2114                OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
2115            }
2116            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
2117                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
2118            ),
2119            _ => OptionalReactCompilerOptions(None),
2120        };
2121
2122        options.cell()
2123    }
2124
2125    /// Returns compilation mode when both `reactCompiler` and `turbopackRustReactCompiler` are set;
2126    /// `None` otherwise.
2127    #[turbo_tasks::function]
2128    pub fn rust_react_compiler(&self) -> Vc<OptionReactCompilerCompilationMode> {
2129        let use_rust = self
2130            .experimental
2131            .turbopack_rust_react_compiler
2132            .unwrap_or(false);
2133        let mode = match (use_rust, &self.react_compiler) {
2134            (true, Some(ReactCompilerOptionsOrBoolean::Boolean(true))) => {
2135                Some(ReactCompilerCompilationMode::Infer)
2136            }
2137            (true, Some(ReactCompilerOptionsOrBoolean::Option(opts))) => {
2138                Some(opts.compilation_mode)
2139            }
2140            _ => None,
2141        };
2142        Vc::cell(mode)
2143    }
2144
2145    #[turbo_tasks::function]
2146    pub fn sass_config(&self) -> Vc<JsonValue> {
2147        Vc::cell(self.sass_options.clone().unwrap_or_default())
2148    }
2149
2150    #[turbo_tasks::function]
2151    pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
2152        Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
2153    }
2154
2155    #[turbo_tasks::function]
2156    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
2157        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
2158    }
2159
2160    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
2161    /// Otherwise, the basePath is used.
2162    #[turbo_tasks::function]
2163    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
2164        let this = self.await?;
2165
2166        Ok(Vc::cell(
2167            format!(
2168                "{}/_next/",
2169                if let Some(asset_prefix) = &this.asset_prefix {
2170                    asset_prefix
2171                } else {
2172                    this.base_path.as_ref().map_or("", |b| b.as_str())
2173                }
2174                .trim_end_matches('/')
2175            )
2176            .into(),
2177        ))
2178    }
2179
2180    /// Returns the suffix to use for chunk loading.
2181    #[turbo_tasks::function]
2182    pub fn asset_suffix_path(&self) -> Vc<Option<RcStr>> {
2183        let needs_dpl_id = self
2184            .experimental
2185            .supports_immutable_assets
2186            .is_none_or(|f| !f);
2187
2188        Vc::cell(
2189            needs_dpl_id
2190                .then_some(self.deployment_id.as_ref())
2191                .flatten()
2192                .map(|id| format!("?dpl={id}").into()),
2193        )
2194    }
2195
2196    /// Whether to enable immutable assets, which uses a different asset suffix, and writes a
2197    /// .next/immutable-static-hashes.json manifest.
2198    #[turbo_tasks::function]
2199    pub fn enable_immutable_assets(&self) -> Vc<bool> {
2200        Vc::cell(self.experimental.supports_immutable_assets == Some(true))
2201    }
2202
2203    #[turbo_tasks::function]
2204    pub fn client_static_folder_name(&self) -> Vc<RcStr> {
2205        Vc::cell(
2206            if self.experimental.supports_immutable_assets == Some(true) {
2207                // Ends up as `_next/static/immutable`
2208                rcstr!("static/immutable")
2209            } else {
2210                rcstr!("static")
2211            },
2212        )
2213    }
2214
2215    #[turbo_tasks::function]
2216    pub fn enable_taint(&self) -> Vc<bool> {
2217        Vc::cell(self.experimental.taint.unwrap_or(false))
2218    }
2219
2220    #[turbo_tasks::function]
2221    pub fn enable_transition_indicator(&self) -> Vc<bool> {
2222        Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
2223    }
2224
2225    #[turbo_tasks::function]
2226    pub fn enable_gesture_transition(&self) -> Vc<bool> {
2227        Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
2228    }
2229
2230    #[turbo_tasks::function]
2231    pub fn enable_blocking_ssr(&self) -> Vc<bool> {
2232        Vc::cell(self.experimental.blocking_ssr.unwrap_or(false))
2233    }
2234
2235    #[turbo_tasks::function]
2236    pub fn enable_cache_components(&self) -> Vc<bool> {
2237        Vc::cell(self.cache_components.unwrap_or(false))
2238    }
2239
2240    #[turbo_tasks::function]
2241    pub fn enable_use_cache(&self) -> Vc<bool> {
2242        Vc::cell(
2243            self.experimental
2244                .use_cache
2245                // "use cache" was originally implicitly enabled with the
2246                // cacheComponents flag, so we transfer the value for cacheComponents to the
2247                // explicit useCache flag to ensure backwards compatibility.
2248                .unwrap_or(self.cache_components.unwrap_or(false)),
2249        )
2250    }
2251
2252    #[turbo_tasks::function]
2253    pub fn is_using_adapter(&self) -> Vc<bool> {
2254        Vc::cell(self.adapter_path.is_some())
2255    }
2256
2257    #[turbo_tasks::function]
2258    pub fn should_append_server_deployment_id_at_runtime(&self) -> Vc<bool> {
2259        let needs_dpl_id = self
2260            .experimental
2261            .supports_immutable_assets
2262            .is_none_or(|f| !f);
2263
2264        Vc::cell(
2265            needs_dpl_id
2266                && self
2267                    .experimental
2268                    .runtime_server_deployment_id
2269                    .unwrap_or(false),
2270        )
2271    }
2272
2273    #[turbo_tasks::function]
2274    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
2275        let mut cache_kinds = CacheKinds::default();
2276
2277        if let Some(handlers) = self.cache_handlers.as_ref() {
2278            cache_kinds.extend(handlers.keys().cloned());
2279        }
2280
2281        cache_kinds.cell()
2282    }
2283
2284    #[turbo_tasks::function]
2285    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
2286        Vc::cell(
2287            self.experimental
2288                .optimize_package_imports
2289                .clone()
2290                .unwrap_or_default(),
2291        )
2292    }
2293
2294    #[turbo_tasks::function]
2295    pub fn tree_shaking_mode_for_foreign_code(
2296        &self,
2297        _is_development: bool,
2298    ) -> Vc<OptionTreeShaking> {
2299        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2300            Some(false) => Some(TreeShakingMode::ReexportsOnly),
2301            Some(true) => Some(TreeShakingMode::ModuleFragments),
2302            None => Some(TreeShakingMode::ReexportsOnly),
2303        })
2304        .cell()
2305    }
2306
2307    #[turbo_tasks::function]
2308    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
2309        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2310            Some(false) => Some(TreeShakingMode::ReexportsOnly),
2311            Some(true) => Some(TreeShakingMode::ModuleFragments),
2312            None => Some(TreeShakingMode::ReexportsOnly),
2313        })
2314        .cell()
2315    }
2316
2317    #[turbo_tasks::function]
2318    pub async fn turbopack_remove_unused_imports(
2319        self: Vc<Self>,
2320        mode: Vc<NextMode>,
2321    ) -> Result<Vc<bool>> {
2322        let remove_unused_imports = self
2323            .await?
2324            .experimental
2325            .turbopack_remove_unused_imports
2326            .unwrap_or(matches!(*mode.await?, NextMode::Build));
2327
2328        if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2329            bail!(
2330                "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2331                 enabling `experimental.turbopackRemoveUnusedExports`"
2332            );
2333        }
2334
2335        Ok(Vc::cell(remove_unused_imports))
2336    }
2337
2338    #[turbo_tasks::function]
2339    pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2340        Ok(Vc::cell(
2341            self.experimental
2342                .turbopack_remove_unused_exports
2343                .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2344        ))
2345    }
2346
2347    #[turbo_tasks::function]
2348    pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2349        Vc::cell(
2350            self.experimental
2351                .turbopack_infer_module_side_effects
2352                .unwrap_or(true),
2353        )
2354    }
2355
2356    #[turbo_tasks::function]
2357    pub fn turbopack_plugin_runtime_strategy(&self) -> Vc<TurbopackPluginRuntimeStrategy> {
2358        #[cfg(feature = "process_pool")]
2359        let default = TurbopackPluginRuntimeStrategy::ChildProcesses;
2360        #[cfg(all(feature = "worker_pool", not(feature = "process_pool")))]
2361        let default = TurbopackPluginRuntimeStrategy::WorkerThreads;
2362
2363        self.experimental
2364            .turbopack_plugin_runtime_strategy
2365            .unwrap_or(default)
2366            .cell()
2367    }
2368
2369    #[turbo_tasks::function]
2370    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2371        Ok(match *mode.await? {
2372            // Ignore configuration in development mode, HMR only works with `named`
2373            NextMode::Development => ModuleIds::Named.cell(),
2374            NextMode::Build => self
2375                .experimental
2376                .turbopack_module_ids
2377                .unwrap_or(ModuleIds::Deterministic)
2378                .cell(),
2379        })
2380    }
2381
2382    #[turbo_tasks::function]
2383    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2384        let minify = self.experimental.turbopack_minify;
2385        Ok(Vc::cell(
2386            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2387        ))
2388    }
2389
2390    #[turbo_tasks::function]
2391    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2392        Ok(Vc::cell(match *mode.await? {
2393            // Ignore configuration in development mode to not break HMR
2394            NextMode::Development => false,
2395            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2396        }))
2397    }
2398
2399    #[turbo_tasks::function]
2400    pub async fn turbo_nested_async_chunking(
2401        &self,
2402        mode: Vc<NextMode>,
2403        client_side: bool,
2404    ) -> Result<Vc<bool>> {
2405        let option = if client_side {
2406            self.experimental
2407                .turbopack_client_side_nested_async_chunking
2408        } else {
2409            self.experimental
2410                .turbopack_server_side_nested_async_chunking
2411        };
2412        Ok(Vc::cell(if let Some(value) = option {
2413            value
2414        } else {
2415            match *mode.await? {
2416                NextMode::Development => false,
2417                NextMode::Build => client_side,
2418            }
2419        }))
2420    }
2421
2422    #[turbo_tasks::function]
2423    pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2424        Vc::cell(
2425            self.experimental
2426                .turbopack_import_type_bytes
2427                .unwrap_or(false),
2428        )
2429    }
2430
2431    #[turbo_tasks::function]
2432    pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2433        Vc::cell(
2434            self.experimental
2435                .turbopack_import_type_text
2436                .unwrap_or(false),
2437        )
2438    }
2439
2440    #[turbo_tasks::function]
2441    pub fn lightningcss_feature_flags(
2442        &self,
2443    ) -> Result<Vc<turbopack_css::LightningCssFeatureFlags>> {
2444        Ok(turbopack_css::LightningCssFeatureFlags {
2445            include: lightningcss_features_field_mask(
2446                &self.experimental.lightning_css_features,
2447                |f| f.include.as_ref(),
2448            )?,
2449            exclude: lightningcss_features_field_mask(
2450                &self.experimental.lightning_css_features,
2451                |f| f.exclude.as_ref(),
2452            )?,
2453        }
2454        .cell())
2455    }
2456
2457    #[turbo_tasks::function]
2458    pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2459        let input_source_maps = self
2460            .experimental
2461            .turbopack_input_source_maps
2462            .unwrap_or(true);
2463        let source_maps = self
2464            .experimental
2465            .turbopack_source_maps
2466            .unwrap_or(match &*mode.await? {
2467                NextMode::Development => true,
2468                NextMode::Build => self.production_browser_source_maps,
2469            });
2470        Ok(match (source_maps, input_source_maps) {
2471            (true, true) => SourceMapsType::Full,
2472            (true, false) => SourceMapsType::Partial,
2473            (false, _) => SourceMapsType::None,
2474        }
2475        .cell())
2476    }
2477
2478    #[turbo_tasks::function]
2479    pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2480        let input_source_maps = self
2481            .experimental
2482            .turbopack_input_source_maps
2483            .unwrap_or(true);
2484        let source_maps = self
2485            .experimental
2486            .turbopack_source_maps
2487            .or(self.experimental.server_source_maps)
2488            .unwrap_or(true);
2489        Ok(match (source_maps, input_source_maps) {
2490            (true, true) => SourceMapsType::Full,
2491            (true, false) => SourceMapsType::Partial,
2492            (false, _) => SourceMapsType::None,
2493        }
2494        .cell())
2495    }
2496
2497    #[turbo_tasks::function]
2498    pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2499        Vc::cell(
2500            self.turbopack
2501                .as_ref()
2502                .and_then(|turbopack| turbopack.debug_ids)
2503                .unwrap_or(false),
2504        )
2505    }
2506
2507    /// Returns the resolved worker chunk base path with `/_next/` appended,
2508    /// or `None` to fall back to the regular chunk base path.
2509    #[turbo_tasks::function]
2510    pub fn turbopack_worker_asset_prefix(&self) -> Vc<Option<RcStr>> {
2511        Vc::cell(
2512            self.experimental
2513                .turbopack_worker_asset_prefix
2514                .as_ref()
2515                .map(|prefix| format!("{}/_next/", prefix.trim_end_matches('/')).into()),
2516        )
2517    }
2518
2519    #[turbo_tasks::function]
2520    pub fn turbopack_chunk_loading_global(&self) -> Vc<Option<RcStr>> {
2521        Vc::cell(
2522            self.turbopack
2523                .as_ref()
2524                .and_then(|t| t.chunk_loading_global.clone()),
2525        )
2526    }
2527
2528    #[turbo_tasks::function]
2529    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2530        Ok(Vc::cell(
2531            self.typescript
2532                .tsconfig_path
2533                .as_ref()
2534                .map(|path| path.to_owned().into()),
2535        ))
2536    }
2537
2538    #[turbo_tasks::function]
2539    pub fn cross_origin(&self) -> Vc<CrossOrigin> {
2540        *self.cross_origin.resolved_cell()
2541    }
2542
2543    #[turbo_tasks::function]
2544    pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2545        Vc::cell(self.i18n.clone())
2546    }
2547
2548    #[turbo_tasks::function]
2549    pub fn output(&self) -> Vc<OptionOutputType> {
2550        Vc::cell(self.output.clone())
2551    }
2552
2553    #[turbo_tasks::function]
2554    pub async fn output_file_tracing_includes(
2555        &self,
2556        project_path: FileSystemPath,
2557    ) -> Result<Vc<OutputFileTracingIncludesExcludes>> {
2558        Ok(OutputFileTracingIncludesExcludes::parse(
2559            project_path,
2560            &self.output_file_tracing_includes,
2561        )
2562        .await?
2563        .cell())
2564    }
2565
2566    #[turbo_tasks::function]
2567    pub async fn output_file_tracing_excludes(
2568        &self,
2569        project_path: FileSystemPath,
2570    ) -> Result<Vc<OutputFileTracingIncludesExcludes>> {
2571        Ok(OutputFileTracingIncludesExcludes::parse(
2572            project_path,
2573            &self.output_file_tracing_excludes,
2574        )
2575        .await?
2576        .cell())
2577    }
2578
2579    #[turbo_tasks::function]
2580    pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2581        FetchClientConfig::default().cell()
2582    }
2583
2584    #[turbo_tasks::function]
2585    pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2586        match self.experimental.report_system_env_inlining.as_deref() {
2587            None => Ok(IssueSeverity::Suggestion.cell()),
2588            Some("warn") => Ok(IssueSeverity::Warning.cell()),
2589            Some("error") => Ok(IssueSeverity::Error.cell()),
2590            _ => bail!(
2591                "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2592            ),
2593        }
2594    }
2595
2596    /// Returns the list of ignore-issue rules from the turbopack config,
2597    /// converted to the `IgnoreIssue` type used by `IssueFilter`.
2598    #[turbo_tasks::function]
2599    pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2600        let rules = self
2601            .turbopack
2602            .as_ref()
2603            .and_then(|tp| tp.ignore_issue.as_deref())
2604            .unwrap_or_default()
2605            .iter()
2606            .map(|rule| {
2607                Ok(IgnoreIssue {
2608                    path: rule.path.to_ignore_pattern()?,
2609                    title: rule
2610                        .title
2611                        .as_ref()
2612                        .map(|t| t.to_ignore_pattern())
2613                        .transpose()?,
2614                    description: rule
2615                        .description
2616                        .as_ref()
2617                        .map(|d| d.to_ignore_pattern())
2618                        .transpose()?,
2619                })
2620            })
2621            .collect::<Result<_>>()?;
2622        Ok(Vc::cell(rules))
2623    }
2624
2625    #[turbo_tasks::function]
2626    pub fn output_hash_salt(&self) -> Vc<RcStr> {
2627        Vc::cell(
2628            self.experimental
2629                .output_hash_salt
2630                .clone()
2631                .unwrap_or_default(),
2632        )
2633    }
2634}
2635
2636/// A subset of ts/jsconfig that next.js implicitly
2637/// interops with.
2638#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2639#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2640#[serde(rename_all = "camelCase")]
2641pub struct JsConfig {
2642    #[bincode(with = "turbo_bincode::serde_self_describing")]
2643    compiler_options: Option<serde_json::Value>,
2644}
2645
2646#[turbo_tasks::value_impl]
2647impl JsConfig {
2648    #[turbo_tasks::function]
2649    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2650        let string = string.await?;
2651        let config: JsConfig = serde_json::from_str(&string)
2652            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2653
2654        Ok(config.cell())
2655    }
2656
2657    #[turbo_tasks::function]
2658    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2659        Vc::cell(self.compiler_options.clone().unwrap_or_default())
2660    }
2661}
2662
2663/// Extract either the `include` or `exclude` field from `LightningCssFeatures`
2664/// and convert the feature names to a bitmask.
2665fn lightningcss_features_field_mask(
2666    features: &Option<LightningCssFeatures>,
2667    field: impl FnOnce(&LightningCssFeatures) -> Option<&Vec<RcStr>>,
2668) -> Result<u32> {
2669    features
2670        .as_ref()
2671        .and_then(field)
2672        .map(|names| lightningcss_feature_names_to_mask(names))
2673        .unwrap_or(Ok(0))
2674}
2675
2676/// Convert dash-case feature name strings to a lightningcss `Features` bitmask.
2677///
2678/// Uses the canonical `Features` constants from the lightningcss crate.
2679/// Composite names (`selectors`, `media-queries`, `colors`) OR together the
2680/// bits of their constituent individual features.
2681///
2682/// Feature names must match: `packages/next/src/server/config-shared.ts`
2683/// (`LIGHTNINGCSS_FEATURE_NAMES`)
2684pub fn lightningcss_feature_names_to_mask(
2685    names: &[impl std::ops::Deref<Target = str>],
2686) -> Result<u32> {
2687    use lightningcss::targets::Features;
2688    let mut mask = Features::empty();
2689    for name in names {
2690        mask |= match &**name {
2691            "nesting" => Features::Nesting,
2692            "not-selector-list" => Features::NotSelectorList,
2693            "dir-selector" => Features::DirSelector,
2694            "lang-selector-list" => Features::LangSelectorList,
2695            "is-selector" => Features::IsSelector,
2696            "text-decoration-thickness-percent" => Features::TextDecorationThicknessPercent,
2697            "media-interval-syntax" => Features::MediaIntervalSyntax,
2698            "media-range-syntax" => Features::MediaRangeSyntax,
2699            "custom-media-queries" => Features::CustomMediaQueries,
2700            "clamp-function" => Features::ClampFunction,
2701            "color-function" => Features::ColorFunction,
2702            "oklab-colors" => Features::OklabColors,
2703            "lab-colors" => Features::LabColors,
2704            "p3-colors" => Features::P3Colors,
2705            "hex-alpha-colors" => Features::HexAlphaColors,
2706            "space-separated-color-notation" => Features::SpaceSeparatedColorNotation,
2707            "font-family-system-ui" => Features::FontFamilySystemUi,
2708            "double-position-gradients" => Features::DoublePositionGradients,
2709            "vendor-prefixes" => Features::VendorPrefixes,
2710            "logical-properties" => Features::LogicalProperties,
2711            "light-dark" => Features::LightDark,
2712            // Composite groups
2713            "selectors" => Features::Selectors,
2714            "media-queries" => Features::MediaQueries,
2715            "colors" => Features::Colors,
2716            _ => bail!("Unknown lightningcss feature: {}", &**name),
2717        };
2718    }
2719    Ok(mask.bits())
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724    use super::*;
2725
2726    #[test]
2727    fn test_serde_rule_config_item_options() {
2728        let json_value = serde_json::json!({
2729            "loaders": [],
2730            "as": "*.js",
2731            "condition": {
2732                "all": [
2733                    "production",
2734                    {"not": "foreign"},
2735                    {"any": [
2736                        "browser",
2737                        {
2738                            "path": { "type": "glob", "value": "*.svg"},
2739                            "query": {
2740                                "type": "regex",
2741                                "value": {
2742                                    "source": "@someQuery",
2743                                    "flags": ""
2744                                }
2745                            },
2746                            "content": {
2747                                "source": "@someTag",
2748                                "flags": ""
2749                            }
2750                        }
2751                    ]},
2752                ],
2753            }
2754        });
2755
2756        let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2757
2758        assert_eq!(
2759            rule_config,
2760            RuleConfigItem {
2761                loaders: vec![],
2762                rename_as: Some(rcstr!("*.js")),
2763                module_type: None,
2764                condition: Some(ConfigConditionItem::All(
2765                    [
2766                        ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2767                        ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2768                            WebpackLoaderBuiltinCondition::Foreign
2769                        ))),
2770                        ConfigConditionItem::Any(
2771                            vec![
2772                                ConfigConditionItem::Builtin(
2773                                    WebpackLoaderBuiltinCondition::Browser
2774                                ),
2775                                ConfigConditionItem::Base {
2776                                    path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2777                                    content: Some(RegexComponents {
2778                                        source: rcstr!("@someTag"),
2779                                        flags: rcstr!(""),
2780                                    }),
2781                                    query: Some(ConfigConditionQuery::Regex(RegexComponents {
2782                                        source: rcstr!("@someQuery"),
2783                                        flags: rcstr!(""),
2784                                    })),
2785                                    content_type: None,
2786                                },
2787                            ]
2788                            .into(),
2789                        ),
2790                    ]
2791                    .into(),
2792                )),
2793            }
2794        );
2795    }
2796}