Skip to main content

next_core/
next_config.rs

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