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    /// @internal Used by the Next.js internals only.
874    trust_host_header: Option<bool>,
875
876    url_imports: Option<serde_json::Value>,
877    /// This option is to enable running the Webpack build in a worker thread
878    /// (doesn't apply to Turbopack).
879    webpack_build_worker: Option<bool>,
880    worker_threads: Option<bool>,
881
882    turbopack_minify: Option<bool>,
883    turbopack_module_ids: Option<ModuleIds>,
884    turbopack_persistent_caching: Option<bool>,
885    turbopack_source_maps: Option<bool>,
886    turbopack_tree_shaking: Option<bool>,
887    turbopack_scope_hoisting: Option<bool>,
888    turbopack_import_type_bytes: Option<bool>,
889    turbopack_use_system_tls_certs: Option<bool>,
890    /// Disable automatic configuration of the sass loader.
891    #[serde(default)]
892    turbopack_use_builtin_sass: Option<bool>,
893    /// Disable automatic configuration of the babel loader when a babel configuration file is
894    /// present.
895    #[serde(default)]
896    turbopack_use_builtin_babel: Option<bool>,
897    // Whether to enable the global-not-found convention
898    global_not_found: Option<bool>,
899    /// Defaults to false in development mode, true in production mode.
900    turbopack_remove_unused_exports: Option<bool>,
901    /// Devtool option for the segment explorer.
902    devtool_segment_explorer: Option<bool>,
903}
904
905#[derive(
906    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
907)]
908#[serde(rename_all = "camelCase")]
909pub struct CacheLifeProfile {
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub stale: Option<u32>,
912    #[serde(skip_serializing_if = "Option::is_none")]
913    pub revalidate: Option<u32>,
914    #[serde(skip_serializing_if = "Option::is_none")]
915    pub expire: Option<u32>,
916}
917
918#[test]
919fn test_cache_life_profiles() {
920    let json = serde_json::json!({
921        "cacheLife": {
922            "frequent": {
923                "stale": 19,
924                "revalidate": 100,
925            },
926        }
927    });
928
929    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
930    let mut expected_cache_life = FxIndexMap::default();
931
932    expected_cache_life.insert(
933        "frequent".to_string(),
934        CacheLifeProfile {
935            stale: Some(19),
936            revalidate: Some(100),
937            expire: None,
938        },
939    );
940
941    assert_eq!(config.cache_life, Some(expected_cache_life));
942}
943
944#[test]
945fn test_cache_life_profiles_invalid() {
946    let json = serde_json::json!({
947        "cacheLife": {
948            "invalid": {
949                "stale": "invalid_value",
950            },
951        }
952    });
953
954    let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
955
956    assert!(
957        result.is_err(),
958        "Deserialization should fail due to invalid 'stale' value type"
959    );
960}
961
962#[derive(
963    Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
964)]
965#[serde(rename_all = "camelCase")]
966pub struct SubResourceIntegrity {
967    pub algorithm: Option<RcStr>,
968}
969
970#[derive(
971    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
972)]
973#[serde(untagged)]
974pub enum ServerActionsOrLegacyBool {
975    /// The current way to configure server actions sub behaviors.
976    ServerActionsConfig(ServerActions),
977
978    /// The legacy way to disable server actions. This is no longer used, server
979    /// actions is always enabled.
980    LegacyBool(bool),
981}
982
983#[derive(
984    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
985)]
986#[serde(rename_all = "kebab-case")]
987pub enum EsmExternalsValue {
988    Loose,
989}
990
991#[derive(
992    Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
993)]
994#[serde(untagged)]
995pub enum EsmExternals {
996    Loose(EsmExternalsValue),
997    Bool(bool),
998}
999
1000// Test for esm externals deserialization.
1001#[test]
1002fn test_esm_externals_deserialization() {
1003    let json = serde_json::json!({
1004        "esmExternals": true
1005    });
1006    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1007    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1008
1009    let json = serde_json::json!({
1010        "esmExternals": "loose"
1011    });
1012    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1013    assert_eq!(
1014        config.esm_externals,
1015        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1016    );
1017}
1018
1019#[derive(
1020    Clone,
1021    Debug,
1022    Default,
1023    PartialEq,
1024    Eq,
1025    Deserialize,
1026    Serialize,
1027    TraceRawVcs,
1028    NonLocalValue,
1029    OperationValue,
1030)]
1031#[serde(rename_all = "camelCase")]
1032pub struct ServerActions {
1033    /// Allows adjusting body parser size limit for server actions.
1034    pub body_size_limit: Option<SizeLimit>,
1035}
1036
1037#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
1038#[serde(untagged)]
1039pub enum SizeLimit {
1040    Number(f64),
1041    WithUnit(String),
1042}
1043
1044// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
1045// implement Eq.
1046impl PartialEq for SizeLimit {
1047    fn eq(&self, other: &Self) -> bool {
1048        match (self, other) {
1049            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1050            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1051            _ => false,
1052        }
1053    }
1054}
1055
1056impl Eq for SizeLimit {}
1057
1058#[derive(
1059    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1060)]
1061#[serde(rename_all = "kebab-case")]
1062pub enum MiddlewarePrefetchType {
1063    Strict,
1064    Flexible,
1065}
1066
1067#[derive(
1068    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1069)]
1070#[serde(untagged)]
1071pub enum EmotionTransformOptionsOrBoolean {
1072    Boolean(bool),
1073    Options(EmotionTransformConfig),
1074}
1075
1076impl EmotionTransformOptionsOrBoolean {
1077    pub fn is_enabled(&self) -> bool {
1078        match self {
1079            Self::Boolean(enabled) => *enabled,
1080            _ => true,
1081        }
1082    }
1083}
1084
1085#[derive(
1086    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1087)]
1088#[serde(untagged)]
1089pub enum StyledComponentsTransformOptionsOrBoolean {
1090    Boolean(bool),
1091    Options(StyledComponentsTransformConfig),
1092}
1093
1094impl StyledComponentsTransformOptionsOrBoolean {
1095    pub fn is_enabled(&self) -> bool {
1096        match self {
1097            Self::Boolean(enabled) => *enabled,
1098            _ => true,
1099        }
1100    }
1101}
1102
1103#[turbo_tasks::value(eq = "manual")]
1104#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1105#[serde(rename_all = "camelCase")]
1106pub struct CompilerConfig {
1107    pub react_remove_properties: Option<ReactRemoveProperties>,
1108    pub relay: Option<RelayConfig>,
1109    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1110    pub remove_console: Option<RemoveConsoleConfig>,
1111    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1112}
1113
1114#[derive(
1115    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1116)]
1117#[serde(untagged, rename_all = "camelCase")]
1118pub enum ReactRemoveProperties {
1119    Boolean(bool),
1120    Config { properties: Option<Vec<String>> },
1121}
1122
1123impl ReactRemoveProperties {
1124    pub fn is_enabled(&self) -> bool {
1125        match self {
1126            Self::Boolean(enabled) => *enabled,
1127            _ => true,
1128        }
1129    }
1130}
1131
1132#[derive(
1133    Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1134)]
1135#[serde(untagged)]
1136pub enum RemoveConsoleConfig {
1137    Boolean(bool),
1138    Config { exclude: Option<Vec<String>> },
1139}
1140
1141impl RemoveConsoleConfig {
1142    pub fn is_enabled(&self) -> bool {
1143        match self {
1144            Self::Boolean(enabled) => *enabled,
1145            _ => true,
1146        }
1147    }
1148}
1149
1150#[turbo_tasks::value(transparent)]
1151pub struct ResolveExtensions(Option<Vec<RcStr>>);
1152
1153#[turbo_tasks::value(transparent)]
1154pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1155
1156#[turbo_tasks::value(transparent)]
1157pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1158
1159#[turbo_tasks::value(transparent)]
1160
1161pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1162
1163#[turbo_tasks::value(transparent)]
1164pub struct OptionFileSystemPath(Option<FileSystemPath>);
1165
1166#[turbo_tasks::value(transparent)]
1167pub struct OptionServerActions(Option<ServerActions>);
1168
1169#[turbo_tasks::value(transparent)]
1170pub struct OptionJsonValue(pub Option<serde_json::Value>);
1171
1172fn turbopack_config_documentation_link() -> RcStr {
1173    rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1174}
1175
1176#[turbo_tasks::value(shared)]
1177struct InvalidLoaderRuleRenameAsIssue {
1178    glob: RcStr,
1179    rename_as: RcStr,
1180    config_file_path: FileSystemPath,
1181}
1182
1183#[turbo_tasks::value_impl]
1184impl Issue for InvalidLoaderRuleRenameAsIssue {
1185    #[turbo_tasks::function]
1186    async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1187        Ok(self.config_file_path.clone().cell())
1188    }
1189
1190    #[turbo_tasks::function]
1191    fn stage(&self) -> Vc<IssueStage> {
1192        IssueStage::Config.cell()
1193    }
1194
1195    #[turbo_tasks::function]
1196    async fn title(&self) -> Result<Vc<StyledString>> {
1197        Ok(
1198            StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1199                .cell(),
1200        )
1201    }
1202
1203    #[turbo_tasks::function]
1204    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1205        Ok(Vc::cell(Some(
1206            StyledString::Text(RcStr::from(format!(
1207                "The extension {} contains a wildcard, but the `as` option does not: {}",
1208                self.glob, self.rename_as,
1209            )))
1210            .resolved_cell(),
1211        )))
1212    }
1213
1214    #[turbo_tasks::function]
1215    fn documentation_link(&self) -> Vc<RcStr> {
1216        Vc::cell(turbopack_config_documentation_link())
1217    }
1218}
1219
1220#[turbo_tasks::value(shared)]
1221struct InvalidLoaderRuleConditionIssue {
1222    condition: ConfigConditionItem,
1223    config_file_path: FileSystemPath,
1224}
1225
1226#[turbo_tasks::value_impl]
1227impl Issue for InvalidLoaderRuleConditionIssue {
1228    #[turbo_tasks::function]
1229    async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1230        Ok(self.await?.config_file_path.clone().cell())
1231    }
1232
1233    #[turbo_tasks::function]
1234    fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1235        IssueStage::Config.cell()
1236    }
1237
1238    #[turbo_tasks::function]
1239    async fn title(&self) -> Result<Vc<StyledString>> {
1240        Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1241    }
1242
1243    #[turbo_tasks::function]
1244    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1245        Ok(Vc::cell(Some(
1246            StyledString::Text(RcStr::from(
1247                serde_json::to_string_pretty(&self.condition)
1248                    .expect("condition must be serializable"),
1249            ))
1250            .resolved_cell(),
1251        )))
1252    }
1253
1254    #[turbo_tasks::function]
1255    fn documentation_link(&self) -> Vc<RcStr> {
1256        Vc::cell(turbopack_config_documentation_link())
1257    }
1258}
1259
1260#[turbo_tasks::value_impl]
1261impl NextConfig {
1262    #[turbo_tasks::function]
1263    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1264        let string = string.await?;
1265        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1266        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1267            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1268        Ok(config.cell())
1269    }
1270
1271    #[turbo_tasks::function]
1272    pub async fn config_file_path(
1273        &self,
1274        project_path: FileSystemPath,
1275    ) -> Result<Vc<FileSystemPath>> {
1276        Ok(project_path.join(&self.config_file_name)?.cell())
1277    }
1278
1279    #[turbo_tasks::function]
1280    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1281        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1282    }
1283
1284    #[turbo_tasks::function]
1285    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1286        Vc::cell(self.react_production_profiling.unwrap_or_default())
1287    }
1288
1289    #[turbo_tasks::function]
1290    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1291        Vc::cell(
1292            self.server_external_packages
1293                .as_ref()
1294                .cloned()
1295                .unwrap_or_default(),
1296        )
1297    }
1298
1299    #[turbo_tasks::function]
1300    pub fn is_standalone(&self) -> Vc<bool> {
1301        Vc::cell(self.output == Some(OutputType::Standalone))
1302    }
1303
1304    #[turbo_tasks::function]
1305    pub fn base_path(&self) -> Vc<Option<RcStr>> {
1306        Vc::cell(self.base_path.clone())
1307    }
1308
1309    #[turbo_tasks::function]
1310    pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1311        if let Some(handler) = &self.cache_handler {
1312            Ok(Vc::cell(Some(project_path.join(handler)?)))
1313        } else {
1314            Ok(Vc::cell(None))
1315        }
1316    }
1317
1318    #[turbo_tasks::function]
1319    pub fn compiler(&self) -> Vc<CompilerConfig> {
1320        self.compiler.clone().unwrap_or_default().cell()
1321    }
1322
1323    #[turbo_tasks::function]
1324    pub fn env(&self) -> Vc<EnvMap> {
1325        // The value expected for env is Record<String, String>, but config itself
1326        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1327        // then stringifies it. We do the interop here as well.
1328        let env = self
1329            .env
1330            .iter()
1331            .map(|(k, v)| {
1332                (
1333                    k.as_str().into(),
1334                    if let JsonValue::String(s) = v {
1335                        // A string value is kept, calling `to_string` would wrap in to quotes.
1336                        s.as_str().into()
1337                    } else {
1338                        v.to_string().into()
1339                    },
1340                )
1341            })
1342            .collect();
1343
1344        Vc::cell(env)
1345    }
1346
1347    #[turbo_tasks::function]
1348    pub fn image_config(&self) -> Vc<ImageConfig> {
1349        self.images.clone().cell()
1350    }
1351
1352    #[turbo_tasks::function]
1353    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1354        // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
1355        // which just builds a regex alternative, which greedily matches the longest
1356        // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
1357        let mut extensions = self.page_extensions.clone();
1358        extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1359        Vc::cell(extensions)
1360    }
1361
1362    #[turbo_tasks::function]
1363    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1364        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1365    }
1366
1367    #[turbo_tasks::function]
1368    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1369        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1370    }
1371
1372    #[turbo_tasks::function]
1373    pub async fn webpack_rules(
1374        self: Vc<Self>,
1375        project_path: FileSystemPath,
1376    ) -> Result<Vc<WebpackRules>> {
1377        let this = self.await?;
1378        let Some(turbo_rules) = this.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1379            return Ok(Vc::cell(Vec::new()));
1380        };
1381        if turbo_rules.is_empty() {
1382            return Ok(Vc::cell(Vec::new()));
1383        }
1384        let mut rules = Vec::new();
1385        for (glob, rule_collection) in turbo_rules.iter() {
1386            fn transform_loaders(
1387                loaders: &mut dyn Iterator<Item = &LoaderItem>,
1388            ) -> ResolvedVc<WebpackLoaderItems> {
1389                ResolvedVc::cell(
1390                    loaders
1391                        .map(|item| match item {
1392                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1393                                loader: name.clone(),
1394                                options: Default::default(),
1395                            },
1396                            LoaderItem::LoaderOptions(options) => options.clone(),
1397                        })
1398                        .collect(),
1399                )
1400            }
1401            for item in &rule_collection.0 {
1402                match item {
1403                    RuleConfigCollectionItem::Shorthand(loaders) => {
1404                        rules.push((
1405                            glob.clone(),
1406                            LoaderRuleItem {
1407                                loaders: transform_loaders(&mut [loaders].into_iter()),
1408                                rename_as: None,
1409                                condition: None,
1410                            },
1411                        ));
1412                    }
1413                    RuleConfigCollectionItem::Full(RuleConfigItem {
1414                        loaders,
1415                        rename_as,
1416                        condition,
1417                    }) => {
1418                        // If the extension contains a wildcard, and the rename_as does not,
1419                        // emit an issue to prevent users from encountering duplicate module
1420                        // names.
1421                        if glob.contains("*")
1422                            && let Some(rename_as) = rename_as.as_ref()
1423                            && !rename_as.contains("*")
1424                        {
1425                            InvalidLoaderRuleRenameAsIssue {
1426                                glob: glob.clone(),
1427                                config_file_path: self
1428                                    .config_file_path(project_path.clone())
1429                                    .owned()
1430                                    .await?,
1431                                rename_as: rename_as.clone(),
1432                            }
1433                            .resolved_cell()
1434                            .emit();
1435                        }
1436
1437                        // convert from Next.js-specific condition type to internal Turbopack
1438                        // condition type
1439                        let condition = if let Some(condition) = condition {
1440                            if let Ok(cond) = ConditionItem::try_from(condition.clone()) {
1441                                Some(cond)
1442                            } else {
1443                                InvalidLoaderRuleConditionIssue {
1444                                    condition: condition.clone(),
1445                                    config_file_path: self
1446                                        .config_file_path(project_path.clone())
1447                                        .owned()
1448                                        .await?,
1449                                }
1450                                .resolved_cell()
1451                                .emit();
1452                                None
1453                            }
1454                        } else {
1455                            None
1456                        };
1457                        rules.push((
1458                            glob.clone(),
1459                            LoaderRuleItem {
1460                                loaders: transform_loaders(&mut loaders.iter()),
1461                                rename_as: rename_as.clone(),
1462                                condition,
1463                            },
1464                        ));
1465                    }
1466                }
1467            }
1468        }
1469        Ok(Vc::cell(rules))
1470    }
1471
1472    #[turbo_tasks::function]
1473    pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1474        Ok(Vc::cell(
1475            self.experimental
1476                .turbopack_persistent_caching
1477                .unwrap_or_default(),
1478        ))
1479    }
1480
1481    #[turbo_tasks::function]
1482    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1483        let Some(resolve_alias) = self
1484            .turbopack
1485            .as_ref()
1486            .and_then(|t| t.resolve_alias.as_ref())
1487        else {
1488            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1489        };
1490        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1491        Ok(alias_map.cell())
1492    }
1493
1494    #[turbo_tasks::function]
1495    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1496        let Some(resolve_extensions) = self
1497            .turbopack
1498            .as_ref()
1499            .and_then(|t| t.resolve_extensions.as_ref())
1500        else {
1501            return Vc::cell(None);
1502        };
1503        Vc::cell(Some(resolve_extensions.clone()))
1504    }
1505
1506    #[turbo_tasks::function]
1507    pub fn import_externals(&self) -> Result<Vc<bool>> {
1508        Ok(Vc::cell(match self.experimental.esm_externals {
1509            Some(EsmExternals::Bool(b)) => b,
1510            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1511            None => true,
1512        }))
1513    }
1514
1515    #[turbo_tasks::function]
1516    pub fn inline_css(&self) -> Vc<bool> {
1517        Vc::cell(self.experimental.inline_css.unwrap_or(false))
1518    }
1519
1520    #[turbo_tasks::function]
1521    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1522        let options = &self.experimental.mdx_rs;
1523
1524        let options = match options {
1525            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1526                MdxTransformOptions {
1527                    provider_import_source: Some(mdx_import_source_file()),
1528                    ..Default::default()
1529                }
1530                .resolved_cell(),
1531            )),
1532            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1533                MdxTransformOptions {
1534                    provider_import_source: Some(
1535                        options
1536                            .provider_import_source
1537                            .clone()
1538                            .unwrap_or(mdx_import_source_file()),
1539                    ),
1540                    ..options.clone()
1541                }
1542                .resolved_cell(),
1543            )),
1544            _ => OptionalMdxTransformOptions(None),
1545        };
1546
1547        options.cell()
1548    }
1549
1550    #[turbo_tasks::function]
1551    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1552        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1553    }
1554
1555    #[turbo_tasks::function]
1556    pub fn dist_dir(&self) -> Vc<RcStr> {
1557        Vc::cell(self.dist_dir.clone())
1558    }
1559    #[turbo_tasks::function]
1560    pub fn dist_dir_root(&self) -> Vc<RcStr> {
1561        Vc::cell(self.dist_dir_root.clone())
1562    }
1563
1564    #[turbo_tasks::function]
1565    pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1566        if let Some(handlers) = &self.cache_handlers {
1567            Ok(Vc::cell(
1568                handlers
1569                    .values()
1570                    .map(|h| project_path.join(h))
1571                    .collect::<Result<Vec<_>>>()?,
1572            ))
1573        } else {
1574            Ok(Vc::cell(vec![]))
1575        }
1576    }
1577
1578    #[turbo_tasks::function]
1579    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1580        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1581    }
1582
1583    #[turbo_tasks::function]
1584    pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1585        Vc::cell(self.experimental.sri.clone())
1586    }
1587
1588    #[turbo_tasks::function]
1589    pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1590        Vc::cell(match self.experimental.server_actions.as_ref() {
1591            Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1592                Some(server_actions.clone())
1593            }
1594            Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1595            _ => None,
1596        })
1597    }
1598
1599    #[turbo_tasks::function]
1600    pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1601        Vc::cell(self.experimental.turbopack_use_builtin_babel)
1602    }
1603
1604    #[turbo_tasks::function]
1605    pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1606        Vc::cell(self.experimental.turbopack_use_builtin_sass)
1607    }
1608
1609    #[turbo_tasks::function]
1610    pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1611        let options = &self.react_compiler;
1612
1613        let options = match options {
1614            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1615                OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1616            }
1617            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1618                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1619            ),
1620            _ => OptionalReactCompilerOptions(None),
1621        };
1622
1623        options.cell()
1624    }
1625
1626    #[turbo_tasks::function]
1627    pub fn sass_config(&self) -> Vc<JsonValue> {
1628        Vc::cell(self.sass_options.clone().unwrap_or_default())
1629    }
1630
1631    #[turbo_tasks::function]
1632    pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1633        Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1634    }
1635
1636    #[turbo_tasks::function]
1637    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1638        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1639    }
1640
1641    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
1642    /// Otherwise, the basePath is used.
1643    #[turbo_tasks::function]
1644    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1645        let this = self.await?;
1646
1647        Ok(Vc::cell(
1648            format!(
1649                "{}/_next/",
1650                if let Some(asset_prefix) = &this.asset_prefix {
1651                    asset_prefix
1652                } else {
1653                    this.base_path.as_ref().map_or("", |b| b.as_str())
1654                }
1655                .trim_end_matches('/')
1656            )
1657            .into(),
1658        ))
1659    }
1660
1661    /// Returns the suffix to use for chunk loading.
1662    #[turbo_tasks::function]
1663    pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1664        let this = self.await?;
1665
1666        match &this.deployment_id {
1667            Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1668            None => Ok(Vc::cell(None)),
1669        }
1670    }
1671
1672    #[turbo_tasks::function]
1673    pub fn enable_taint(&self) -> Vc<bool> {
1674        Vc::cell(self.experimental.taint.unwrap_or(false))
1675    }
1676
1677    #[turbo_tasks::function]
1678    pub fn enable_cache_components(&self) -> Vc<bool> {
1679        Vc::cell(self.cache_components.unwrap_or(false))
1680    }
1681
1682    #[turbo_tasks::function]
1683    pub fn enable_use_cache(&self) -> Vc<bool> {
1684        Vc::cell(
1685            self.experimental
1686                .use_cache
1687                // "use cache" was originally implicitly enabled with the
1688                // cacheComponents flag, so we transfer the value for cacheComponents to the
1689                // explicit useCache flag to ensure backwards compatibility.
1690                .unwrap_or(self.cache_components.unwrap_or(false)),
1691        )
1692    }
1693
1694    #[turbo_tasks::function]
1695    pub fn enable_root_params(&self) -> Vc<bool> {
1696        Vc::cell(
1697            self.experimental
1698                .root_params
1699                // rootParams should be enabled implicitly in cacheComponents.
1700                .unwrap_or(self.cache_components.unwrap_or(false)),
1701        )
1702    }
1703
1704    #[turbo_tasks::function]
1705    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1706        let mut cache_kinds = CacheKinds::default();
1707
1708        if let Some(handlers) = self.cache_handlers.as_ref() {
1709            cache_kinds.extend(handlers.keys().cloned());
1710        }
1711
1712        cache_kinds.cell()
1713    }
1714
1715    #[turbo_tasks::function]
1716    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1717        Vc::cell(
1718            self.experimental
1719                .optimize_package_imports
1720                .clone()
1721                .unwrap_or_default(),
1722        )
1723    }
1724
1725    #[turbo_tasks::function]
1726    pub fn tree_shaking_mode_for_foreign_code(
1727        &self,
1728        _is_development: bool,
1729    ) -> Vc<OptionTreeShaking> {
1730        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1731            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1732            Some(true) => Some(TreeShakingMode::ModuleFragments),
1733            None => Some(TreeShakingMode::ReexportsOnly),
1734        })
1735        .cell()
1736    }
1737
1738    #[turbo_tasks::function]
1739    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1740        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1741            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1742            Some(true) => Some(TreeShakingMode::ModuleFragments),
1743            None => Some(TreeShakingMode::ReexportsOnly),
1744        })
1745        .cell()
1746    }
1747
1748    #[turbo_tasks::function]
1749    pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1750        Ok(Vc::cell(
1751            self.experimental
1752                .turbopack_remove_unused_exports
1753                .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1754        ))
1755    }
1756
1757    #[turbo_tasks::function]
1758    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1759        Ok(match *mode.await? {
1760            // Ignore configuration in development mode, HMR only works with `named`
1761            NextMode::Development => ModuleIds::Named.cell(),
1762            NextMode::Build => self
1763                .experimental
1764                .turbopack_module_ids
1765                .unwrap_or(ModuleIds::Deterministic)
1766                .cell(),
1767        })
1768    }
1769
1770    #[turbo_tasks::function]
1771    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1772        let minify = self.experimental.turbopack_minify;
1773        Ok(Vc::cell(
1774            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1775        ))
1776    }
1777
1778    #[turbo_tasks::function]
1779    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1780        Ok(Vc::cell(match *mode.await? {
1781            // Ignore configuration in development mode to not break HMR
1782            NextMode::Development => false,
1783            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1784        }))
1785    }
1786
1787    #[turbo_tasks::function]
1788    pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
1789        Vc::cell(
1790            self.experimental
1791                .turbopack_import_type_bytes
1792                .unwrap_or(false),
1793        )
1794    }
1795
1796    #[turbo_tasks::function]
1797    pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1798        let source_maps = self.experimental.turbopack_source_maps;
1799        Ok(Vc::cell(source_maps.unwrap_or(match &*mode.await? {
1800            NextMode::Development => true,
1801            NextMode::Build => self.production_browser_source_maps,
1802        })))
1803    }
1804
1805    #[turbo_tasks::function]
1806    pub fn server_source_maps(&self) -> Result<Vc<bool>> {
1807        let source_maps = self.experimental.turbopack_source_maps;
1808        Ok(Vc::cell(source_maps.unwrap_or(true)))
1809    }
1810
1811    #[turbo_tasks::function]
1812    pub fn turbopack_debug_ids(&self) -> Vc<bool> {
1813        Vc::cell(
1814            self.turbopack
1815                .as_ref()
1816                .and_then(|turbopack| turbopack.debug_ids)
1817                .unwrap_or(false),
1818        )
1819    }
1820
1821    #[turbo_tasks::function]
1822    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1823        Ok(Vc::cell(
1824            self.typescript
1825                .tsconfig_path
1826                .as_ref()
1827                .map(|path| path.to_owned().into()),
1828        ))
1829    }
1830
1831    #[turbo_tasks::function]
1832    pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
1833        Vc::cell(self.cross_origin.clone())
1834    }
1835
1836    #[turbo_tasks::function]
1837    pub fn i18n(&self) -> Vc<OptionI18NConfig> {
1838        Vc::cell(self.i18n.clone())
1839    }
1840
1841    #[turbo_tasks::function]
1842    pub fn output(&self) -> Vc<OptionOutputType> {
1843        Vc::cell(self.output.clone())
1844    }
1845
1846    #[turbo_tasks::function]
1847    pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
1848        Vc::cell(self.output_file_tracing_includes.clone())
1849    }
1850
1851    #[turbo_tasks::function]
1852    pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
1853        Vc::cell(self.output_file_tracing_excludes.clone())
1854    }
1855
1856    #[turbo_tasks::function]
1857    pub async fn fetch_client(
1858        &self,
1859        env: Vc<Box<dyn ProcessEnv>>,
1860    ) -> Result<Vc<FetchClientConfig>> {
1861        // Support both an env var and the experimental flag to provide more flexibility to
1862        // developers on locked down systems, depending on if they want to configure this on a
1863        // per-system or per-project basis.
1864        let use_system_tls_certs = env
1865            .read(rcstr!("NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS"))
1866            .await?
1867            .as_ref()
1868            .and_then(|env_value| {
1869                // treat empty value same as an unset value
1870                (!env_value.is_empty()).then(|| env_value == "1" || env_value == "true")
1871            })
1872            .or(self.experimental.turbopack_use_system_tls_certs)
1873            .unwrap_or(false);
1874        Ok(FetchClientConfig {
1875            tls_built_in_webpki_certs: !use_system_tls_certs,
1876            tls_built_in_native_certs: use_system_tls_certs,
1877        }
1878        .cell())
1879    }
1880}
1881
1882/// A subset of ts/jsconfig that next.js implicitly
1883/// interops with.
1884#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1885#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1886#[serde(rename_all = "camelCase")]
1887pub struct JsConfig {
1888    compiler_options: Option<serde_json::Value>,
1889}
1890
1891#[turbo_tasks::value_impl]
1892impl JsConfig {
1893    #[turbo_tasks::function]
1894    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1895        let string = string.await?;
1896        let config: JsConfig = serde_json::from_str(&string)
1897            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1898
1899        Ok(config.cell())
1900    }
1901
1902    #[turbo_tasks::function]
1903    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1904        Vc::cell(self.compiler_options.clone().unwrap_or_default())
1905    }
1906}
1907
1908#[cfg(test)]
1909mod tests {
1910    use super::*;
1911
1912    #[test]
1913    fn test_serde_rule_config_item_options() {
1914        let json_value = serde_json::json!({
1915            "loaders": [],
1916            "as": "*.js",
1917            "condition": {
1918                "all": [
1919                    "production",
1920                    {"not": "foreign"},
1921                    {"any": [
1922                        "browser",
1923                        {
1924                            "path": { "type": "glob", "value": "*.svg"},
1925                            "content": {
1926                                "source": "@someTag",
1927                                "flags": ""
1928                            }
1929                        }
1930                    ]},
1931                ],
1932            }
1933        });
1934
1935        let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
1936
1937        assert_eq!(
1938            rule_config,
1939            RuleConfigItem {
1940                loaders: vec![],
1941                rename_as: Some(rcstr!("*.js")),
1942                condition: Some(ConfigConditionItem::All(
1943                    [
1944                        ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
1945                        ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
1946                            WebpackLoaderBuiltinCondition::Foreign
1947                        ))),
1948                        ConfigConditionItem::Any(
1949                            vec![
1950                                ConfigConditionItem::Builtin(
1951                                    WebpackLoaderBuiltinCondition::Browser
1952                                ),
1953                                ConfigConditionItem::Base {
1954                                    path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
1955                                    content: Some(RegexComponents {
1956                                        source: rcstr!("@someTag"),
1957                                        flags: rcstr!(""),
1958                                    }),
1959                                },
1960                            ]
1961                            .into(),
1962                        ),
1963                    ]
1964                    .into(),
1965                )),
1966            }
1967        );
1968    }
1969}