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