next_core/
next_config.rs

1use anyhow::{Context, Result, bail};
2use rustc_hash::FxHashSet;
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value as JsonValue;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{
7    FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
8    trace::TraceRawVcs,
9};
10use turbo_tasks_env::EnvMap;
11use turbo_tasks_fs::FileSystemPath;
12use turbopack::module_options::{
13    LoaderRuleItem, OptionWebpackRules,
14    module_options_context::{
15        ConditionItem, ConditionPath, MdxTransformOptions, OptionWebpackConditions,
16    },
17};
18use turbopack_core::{
19    issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString},
20    resolve::ResolveAliasMap,
21};
22use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
23use turbopack_ecmascript_plugins::transform::{
24    emotion::EmotionTransformConfig, relay::RelayConfig,
25    styled_components::StyledComponentsTransformConfig,
26};
27use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
28
29use crate::{
30    mode::NextMode, next_import_map::mdx_import_source_file,
31    next_shared::transforms::ModularizeImportPackageConfig,
32};
33
34#[turbo_tasks::value]
35struct NextConfigAndCustomRoutes {
36    config: ResolvedVc<NextConfig>,
37    custom_routes: ResolvedVc<CustomRoutes>,
38}
39
40#[turbo_tasks::value]
41struct CustomRoutes {
42    rewrites: ResolvedVc<Rewrites>,
43}
44
45#[turbo_tasks::value(transparent)]
46pub struct ModularizeImports(FxIndexMap<String, ModularizeImportPackageConfig>);
47
48#[turbo_tasks::value(transparent)]
49#[derive(Clone, Debug)]
50pub struct CacheKinds(FxHashSet<RcStr>);
51
52impl CacheKinds {
53    pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
54        self.0.extend(iter);
55    }
56}
57
58impl Default for CacheKinds {
59    fn default() -> Self {
60        CacheKinds(["default", "remote"].iter().map(|&s| s.into()).collect())
61    }
62}
63
64#[turbo_tasks::value(serialization = "custom", eq = "manual")]
65#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, OperationValue)]
66#[serde(rename_all = "camelCase")]
67pub struct NextConfig {
68    // TODO all fields should be private and access should be wrapped within a turbo-tasks function
69    // Otherwise changing NextConfig will lead to invalidating all tasks accessing it.
70    pub config_file: Option<RcStr>,
71    pub config_file_name: RcStr,
72
73    /// In-memory cache size in bytes.
74    ///
75    /// If `cache_max_memory_size: 0` disables in-memory caching.
76    pub cache_max_memory_size: Option<f64>,
77    /// custom path to a cache handler to use
78    pub cache_handler: Option<RcStr>,
79
80    pub env: FxIndexMap<String, JsonValue>,
81    pub experimental: ExperimentalConfig,
82    pub images: ImageConfig,
83    pub page_extensions: Vec<RcStr>,
84    pub react_production_profiling: Option<bool>,
85    pub react_strict_mode: Option<bool>,
86    pub transpile_packages: Option<Vec<RcStr>>,
87    pub modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
88    pub dist_dir: Option<RcStr>,
89    pub deployment_id: Option<RcStr>,
90    sass_options: Option<serde_json::Value>,
91    pub trailing_slash: Option<bool>,
92    pub asset_prefix: Option<RcStr>,
93    pub base_path: Option<RcStr>,
94    pub skip_middleware_url_normalize: Option<bool>,
95    pub skip_trailing_slash_redirect: Option<bool>,
96    pub i18n: Option<I18NConfig>,
97    pub cross_origin: Option<CrossOriginConfig>,
98    pub dev_indicators: Option<DevIndicatorsConfig>,
99    pub output: Option<OutputType>,
100    pub turbopack: Option<TurbopackConfig>,
101    production_browser_source_maps: bool,
102
103    /// Enables the bundling of node_modules packages (externals) for pages
104    /// server-side bundles.
105    ///
106    /// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies)
107    pub bundle_pages_router_dependencies: Option<bool>,
108
109    /// A list of packages that should be treated as external on the server
110    /// build.
111    ///
112    /// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages)
113    pub server_external_packages: Option<Vec<RcStr>>,
114
115    #[serde(rename = "_originalRedirects")]
116    pub original_redirects: Option<Vec<Redirect>>,
117
118    // Partially supported
119    pub compiler: Option<CompilerConfig>,
120
121    pub optimize_fonts: Option<bool>,
122
123    // unsupported
124    amp: AmpConfig,
125    clean_dist_dir: bool,
126    compress: bool,
127    eslint: EslintConfig,
128    exclude_default_moment_locales: bool,
129    // this can be a function in js land
130    export_path_map: Option<serde_json::Value>,
131    // this is a function in js land
132    generate_build_id: Option<serde_json::Value>,
133    generate_etags: bool,
134    http_agent_options: HttpAgentConfig,
135    on_demand_entries: OnDemandEntriesConfig,
136    powered_by_header: bool,
137    public_runtime_config: FxIndexMap<String, serde_json::Value>,
138    server_runtime_config: FxIndexMap<String, serde_json::Value>,
139    static_page_generation_timeout: f64,
140    target: Option<String>,
141    typescript: TypeScriptConfig,
142    use_file_system_public_routes: bool,
143    webpack: Option<serde_json::Value>,
144}
145
146#[derive(
147    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
148)]
149#[serde(rename_all = "kebab-case")]
150pub enum CrossOriginConfig {
151    Anonymous,
152    UseCredentials,
153}
154
155#[derive(
156    Clone,
157    Debug,
158    Default,
159    PartialEq,
160    Serialize,
161    Deserialize,
162    TraceRawVcs,
163    NonLocalValue,
164    OperationValue,
165)]
166#[serde(rename_all = "camelCase")]
167struct AmpConfig {
168    canonical_base: Option<String>,
169}
170
171#[derive(
172    Clone,
173    Debug,
174    Default,
175    PartialEq,
176    Serialize,
177    Deserialize,
178    TraceRawVcs,
179    NonLocalValue,
180    OperationValue,
181)]
182#[serde(rename_all = "camelCase")]
183struct EslintConfig {
184    dirs: Option<Vec<String>>,
185    ignore_during_builds: Option<bool>,
186}
187
188#[derive(
189    Clone,
190    Debug,
191    Default,
192    PartialEq,
193    Serialize,
194    Deserialize,
195    TraceRawVcs,
196    NonLocalValue,
197    OperationValue,
198)]
199#[serde(rename_all = "kebab-case")]
200pub enum BuildActivityPositions {
201    #[default]
202    BottomRight,
203    BottomLeft,
204    TopRight,
205    TopLeft,
206}
207
208#[derive(
209    Clone,
210    Debug,
211    Default,
212    PartialEq,
213    Serialize,
214    Deserialize,
215    TraceRawVcs,
216    NonLocalValue,
217    OperationValue,
218)]
219#[serde(rename_all = "camelCase")]
220pub struct DevIndicatorsOptions {
221    pub build_activity_position: Option<BuildActivityPositions>,
222    pub position: Option<BuildActivityPositions>,
223}
224
225#[derive(
226    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
227)]
228#[serde(untagged)]
229pub enum DevIndicatorsConfig {
230    WithOptions(DevIndicatorsOptions),
231    Boolean(bool),
232}
233
234#[derive(
235    Clone,
236    Debug,
237    Default,
238    PartialEq,
239    Serialize,
240    Deserialize,
241    TraceRawVcs,
242    NonLocalValue,
243    OperationValue,
244)]
245#[serde(rename_all = "camelCase")]
246struct OnDemandEntriesConfig {
247    max_inactive_age: f64,
248    pages_buffer_length: f64,
249}
250
251#[derive(
252    Clone,
253    Debug,
254    Default,
255    PartialEq,
256    Serialize,
257    Deserialize,
258    TraceRawVcs,
259    NonLocalValue,
260    OperationValue,
261)]
262#[serde(rename_all = "camelCase")]
263struct HttpAgentConfig {
264    keep_alive: bool,
265}
266
267#[derive(
268    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
269)]
270#[serde(rename_all = "camelCase")]
271pub struct DomainLocale {
272    pub default_locale: String,
273    pub domain: String,
274    pub http: Option<bool>,
275    pub locales: Option<Vec<String>>,
276}
277
278#[derive(
279    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
280)]
281#[serde(rename_all = "camelCase")]
282pub struct I18NConfig {
283    pub default_locale: String,
284    pub domains: Option<Vec<DomainLocale>>,
285    pub locale_detection: Option<bool>,
286    pub locales: Vec<String>,
287}
288
289#[derive(
290    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
291)]
292#[serde(rename_all = "kebab-case")]
293pub enum OutputType {
294    Standalone,
295    Export,
296}
297
298#[derive(
299    Debug,
300    Clone,
301    Hash,
302    Eq,
303    PartialEq,
304    Ord,
305    PartialOrd,
306    TaskInput,
307    TraceRawVcs,
308    Serialize,
309    Deserialize,
310    NonLocalValue,
311    OperationValue,
312)]
313#[serde(tag = "type", rename_all = "kebab-case")]
314pub enum RouteHas {
315    Header {
316        key: RcStr,
317        #[serde(skip_serializing_if = "Option::is_none")]
318        value: Option<RcStr>,
319    },
320    Cookie {
321        key: RcStr,
322        #[serde(skip_serializing_if = "Option::is_none")]
323        value: Option<RcStr>,
324    },
325    Query {
326        key: RcStr,
327        #[serde(skip_serializing_if = "Option::is_none")]
328        value: Option<RcStr>,
329    },
330    Host {
331        value: RcStr,
332    },
333}
334
335#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
336#[serde(rename_all = "camelCase")]
337pub struct HeaderValue {
338    pub key: RcStr,
339    pub value: RcStr,
340}
341
342#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
343#[serde(rename_all = "camelCase")]
344pub struct Header {
345    pub source: String,
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub base_path: Option<bool>,
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub locale: Option<bool>,
350    pub headers: Vec<HeaderValue>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub has: Option<Vec<RouteHas>>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub missing: Option<Vec<RouteHas>>,
355}
356
357#[derive(
358    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
359)]
360#[serde(rename_all = "camelCase")]
361pub enum RedirectStatus {
362    StatusCode(f64),
363    Permanent(bool),
364}
365
366#[derive(
367    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
368)]
369#[serde(rename_all = "camelCase")]
370pub struct Redirect {
371    pub source: String,
372    pub destination: String,
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub base_path: Option<bool>,
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub locale: Option<bool>,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub has: Option<Vec<RouteHas>>,
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub missing: Option<Vec<RouteHas>>,
381
382    #[serde(flatten)]
383    pub status: RedirectStatus,
384}
385
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
387#[serde(rename_all = "camelCase")]
388pub struct Rewrite {
389    pub source: String,
390    pub destination: String,
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub base_path: Option<bool>,
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub locale: Option<bool>,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub has: Option<Vec<RouteHas>>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub missing: Option<Vec<RouteHas>>,
399}
400
401#[turbo_tasks::value(eq = "manual")]
402#[derive(Clone, Debug, Default, PartialEq)]
403#[serde(rename_all = "camelCase")]
404pub struct Rewrites {
405    pub before_files: Vec<Rewrite>,
406    pub after_files: Vec<Rewrite>,
407    pub fallback: Vec<Rewrite>,
408}
409
410#[derive(
411    Clone,
412    Debug,
413    Default,
414    PartialEq,
415    Serialize,
416    Deserialize,
417    TraceRawVcs,
418    NonLocalValue,
419    OperationValue,
420)]
421#[serde(rename_all = "camelCase")]
422pub struct TypeScriptConfig {
423    pub ignore_build_errors: Option<bool>,
424    pub tsconfig_path: Option<String>,
425}
426
427#[turbo_tasks::value(eq = "manual", operation)]
428#[derive(Clone, Debug, PartialEq)]
429#[serde(rename_all = "camelCase")]
430pub struct ImageConfig {
431    pub device_sizes: Vec<u16>,
432    pub image_sizes: Vec<u16>,
433    pub path: String,
434    pub loader: ImageLoader,
435    #[serde(deserialize_with = "empty_string_is_none")]
436    pub loader_file: Option<String>,
437    pub domains: Vec<String>,
438    pub disable_static_images: bool,
439    #[serde(rename = "minimumCacheTTL")]
440    pub minimum_cache_ttl: u64,
441    pub formats: Vec<ImageFormat>,
442    #[serde(rename = "dangerouslyAllowSVG")]
443    pub dangerously_allow_svg: bool,
444    pub content_security_policy: String,
445    pub remote_patterns: Vec<RemotePattern>,
446    pub unoptimized: bool,
447}
448
449fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
450where
451    D: Deserializer<'de>,
452{
453    let o = Option::<String>::deserialize(deserializer)?;
454    Ok(o.filter(|s| !s.is_empty()))
455}
456
457impl Default for ImageConfig {
458    fn default() -> Self {
459        // https://github.com/vercel/next.js/blob/327634eb/packages/next/shared/lib/image-config.ts#L100-L114
460        Self {
461            device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
462            image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384],
463            path: "/_next/image".to_string(),
464            loader: ImageLoader::Default,
465            loader_file: None,
466            domains: vec![],
467            disable_static_images: false,
468            minimum_cache_ttl: 60,
469            formats: vec![ImageFormat::Webp],
470            dangerously_allow_svg: false,
471            content_security_policy: "".to_string(),
472            remote_patterns: vec![],
473            unoptimized: false,
474        }
475    }
476}
477
478#[derive(
479    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
480)]
481#[serde(rename_all = "kebab-case")]
482pub enum ImageLoader {
483    Default,
484    Imgix,
485    Cloudinary,
486    Akamai,
487    Custom,
488}
489
490#[derive(
491    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
492)]
493pub enum ImageFormat {
494    #[serde(rename = "image/webp")]
495    Webp,
496    #[serde(rename = "image/avif")]
497    Avif,
498}
499
500#[derive(
501    Clone,
502    Debug,
503    Default,
504    PartialEq,
505    Serialize,
506    Deserialize,
507    TraceRawVcs,
508    NonLocalValue,
509    OperationValue,
510)]
511#[serde(rename_all = "camelCase")]
512pub struct RemotePattern {
513    pub hostname: String,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub protocol: Option<RemotePatternProtocal>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub port: Option<String>,
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub pathname: Option<String>,
520}
521
522#[derive(
523    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
524)]
525#[serde(rename_all = "kebab-case")]
526pub enum RemotePatternProtocal {
527    Http,
528    Https,
529}
530
531#[derive(
532    Clone,
533    Debug,
534    Default,
535    PartialEq,
536    Serialize,
537    Deserialize,
538    TraceRawVcs,
539    NonLocalValue,
540    OperationValue,
541)]
542#[serde(rename_all = "camelCase")]
543pub struct TurbopackConfig {
544    /// This option has been replaced by `rules`.
545    pub loaders: Option<JsonValue>,
546    pub rules: Option<FxIndexMap<RcStr, RuleConfigItemOrShortcut>>,
547    #[turbo_tasks(trace_ignore)]
548    pub conditions: Option<FxIndexMap<RcStr, ConfigConditionItem>>,
549    pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
550    pub resolve_extensions: Option<Vec<RcStr>>,
551    pub module_ids: Option<ModuleIds>,
552}
553
554#[derive(Clone, Debug, PartialEq, Serialize, TraceRawVcs, NonLocalValue)]
555pub struct ConfigConditionItem(ConditionItem);
556
557impl<'de> Deserialize<'de> for ConfigConditionItem {
558    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
559    where
560        D: Deserializer<'de>,
561    {
562        #[derive(Deserialize)]
563        struct RegexComponents {
564            source: RcStr,
565            flags: RcStr,
566        }
567
568        #[derive(Deserialize)]
569        struct ConfigPath {
570            path: RegexOrGlob,
571        }
572
573        #[derive(Deserialize)]
574        #[serde(tag = "type", rename_all = "lowercase")]
575        enum RegexOrGlob {
576            Regexp { value: RegexComponents },
577            Glob { value: String },
578        }
579
580        let config_path = ConfigPath::deserialize(deserializer)?;
581        let condition_item = match config_path.path {
582            RegexOrGlob::Regexp { value } => {
583                let regex = turbo_esregex::EsRegex::new(&value.source, &value.flags)
584                    .map_err(serde::de::Error::custom)?;
585                ConditionItem {
586                    path: ConditionPath::Regex(regex.resolved_cell()),
587                }
588            }
589            RegexOrGlob::Glob { value } => ConditionItem {
590                path: ConditionPath::Glob(value.into()),
591            },
592        };
593
594        Ok(ConfigConditionItem(condition_item))
595    }
596}
597
598#[derive(
599    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
600)]
601#[serde(rename_all = "camelCase")]
602pub struct RuleConfigItemOptions {
603    pub loaders: Vec<LoaderItem>,
604    #[serde(default, alias = "as")]
605    pub rename_as: Option<RcStr>,
606}
607
608#[derive(
609    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
610)]
611#[serde(rename_all = "camelCase", untagged)]
612pub enum RuleConfigItemOrShortcut {
613    Loaders(Vec<LoaderItem>),
614    Advanced(RuleConfigItem),
615}
616
617#[derive(
618    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
619)]
620#[serde(rename_all = "camelCase", untagged)]
621pub enum RuleConfigItem {
622    Options(RuleConfigItemOptions),
623    Conditional(FxIndexMap<RcStr, RuleConfigItem>),
624    Boolean(bool),
625}
626
627#[derive(
628    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
629)]
630#[serde(untagged)]
631pub enum LoaderItem {
632    LoaderName(RcStr),
633    LoaderOptions(WebpackLoaderItem),
634}
635
636#[turbo_tasks::value(operation)]
637#[derive(Copy, Clone, Debug)]
638#[serde(rename_all = "camelCase")]
639pub enum ModuleIds {
640    Named,
641    Deterministic,
642}
643
644#[turbo_tasks::value(transparent)]
645pub struct OptionModuleIds(pub Option<ModuleIds>);
646
647#[derive(
648    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
649)]
650#[serde(untagged)]
651pub enum MdxRsOptions {
652    Boolean(bool),
653    Option(MdxTransformOptions),
654}
655
656#[turbo_tasks::value(shared, operation)]
657#[derive(Clone, Debug)]
658#[serde(rename_all = "camelCase")]
659pub enum ReactCompilerMode {
660    Infer,
661    Annotation,
662    All,
663}
664
665/// Subset of react compiler options
666#[turbo_tasks::value(shared, operation)]
667#[derive(Clone, Debug)]
668#[serde(rename_all = "camelCase")]
669pub struct ReactCompilerOptions {
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub compilation_mode: Option<ReactCompilerMode>,
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub panic_threshold: Option<RcStr>,
674}
675
676#[derive(
677    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
678)]
679#[serde(untagged)]
680pub enum ReactCompilerOptionsOrBoolean {
681    Boolean(bool),
682    Option(ReactCompilerOptions),
683}
684
685#[turbo_tasks::value(transparent)]
686pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
687
688#[derive(
689    Clone,
690    Debug,
691    Default,
692    PartialEq,
693    Serialize,
694    Deserialize,
695    TraceRawVcs,
696    ValueDebugFormat,
697    NonLocalValue,
698    OperationValue,
699)]
700#[serde(rename_all = "camelCase")]
701pub struct ExperimentalConfig {
702    // all fields should be private and access should be wrapped within a turbo-tasks function
703    // Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
704    allowed_revalidate_header_keys: Option<Vec<RcStr>>,
705    client_router_filter: Option<bool>,
706    /// decimal for percent for possible false positives e.g. 0.01 for 10%
707    /// potential false matches lower percent increases size of the filter
708    client_router_filter_allowed_rate: Option<f64>,
709    client_router_filter_redirects: Option<bool>,
710    fetch_cache_key_prefix: Option<RcStr>,
711    isr_flush_to_disk: Option<bool>,
712    /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
713    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
714    mdx_rs: Option<MdxRsOptions>,
715    strict_next_head: Option<bool>,
716    swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
717    external_middleware_rewrites_resolve: Option<bool>,
718    scroll_restoration: Option<bool>,
719    manual_client_base_path: Option<bool>,
720    optimistic_client_cache: Option<bool>,
721    middleware_prefetch: Option<MiddlewarePrefetchType>,
722    /// optimizeCss can be boolean or critters' option object
723    /// 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))
724    optimize_css: Option<serde_json::Value>,
725    next_script_workers: Option<bool>,
726    web_vitals_attribution: Option<Vec<RcStr>>,
727    server_actions: Option<ServerActionsOrLegacyBool>,
728    sri: Option<SubResourceIntegrity>,
729    react_compiler: Option<ReactCompilerOptionsOrBoolean>,
730    #[serde(rename = "dynamicIO")]
731    dynamic_io: Option<bool>,
732    use_cache: Option<bool>,
733    // ---
734    // UNSUPPORTED
735    // ---
736    adjust_font_fallbacks: Option<bool>,
737    adjust_font_fallbacks_with_size_adjust: Option<bool>,
738    after: Option<bool>,
739    amp: Option<serde_json::Value>,
740    app_document_preloading: Option<bool>,
741    cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
742    cache_life: Option<FxIndexMap<String, CacheLifeProfile>>,
743    case_sensitive_routes: Option<bool>,
744    cpus: Option<f64>,
745    cra_compat: Option<bool>,
746    disable_optimized_loading: Option<bool>,
747    disable_postcss_preset_env: Option<bool>,
748    esm_externals: Option<EsmExternals>,
749    extension_alias: Option<serde_json::Value>,
750    external_dir: Option<bool>,
751    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
752    /// in the browser Full list of old polyfills is accessible here:
753    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
754    fallback_node_polyfills: Option<bool>, // false
755    force_swc_transforms: Option<bool>,
756    fully_specified: Option<bool>,
757    gzip_size: Option<bool>,
758
759    pub inline_css: Option<bool>,
760    instrumentation_hook: Option<bool>,
761    client_trace_metadata: Option<Vec<String>>,
762    large_page_data_bytes: Option<f64>,
763    logging: Option<serde_json::Value>,
764    memory_based_workers_count: Option<bool>,
765    /// Optimize React APIs for server builds.
766    optimize_server_react: Option<bool>,
767    /// Automatically apply the "modularize_imports" optimization to imports of
768    /// the specified packages.
769    optimize_package_imports: Option<Vec<RcStr>>,
770    output_file_tracing_ignores: Option<Vec<RcStr>>,
771    output_file_tracing_includes: Option<serde_json::Value>,
772    output_file_tracing_root: Option<RcStr>,
773    /// Using this feature will enable the `react@experimental` for the `app`
774    /// directory.
775    ppr: Option<ExperimentalPartialPrerendering>,
776    taint: Option<bool>,
777    #[serde(rename = "routerBFCache")]
778    router_bfcache: Option<bool>,
779    proxy_timeout: Option<f64>,
780    /// enables the minification of server code.
781    server_minification: Option<bool>,
782    /// Enables source maps generation for the server production bundle.
783    server_source_maps: Option<bool>,
784    swc_trace_profiling: Option<bool>,
785    /// @internal Used by the Next.js internals only.
786    trust_host_header: Option<bool>,
787    /// Generate Route types and enable type checking for Link and Router.push,
788    /// etc. This option requires `appDir` to be enabled first.
789    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/typedRoutes)
790    typed_routes: Option<bool>,
791    url_imports: Option<serde_json::Value>,
792    view_transition: Option<bool>,
793    /// This option is to enable running the Webpack build in a worker thread
794    /// (doesn't apply to Turbopack).
795    webpack_build_worker: Option<bool>,
796    worker_threads: Option<bool>,
797
798    turbopack_minify: Option<bool>,
799    turbopack_persistent_caching: Option<bool>,
800    turbopack_source_maps: Option<bool>,
801    turbopack_tree_shaking: Option<bool>,
802    // Whether to enable the global-not-found convention
803    global_not_found: Option<bool>,
804}
805
806#[derive(
807    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
808)]
809#[serde(rename_all = "camelCase")]
810pub struct CacheLifeProfile {
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub stale: Option<u32>,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub revalidate: Option<u32>,
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub expire: Option<u32>,
817}
818
819#[test]
820fn test_cache_life_profiles() {
821    let json = serde_json::json!({
822        "cacheLife": {
823            "frequent": {
824                "stale": 19,
825                "revalidate": 100,
826            },
827        }
828    });
829
830    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
831    let mut expected_cache_life = FxIndexMap::default();
832
833    expected_cache_life.insert(
834        "frequent".to_string(),
835        CacheLifeProfile {
836            stale: Some(19),
837            revalidate: Some(100),
838            expire: None,
839        },
840    );
841
842    assert_eq!(config.cache_life, Some(expected_cache_life));
843}
844
845#[test]
846fn test_cache_life_profiles_invalid() {
847    let json = serde_json::json!({
848        "cacheLife": {
849            "invalid": {
850                "stale": "invalid_value",
851            },
852        }
853    });
854
855    let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
856
857    assert!(
858        result.is_err(),
859        "Deserialization should fail due to invalid 'stale' value type"
860    );
861}
862
863#[derive(
864    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
865)]
866#[serde(rename_all = "lowercase")]
867pub enum ExperimentalPartialPrerenderingIncrementalValue {
868    Incremental,
869}
870
871#[derive(
872    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
873)]
874#[serde(untagged)]
875pub enum ExperimentalPartialPrerendering {
876    Boolean(bool),
877    Incremental(ExperimentalPartialPrerenderingIncrementalValue),
878}
879
880#[test]
881fn test_parse_experimental_partial_prerendering() {
882    let json = serde_json::json!({
883        "ppr": "incremental"
884    });
885    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
886    assert_eq!(
887        config.ppr,
888        Some(ExperimentalPartialPrerendering::Incremental(
889            ExperimentalPartialPrerenderingIncrementalValue::Incremental
890        ))
891    );
892
893    let json = serde_json::json!({
894        "ppr": true
895    });
896    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
897    assert_eq!(
898        config.ppr,
899        Some(ExperimentalPartialPrerendering::Boolean(true))
900    );
901
902    // Expect if we provide a random string, it will fail.
903    let json = serde_json::json!({
904        "ppr": "random"
905    });
906    let config = serde_json::from_value::<ExperimentalConfig>(json);
907    assert!(config.is_err());
908}
909
910#[derive(
911    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
912)]
913#[serde(rename_all = "camelCase")]
914pub struct SubResourceIntegrity {
915    pub algorithm: Option<RcStr>,
916}
917
918#[derive(
919    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
920)]
921#[serde(untagged)]
922pub enum ServerActionsOrLegacyBool {
923    /// The current way to configure server actions sub behaviors.
924    ServerActionsConfig(ServerActions),
925
926    /// The legacy way to disable server actions. This is no longer used, server
927    /// actions is always enabled.
928    LegacyBool(bool),
929}
930
931#[derive(
932    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
933)]
934#[serde(rename_all = "kebab-case")]
935pub enum EsmExternalsValue {
936    Loose,
937}
938
939#[derive(
940    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
941)]
942#[serde(untagged)]
943pub enum EsmExternals {
944    Loose(EsmExternalsValue),
945    Bool(bool),
946}
947
948// Test for esm externals deserialization.
949#[test]
950fn test_esm_externals_deserialization() {
951    let json = serde_json::json!({
952        "esmExternals": true
953    });
954    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
955    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
956
957    let json = serde_json::json!({
958        "esmExternals": "loose"
959    });
960    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
961    assert_eq!(
962        config.esm_externals,
963        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
964    );
965}
966
967#[derive(
968    Clone,
969    Debug,
970    Default,
971    PartialEq,
972    Eq,
973    Deserialize,
974    Serialize,
975    TraceRawVcs,
976    NonLocalValue,
977    OperationValue,
978)]
979#[serde(rename_all = "camelCase")]
980pub struct ServerActions {
981    /// Allows adjusting body parser size limit for server actions.
982    pub body_size_limit: Option<SizeLimit>,
983}
984
985#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
986#[serde(untagged)]
987pub enum SizeLimit {
988    Number(f64),
989    WithUnit(String),
990}
991
992// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
993// implement Eq.
994impl PartialEq for SizeLimit {
995    fn eq(&self, other: &Self) -> bool {
996        match (self, other) {
997            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
998            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
999            _ => false,
1000        }
1001    }
1002}
1003
1004impl Eq for SizeLimit {}
1005
1006#[derive(
1007    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1008)]
1009#[serde(rename_all = "kebab-case")]
1010pub enum MiddlewarePrefetchType {
1011    Strict,
1012    Flexible,
1013}
1014
1015#[derive(
1016    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1017)]
1018#[serde(untagged)]
1019pub enum EmotionTransformOptionsOrBoolean {
1020    Boolean(bool),
1021    Options(EmotionTransformConfig),
1022}
1023
1024impl EmotionTransformOptionsOrBoolean {
1025    pub fn is_enabled(&self) -> bool {
1026        match self {
1027            Self::Boolean(enabled) => *enabled,
1028            _ => true,
1029        }
1030    }
1031}
1032
1033#[derive(
1034    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1035)]
1036#[serde(untagged)]
1037pub enum StyledComponentsTransformOptionsOrBoolean {
1038    Boolean(bool),
1039    Options(StyledComponentsTransformConfig),
1040}
1041
1042impl StyledComponentsTransformOptionsOrBoolean {
1043    pub fn is_enabled(&self) -> bool {
1044        match self {
1045            Self::Boolean(enabled) => *enabled,
1046            _ => true,
1047        }
1048    }
1049}
1050
1051#[turbo_tasks::value(eq = "manual")]
1052#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1053#[serde(rename_all = "camelCase")]
1054pub struct CompilerConfig {
1055    pub react_remove_properties: Option<ReactRemoveProperties>,
1056    pub relay: Option<RelayConfig>,
1057    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1058    pub remove_console: Option<RemoveConsoleConfig>,
1059    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1060}
1061
1062#[derive(
1063    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1064)]
1065#[serde(untagged, rename_all = "camelCase")]
1066pub enum ReactRemoveProperties {
1067    Boolean(bool),
1068    Config { properties: Option<Vec<String>> },
1069}
1070
1071impl ReactRemoveProperties {
1072    pub fn is_enabled(&self) -> bool {
1073        match self {
1074            Self::Boolean(enabled) => *enabled,
1075            _ => true,
1076        }
1077    }
1078}
1079
1080#[derive(
1081    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1082)]
1083#[serde(untagged)]
1084pub enum RemoveConsoleConfig {
1085    Boolean(bool),
1086    Config { exclude: Option<Vec<String>> },
1087}
1088
1089impl RemoveConsoleConfig {
1090    pub fn is_enabled(&self) -> bool {
1091        match self {
1092            Self::Boolean(enabled) => *enabled,
1093            _ => true,
1094        }
1095    }
1096}
1097
1098#[turbo_tasks::value(transparent)]
1099pub struct ResolveExtensions(Option<Vec<RcStr>>);
1100
1101#[turbo_tasks::value(transparent)]
1102pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1103
1104#[turbo_tasks::value(transparent)]
1105pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1106
1107#[turbo_tasks::value(transparent)]
1108
1109pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1110
1111#[turbo_tasks::value(transparent)]
1112pub struct OptionServerActions(Option<ServerActions>);
1113
1114#[turbo_tasks::value_impl]
1115impl NextConfig {
1116    #[turbo_tasks::function]
1117    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1118        let string = string.await?;
1119        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1120        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1121            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1122        Ok(config.cell())
1123    }
1124
1125    #[turbo_tasks::function]
1126    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1127        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1128    }
1129
1130    #[turbo_tasks::function]
1131    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1132        Vc::cell(self.react_production_profiling.unwrap_or_default())
1133    }
1134
1135    #[turbo_tasks::function]
1136    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1137        Vc::cell(
1138            self.server_external_packages
1139                .as_ref()
1140                .cloned()
1141                .unwrap_or_default(),
1142        )
1143    }
1144
1145    #[turbo_tasks::function]
1146    pub fn is_standalone(&self) -> Vc<bool> {
1147        Vc::cell(self.output == Some(OutputType::Standalone))
1148    }
1149
1150    #[turbo_tasks::function]
1151    pub fn cache_handler(&self) -> Vc<Option<RcStr>> {
1152        Vc::cell(self.cache_handler.clone())
1153    }
1154
1155    #[turbo_tasks::function]
1156    pub fn compiler(&self) -> Vc<CompilerConfig> {
1157        self.compiler.clone().unwrap_or_default().cell()
1158    }
1159
1160    #[turbo_tasks::function]
1161    pub fn env(&self) -> Vc<EnvMap> {
1162        // The value expected for env is Record<String, String>, but config itself
1163        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1164        // then stringifies it. We do the interop here as well.
1165        let env = self
1166            .env
1167            .iter()
1168            .map(|(k, v)| {
1169                (
1170                    k.as_str().into(),
1171                    if let JsonValue::String(s) = v {
1172                        // A string value is kept, calling `to_string` would wrap in to quotes.
1173                        s.as_str().into()
1174                    } else {
1175                        v.to_string().into()
1176                    },
1177                )
1178            })
1179            .collect();
1180
1181        Vc::cell(env)
1182    }
1183
1184    #[turbo_tasks::function]
1185    pub fn image_config(&self) -> Vc<ImageConfig> {
1186        self.images.clone().cell()
1187    }
1188
1189    #[turbo_tasks::function]
1190    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1191        Vc::cell(self.page_extensions.clone())
1192    }
1193
1194    #[turbo_tasks::function]
1195    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1196        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1197    }
1198
1199    #[turbo_tasks::function]
1200    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1201        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1202    }
1203
1204    #[turbo_tasks::function]
1205    pub fn webpack_rules(&self, active_conditions: Vec<RcStr>) -> Vc<OptionWebpackRules> {
1206        let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1207            return Vc::cell(None);
1208        };
1209        if turbo_rules.is_empty() {
1210            return Vc::cell(None);
1211        }
1212        let active_conditions = active_conditions.into_iter().collect::<FxHashSet<_>>();
1213        let mut rules = FxIndexMap::default();
1214        for (ext, rule) in turbo_rules.iter() {
1215            fn transform_loaders(loaders: &[LoaderItem]) -> ResolvedVc<WebpackLoaderItems> {
1216                ResolvedVc::cell(
1217                    loaders
1218                        .iter()
1219                        .map(|item| match item {
1220                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1221                                loader: name.clone(),
1222                                options: Default::default(),
1223                            },
1224                            LoaderItem::LoaderOptions(options) => options.clone(),
1225                        })
1226                        .collect(),
1227                )
1228            }
1229            enum FindRuleResult<'a> {
1230                Found(&'a RuleConfigItemOptions),
1231                NotFound,
1232                Break,
1233            }
1234            fn find_rule<'a>(
1235                rule: &'a RuleConfigItem,
1236                active_conditions: &FxHashSet<RcStr>,
1237            ) -> FindRuleResult<'a> {
1238                match rule {
1239                    RuleConfigItem::Options(rule) => FindRuleResult::Found(rule),
1240                    RuleConfigItem::Conditional(map) => {
1241                        for (condition, rule) in map.iter() {
1242                            if condition == "default" || active_conditions.contains(condition) {
1243                                match find_rule(rule, active_conditions) {
1244                                    FindRuleResult::Found(rule) => {
1245                                        return FindRuleResult::Found(rule);
1246                                    }
1247                                    FindRuleResult::Break => {
1248                                        return FindRuleResult::Break;
1249                                    }
1250                                    FindRuleResult::NotFound => {}
1251                                }
1252                            }
1253                        }
1254                        FindRuleResult::NotFound
1255                    }
1256                    RuleConfigItem::Boolean(_) => FindRuleResult::Break,
1257                }
1258            }
1259            match rule {
1260                RuleConfigItemOrShortcut::Loaders(loaders) => {
1261                    rules.insert(
1262                        ext.clone(),
1263                        LoaderRuleItem {
1264                            loaders: transform_loaders(loaders),
1265                            rename_as: None,
1266                        },
1267                    );
1268                }
1269                RuleConfigItemOrShortcut::Advanced(rule) => {
1270                    if let FindRuleResult::Found(RuleConfigItemOptions { loaders, rename_as }) =
1271                        find_rule(rule, &active_conditions)
1272                    {
1273                        rules.insert(
1274                            ext.clone(),
1275                            LoaderRuleItem {
1276                                loaders: transform_loaders(loaders),
1277                                rename_as: rename_as.clone(),
1278                            },
1279                        );
1280                    }
1281                }
1282            }
1283        }
1284        Vc::cell(Some(ResolvedVc::cell(rules)))
1285    }
1286
1287    #[turbo_tasks::function]
1288    pub fn webpack_conditions(&self) -> Vc<OptionWebpackConditions> {
1289        let Some(config_conditions) = self.turbopack.as_ref().and_then(|t| t.conditions.as_ref())
1290        else {
1291            return Vc::cell(None);
1292        };
1293
1294        let conditions = FxIndexMap::from_iter(
1295            config_conditions
1296                .iter()
1297                .map(|(k, v)| (k.clone(), v.0.clone())),
1298        );
1299
1300        Vc::cell(Some(ResolvedVc::cell(conditions)))
1301    }
1302
1303    #[turbo_tasks::function]
1304    pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1305        Ok(Vc::cell(
1306            self.experimental
1307                .turbopack_persistent_caching
1308                .unwrap_or_default(),
1309        ))
1310    }
1311
1312    #[turbo_tasks::function]
1313    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1314        let Some(resolve_alias) = self
1315            .turbopack
1316            .as_ref()
1317            .and_then(|t| t.resolve_alias.as_ref())
1318        else {
1319            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1320        };
1321        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1322        Ok(alias_map.cell())
1323    }
1324
1325    #[turbo_tasks::function]
1326    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1327        let Some(resolve_extensions) = self
1328            .turbopack
1329            .as_ref()
1330            .and_then(|t| t.resolve_extensions.as_ref())
1331        else {
1332            return Vc::cell(None);
1333        };
1334        Vc::cell(Some(resolve_extensions.clone()))
1335    }
1336
1337    #[turbo_tasks::function]
1338    pub async fn import_externals(&self) -> Result<Vc<bool>> {
1339        Ok(Vc::cell(match self.experimental.esm_externals {
1340            Some(EsmExternals::Bool(b)) => b,
1341            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1342            None => true,
1343        }))
1344    }
1345
1346    #[turbo_tasks::function]
1347    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1348        let options = &self.experimental.mdx_rs;
1349
1350        let options = match options {
1351            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1352                MdxTransformOptions {
1353                    provider_import_source: Some(mdx_import_source_file()),
1354                    ..Default::default()
1355                }
1356                .resolved_cell(),
1357            )),
1358            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1359                MdxTransformOptions {
1360                    provider_import_source: Some(
1361                        options
1362                            .provider_import_source
1363                            .clone()
1364                            .unwrap_or(mdx_import_source_file()),
1365                    ),
1366                    ..options.clone()
1367                }
1368                .resolved_cell(),
1369            )),
1370            _ => OptionalMdxTransformOptions(None),
1371        };
1372
1373        options.cell()
1374    }
1375
1376    #[turbo_tasks::function]
1377    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1378        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1379    }
1380
1381    #[turbo_tasks::function]
1382    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1383        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1384    }
1385
1386    #[turbo_tasks::function]
1387    pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1388        Vc::cell(self.experimental.sri.clone())
1389    }
1390
1391    #[turbo_tasks::function]
1392    pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1393        Vc::cell(match self.experimental.server_actions.as_ref() {
1394            Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1395                Some(server_actions.clone())
1396            }
1397            Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1398            _ => None,
1399        })
1400    }
1401
1402    #[turbo_tasks::function]
1403    pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
1404        let options = &self.experimental.react_compiler;
1405
1406        let options = match options {
1407            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1408                OptionalReactCompilerOptions(Some(
1409                    ReactCompilerOptions {
1410                        compilation_mode: None,
1411                        panic_threshold: None,
1412                    }
1413                    .resolved_cell(),
1414                ))
1415            }
1416            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1417                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1418            ),
1419            _ => OptionalReactCompilerOptions(None),
1420        };
1421
1422        options.cell()
1423    }
1424
1425    #[turbo_tasks::function]
1426    pub fn sass_config(&self) -> Vc<JsonValue> {
1427        Vc::cell(self.sass_options.clone().unwrap_or_default())
1428    }
1429
1430    #[turbo_tasks::function]
1431    pub fn skip_middleware_url_normalize(&self) -> Vc<bool> {
1432        Vc::cell(self.skip_middleware_url_normalize.unwrap_or(false))
1433    }
1434
1435    #[turbo_tasks::function]
1436    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1437        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1438    }
1439
1440    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
1441    /// Otherwise, the basePath is used.
1442    #[turbo_tasks::function]
1443    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1444        let this = self.await?;
1445
1446        Ok(Vc::cell(Some(
1447            format!(
1448                "{}/_next/",
1449                if let Some(asset_prefix) = &this.asset_prefix {
1450                    asset_prefix
1451                } else {
1452                    this.base_path.as_ref().map_or("", |b| b.as_str())
1453                }
1454                .trim_end_matches('/')
1455            )
1456            .into(),
1457        )))
1458    }
1459
1460    /// Returns the suffix to use for chunk loading.
1461    #[turbo_tasks::function]
1462    pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1463        let this = self.await?;
1464
1465        match &this.deployment_id {
1466            Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1467            None => Ok(Vc::cell(None)),
1468        }
1469    }
1470
1471    #[turbo_tasks::function]
1472    pub fn enable_ppr(&self) -> Vc<bool> {
1473        Vc::cell(
1474            self.experimental
1475                .ppr
1476                .as_ref()
1477                .map(|ppr| match ppr {
1478                    ExperimentalPartialPrerendering::Incremental(
1479                        ExperimentalPartialPrerenderingIncrementalValue::Incremental,
1480                    ) => true,
1481                    ExperimentalPartialPrerendering::Boolean(b) => *b,
1482                })
1483                .unwrap_or(false),
1484        )
1485    }
1486
1487    #[turbo_tasks::function]
1488    pub fn enable_taint(&self) -> Vc<bool> {
1489        Vc::cell(self.experimental.taint.unwrap_or(false))
1490    }
1491
1492    #[turbo_tasks::function]
1493    pub fn enable_router_bfcache(&self) -> Vc<bool> {
1494        Vc::cell(self.experimental.router_bfcache.unwrap_or(false))
1495    }
1496
1497    #[turbo_tasks::function]
1498    pub fn enable_view_transition(&self) -> Vc<bool> {
1499        Vc::cell(self.experimental.view_transition.unwrap_or(false))
1500    }
1501
1502    #[turbo_tasks::function]
1503    pub fn enable_dynamic_io(&self) -> Vc<bool> {
1504        Vc::cell(self.experimental.dynamic_io.unwrap_or(false))
1505    }
1506
1507    #[turbo_tasks::function]
1508    pub fn enable_use_cache(&self) -> Vc<bool> {
1509        Vc::cell(
1510            self.experimental
1511                .use_cache
1512                // "use cache" was originally implicitly enabled with the
1513                // dynamicIO flag, so we transfer the value for dynamicIO to the
1514                // explicit useCache flag to ensure backwards compatibility.
1515                .unwrap_or(self.experimental.dynamic_io.unwrap_or(false)),
1516        )
1517    }
1518
1519    #[turbo_tasks::function]
1520    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1521        let mut cache_kinds = CacheKinds::default();
1522
1523        if let Some(handlers) = self.experimental.cache_handlers.as_ref() {
1524            cache_kinds.extend(handlers.keys().cloned());
1525        }
1526
1527        cache_kinds.cell()
1528    }
1529
1530    #[turbo_tasks::function]
1531    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1532        Vc::cell(
1533            self.experimental
1534                .optimize_package_imports
1535                .clone()
1536                .unwrap_or_default(),
1537        )
1538    }
1539
1540    #[turbo_tasks::function]
1541    pub fn tree_shaking_mode_for_foreign_code(
1542        &self,
1543        _is_development: bool,
1544    ) -> Vc<OptionTreeShaking> {
1545        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1546            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1547            Some(true) => Some(TreeShakingMode::ModuleFragments),
1548            None => Some(TreeShakingMode::ReexportsOnly),
1549        })
1550        .cell()
1551    }
1552
1553    #[turbo_tasks::function]
1554    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1555        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1556            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1557            Some(true) => Some(TreeShakingMode::ModuleFragments),
1558            None => Some(TreeShakingMode::ReexportsOnly),
1559        })
1560        .cell()
1561    }
1562
1563    #[turbo_tasks::function]
1564    pub fn module_ids(&self) -> Vc<OptionModuleIds> {
1565        let Some(module_ids) = self.turbopack.as_ref().and_then(|t| t.module_ids) else {
1566            return Vc::cell(None);
1567        };
1568        Vc::cell(Some(module_ids))
1569    }
1570
1571    #[turbo_tasks::function]
1572    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1573        let minify = self.experimental.turbopack_minify;
1574        Ok(Vc::cell(
1575            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1576        ))
1577    }
1578
1579    #[turbo_tasks::function]
1580    pub async fn client_source_maps(&self, _mode: Vc<NextMode>) -> Result<Vc<bool>> {
1581        // Temporarily always enable client source maps as tests regress.
1582        // TODO: Respect both `self.experimental.turbopack_source_maps` and
1583        //       `self.production_browser_source_maps`
1584        let source_maps = self.experimental.turbopack_source_maps;
1585        Ok(Vc::cell(source_maps.unwrap_or(true)))
1586    }
1587
1588    #[turbo_tasks::function]
1589    pub async fn server_source_maps(&self) -> Result<Vc<bool>> {
1590        let source_maps = self.experimental.turbopack_source_maps;
1591        Ok(Vc::cell(source_maps.unwrap_or(true)))
1592    }
1593
1594    #[turbo_tasks::function]
1595    pub async fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1596        Ok(Vc::cell(
1597            self.typescript
1598                .tsconfig_path
1599                .as_ref()
1600                .map(|path| path.to_owned().into()),
1601        ))
1602    }
1603}
1604
1605/// A subset of ts/jsconfig that next.js implicitly
1606/// interops with.
1607#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1608#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1609#[serde(rename_all = "camelCase")]
1610pub struct JsConfig {
1611    compiler_options: Option<serde_json::Value>,
1612}
1613
1614#[turbo_tasks::value_impl]
1615impl JsConfig {
1616    #[turbo_tasks::function]
1617    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1618        let string = string.await?;
1619        let config: JsConfig = serde_json::from_str(&string)
1620            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1621
1622        Ok(config.cell())
1623    }
1624
1625    #[turbo_tasks::function]
1626    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1627        Vc::cell(self.compiler_options.clone().unwrap_or_default())
1628    }
1629}
1630
1631#[turbo_tasks::value]
1632struct OutdatedConfigIssue {
1633    path: ResolvedVc<FileSystemPath>,
1634    old_name: RcStr,
1635    new_name: RcStr,
1636    description: RcStr,
1637}
1638
1639#[turbo_tasks::value_impl]
1640impl Issue for OutdatedConfigIssue {
1641    #[turbo_tasks::function]
1642    fn severity(&self) -> Vc<IssueSeverity> {
1643        IssueSeverity::Error.into()
1644    }
1645
1646    #[turbo_tasks::function]
1647    fn stage(&self) -> Vc<IssueStage> {
1648        IssueStage::Config.into()
1649    }
1650
1651    #[turbo_tasks::function]
1652    fn file_path(&self) -> Vc<FileSystemPath> {
1653        *self.path
1654    }
1655
1656    #[turbo_tasks::function]
1657    fn title(&self) -> Vc<StyledString> {
1658        StyledString::Line(vec![
1659            StyledString::Code(self.old_name.clone()),
1660            StyledString::Text(" has been replaced by ".into()),
1661            StyledString::Code(self.new_name.clone()),
1662        ])
1663        .cell()
1664    }
1665
1666    #[turbo_tasks::function]
1667    fn description(&self) -> Vc<OptionStyledString> {
1668        Vc::cell(Some(
1669            StyledString::Text(self.description.clone()).resolved_cell(),
1670        ))
1671    }
1672}