Skip to main content

next_core/
next_config.rs

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