next_core/
next_config.rs

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