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    /// Issue patterns to ignore (suppress) from Turbopack output.
615    #[serde(default)]
616    pub ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
617}
618
619#[derive(
620    Deserialize,
621    Clone,
622    PartialEq,
623    Eq,
624    Debug,
625    TraceRawVcs,
626    NonLocalValue,
627    OperationValue,
628    Encode,
629    Decode,
630)]
631#[serde(deny_unknown_fields)]
632pub struct RegexComponents {
633    source: RcStr,
634    flags: RcStr,
635}
636
637/// This type should not be hand-written, but instead `packages/next/src/build/swc/index.ts` will
638/// transform a JS `RegExp` to a `RegexComponents` or a string to a `Glob` before passing it to us.
639///
640/// This is needed because `RegExp` objects are not otherwise serializable.
641#[derive(
642    Clone,
643    PartialEq,
644    Eq,
645    Debug,
646    Deserialize,
647    TraceRawVcs,
648    NonLocalValue,
649    OperationValue,
650    Encode,
651    Decode,
652)]
653#[serde(
654    tag = "type",
655    content = "value",
656    rename_all = "camelCase",
657    deny_unknown_fields
658)]
659pub enum ConfigConditionPath {
660    Glob(RcStr),
661    Regex(RegexComponents),
662}
663
664impl TryFrom<ConfigConditionPath> for ConditionPath {
665    type Error = anyhow::Error;
666
667    fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
668        Ok(match config {
669            ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
670            ConfigConditionPath::Regex(path) => {
671                ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
672            }
673        })
674    }
675}
676
677impl TryFrom<RegexComponents> for EsRegex {
678    type Error = anyhow::Error;
679
680    fn try_from(components: RegexComponents) -> Result<EsRegex> {
681        EsRegex::new(&components.source, &components.flags)
682    }
683}
684
685#[derive(
686    Clone,
687    PartialEq,
688    Eq,
689    Debug,
690    Deserialize,
691    TraceRawVcs,
692    NonLocalValue,
693    OperationValue,
694    Encode,
695    Decode,
696)]
697#[serde(
698    tag = "type",
699    content = "value",
700    rename_all = "camelCase",
701    deny_unknown_fields
702)]
703pub enum ConfigConditionQuery {
704    Constant(RcStr),
705    Regex(RegexComponents),
706}
707
708impl TryFrom<ConfigConditionQuery> for ConditionQuery {
709    type Error = anyhow::Error;
710
711    fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
712        Ok(match config {
713            ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
714            ConfigConditionQuery::Regex(regex) => {
715                ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
716            }
717        })
718    }
719}
720
721#[derive(
722    Clone,
723    PartialEq,
724    Eq,
725    Debug,
726    Deserialize,
727    TraceRawVcs,
728    NonLocalValue,
729    OperationValue,
730    Encode,
731    Decode,
732)]
733#[serde(
734    tag = "type",
735    content = "value",
736    rename_all = "camelCase",
737    deny_unknown_fields
738)]
739pub enum ConfigConditionContentType {
740    Glob(RcStr),
741    Regex(RegexComponents),
742}
743
744impl TryFrom<ConfigConditionContentType> for ConditionContentType {
745    type Error = anyhow::Error;
746
747    fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
748        Ok(match config {
749            ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
750            ConfigConditionContentType::Regex(regex) => {
751                ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
752            }
753        })
754    }
755}
756
757#[derive(
758    Deserialize,
759    Clone,
760    PartialEq,
761    Eq,
762    Debug,
763    TraceRawVcs,
764    NonLocalValue,
765    OperationValue,
766    Encode,
767    Decode,
768)]
769// We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
770// match nearly every object, since it has no required field.
771#[serde(deny_unknown_fields)]
772pub enum ConfigConditionItem {
773    #[serde(rename = "all")]
774    All(Box<[ConfigConditionItem]>),
775    #[serde(rename = "any")]
776    Any(Box<[ConfigConditionItem]>),
777    #[serde(rename = "not")]
778    Not(Box<ConfigConditionItem>),
779    #[serde(untagged)]
780    Builtin(WebpackLoaderBuiltinCondition),
781    #[serde(untagged)]
782    Base {
783        #[serde(default)]
784        path: Option<ConfigConditionPath>,
785        #[serde(default)]
786        content: Option<RegexComponents>,
787        #[serde(default)]
788        query: Option<ConfigConditionQuery>,
789        #[serde(default, rename = "contentType")]
790        content_type: Option<ConfigConditionContentType>,
791    },
792}
793
794impl TryFrom<ConfigConditionItem> for ConditionItem {
795    type Error = anyhow::Error;
796
797    fn try_from(config: ConfigConditionItem) -> Result<Self> {
798        let try_from_vec = |conds: Box<[_]>| {
799            conds
800                .into_iter()
801                .map(ConditionItem::try_from)
802                .collect::<Result<_>>()
803        };
804        Ok(match config {
805            ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
806            ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
807            ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
808            ConfigConditionItem::Builtin(cond) => {
809                ConditionItem::Builtin(RcStr::from(cond.as_str()))
810            }
811            ConfigConditionItem::Base {
812                path,
813                content,
814                query,
815                content_type,
816            } => ConditionItem::Base {
817                path: path.map(ConditionPath::try_from).transpose()?,
818                content: content
819                    .map(EsRegex::try_from)
820                    .transpose()?
821                    .map(EsRegex::resolved_cell),
822                query: query.map(ConditionQuery::try_from).transpose()?,
823                content_type: content_type
824                    .map(ConditionContentType::try_from)
825                    .transpose()?,
826            },
827        })
828    }
829}
830
831#[derive(
832    Clone,
833    Debug,
834    PartialEq,
835    Eq,
836    Deserialize,
837    TraceRawVcs,
838    NonLocalValue,
839    OperationValue,
840    Encode,
841    Decode,
842)]
843#[serde(rename_all = "camelCase")]
844pub struct RuleConfigItem {
845    #[serde(default)]
846    pub loaders: Vec<LoaderItem>,
847    #[serde(default, alias = "as")]
848    pub rename_as: Option<RcStr>,
849    #[serde(default)]
850    pub condition: Option<ConfigConditionItem>,
851    #[serde(default, alias = "type")]
852    pub module_type: Option<RcStr>,
853}
854
855#[derive(
856    Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
857)]
858pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
859
860impl<'de> Deserialize<'de> for RuleConfigCollection {
861    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
862    where
863        D: Deserializer<'de>,
864    {
865        match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
866            deserializer,
867        )? {
868            Either::Left(collection) => Ok(RuleConfigCollection(collection)),
869            Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
870                item,
871            )])),
872        }
873    }
874}
875
876#[derive(
877    Clone,
878    Debug,
879    PartialEq,
880    Eq,
881    Deserialize,
882    TraceRawVcs,
883    NonLocalValue,
884    OperationValue,
885    Encode,
886    Decode,
887)]
888#[serde(untagged)]
889pub enum RuleConfigCollectionItem {
890    Shorthand(LoaderItem),
891    Full(RuleConfigItem),
892}
893
894#[derive(
895    Clone,
896    Debug,
897    PartialEq,
898    Eq,
899    Deserialize,
900    TraceRawVcs,
901    NonLocalValue,
902    OperationValue,
903    Encode,
904    Decode,
905)]
906#[serde(untagged)]
907pub enum LoaderItem {
908    LoaderName(RcStr),
909    LoaderOptions(WebpackLoaderItem),
910}
911
912#[turbo_tasks::value(operation)]
913#[derive(Copy, Clone, Debug, Deserialize)]
914#[serde(rename_all = "camelCase")]
915pub enum ModuleIds {
916    Named,
917    Deterministic,
918}
919
920#[turbo_tasks::value(transparent)]
921pub struct OptionModuleIds(pub Option<ModuleIds>);
922
923#[derive(
924    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
925)]
926#[serde(untagged)]
927pub enum MdxRsOptions {
928    Boolean(bool),
929    Option(MdxTransformOptions),
930}
931
932#[turbo_tasks::value(shared, operation)]
933#[derive(Clone, Debug, Default, Serialize, Deserialize)]
934#[serde(rename_all = "camelCase")]
935pub enum ReactCompilerCompilationMode {
936    #[default]
937    Infer,
938    Annotation,
939    All,
940}
941
942#[turbo_tasks::value(shared, operation)]
943#[derive(Clone, Debug, Default, Serialize, Deserialize)]
944#[serde(rename_all = "snake_case")]
945pub enum ReactCompilerPanicThreshold {
946    #[default]
947    None,
948    CriticalErrors,
949    AllErrors,
950}
951
952/// Subset of react compiler options, we pass these options through to the webpack loader, so it
953/// must be serializable
954#[turbo_tasks::value(shared, operation)]
955#[derive(Clone, Debug, Default, Serialize, Deserialize)]
956#[serde(rename_all = "camelCase")]
957pub struct ReactCompilerOptions {
958    #[serde(default)]
959    pub compilation_mode: ReactCompilerCompilationMode,
960    #[serde(default)]
961    pub panic_threshold: ReactCompilerPanicThreshold,
962}
963
964#[derive(
965    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
966)]
967#[serde(untagged)]
968pub enum ReactCompilerOptionsOrBoolean {
969    Boolean(bool),
970    Option(ReactCompilerOptions),
971}
972
973#[turbo_tasks::value(transparent)]
974pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
975
976/// Serialized representation of a path pattern for `turbopack.ignoreIssue`.
977/// Strings are serialized as `{ "type": "glob", "value": "..." }` and
978/// RegExp as `{ "type": "regex", "source": "...", "flags": "..." }`.
979#[derive(
980    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
981)]
982#[serde(tag = "type")]
983pub enum TurbopackIgnoreIssuePathPattern {
984    #[serde(rename = "glob")]
985    Glob { value: RcStr },
986    #[serde(rename = "regex")]
987    Regex { source: RcStr, flags: RcStr },
988}
989
990impl TurbopackIgnoreIssuePathPattern {
991    fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
992        match self {
993            TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
994                Glob::parse(value.clone(), GlobOptions::default())?,
995            )),
996            TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
997                Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
998            }
999        }
1000    }
1001}
1002
1003/// Serialized representation of a text pattern (title/description) for
1004/// `turbopack.ignoreIssue`. Strings are serialized as
1005/// `{ "type": "string", "value": "..." }` and RegExp as
1006/// `{ "type": "regex", "source": "...", "flags": "..." }`.
1007#[derive(
1008    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1009)]
1010#[serde(tag = "type")]
1011pub enum TurbopackIgnoreIssueTextPattern {
1012    #[serde(rename = "string")]
1013    String { value: RcStr },
1014    #[serde(rename = "regex")]
1015    Regex { source: RcStr, flags: RcStr },
1016}
1017
1018impl TurbopackIgnoreIssueTextPattern {
1019    fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
1020        match self {
1021            TurbopackIgnoreIssueTextPattern::String { value } => {
1022                Ok(IgnoreIssuePattern::ExactString(value.clone()))
1023            }
1024            TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
1025                Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
1026            }
1027        }
1028    }
1029}
1030
1031/// A single rule in `turbopack.ignoreIssue`.
1032#[derive(
1033    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1034)]
1035pub struct TurbopackIgnoreIssueRule {
1036    pub path: TurbopackIgnoreIssuePathPattern,
1037    #[serde(default)]
1038    pub title: Option<TurbopackIgnoreIssueTextPattern>,
1039    #[serde(default)]
1040    pub description: Option<TurbopackIgnoreIssueTextPattern>,
1041}
1042
1043#[derive(
1044    Clone,
1045    Debug,
1046    Default,
1047    PartialEq,
1048    Deserialize,
1049    TraceRawVcs,
1050    ValueDebugFormat,
1051    NonLocalValue,
1052    OperationValue,
1053    Encode,
1054    Decode,
1055)]
1056#[serde(rename_all = "camelCase")]
1057pub struct ExperimentalConfig {
1058    // all fields should be private and access should be wrapped within a turbo-tasks function
1059    // Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
1060    allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1061    client_router_filter: Option<bool>,
1062    /// decimal for percent for possible false positives e.g. 0.01 for 10%
1063    /// potential false matches lower percent increases size of the filter
1064    client_router_filter_allowed_rate: Option<f64>,
1065    client_router_filter_redirects: Option<bool>,
1066    fetch_cache_key_prefix: Option<RcStr>,
1067    isr_flush_to_disk: Option<bool>,
1068    /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
1069    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
1070    mdx_rs: Option<MdxRsOptions>,
1071    strict_next_head: Option<bool>,
1072    #[bincode(with = "turbo_bincode::serde_self_describing")]
1073    swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
1074    external_middleware_rewrites_resolve: Option<bool>,
1075    scroll_restoration: Option<bool>,
1076    manual_client_base_path: Option<bool>,
1077    optimistic_client_cache: Option<bool>,
1078    middleware_prefetch: Option<MiddlewarePrefetchType>,
1079    /// optimizeCss can be boolean or critters' option object
1080    /// 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))
1081    #[bincode(with = "turbo_bincode::serde_self_describing")]
1082    optimize_css: Option<serde_json::Value>,
1083    next_script_workers: Option<bool>,
1084    web_vitals_attribution: Option<Vec<RcStr>>,
1085    server_actions: Option<ServerActionsOrLegacyBool>,
1086    sri: Option<SubResourceIntegrity>,
1087    /// @deprecated - use top-level cache_components instead.
1088    /// This field is kept for backwards compatibility during migration.
1089    cache_components: Option<bool>,
1090    use_cache: Option<bool>,
1091    root_params: Option<bool>,
1092    runtime_server_deployment_id: Option<bool>,
1093    immutable_asset_token: Option<RcStr>,
1094
1095    // ---
1096    // UNSUPPORTED
1097    // ---
1098    adjust_font_fallbacks: Option<bool>,
1099    adjust_font_fallbacks_with_size_adjust: Option<bool>,
1100    after: Option<bool>,
1101    app_document_preloading: Option<bool>,
1102    app_new_scroll_handler: Option<bool>,
1103    case_sensitive_routes: Option<bool>,
1104    cpus: Option<f64>,
1105    cra_compat: Option<bool>,
1106    disable_optimized_loading: Option<bool>,
1107    disable_postcss_preset_env: Option<bool>,
1108    esm_externals: Option<EsmExternals>,
1109    #[bincode(with = "turbo_bincode::serde_self_describing")]
1110    extension_alias: Option<serde_json::Value>,
1111    external_dir: Option<bool>,
1112    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
1113    /// in the browser Full list of old polyfills is accessible here:
1114    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
1115    fallback_node_polyfills: Option<bool>, // false
1116    force_swc_transforms: Option<bool>,
1117    fully_specified: Option<bool>,
1118    gzip_size: Option<bool>,
1119
1120    inline_css: Option<bool>,
1121    instrumentation_hook: Option<bool>,
1122    client_trace_metadata: Option<Vec<String>>,
1123    large_page_data_bytes: Option<f64>,
1124    #[bincode(with = "turbo_bincode::serde_self_describing")]
1125    logging: Option<serde_json::Value>,
1126    memory_based_workers_count: Option<bool>,
1127    /// Optimize React APIs for server builds.
1128    optimize_server_react: Option<bool>,
1129    /// Automatically apply the "modularize_imports" optimization to imports of
1130    /// the specified packages.
1131    optimize_package_imports: Option<Vec<RcStr>>,
1132    taint: Option<bool>,
1133    proxy_timeout: Option<f64>,
1134    /// enables the minification of server code.
1135    server_minification: Option<bool>,
1136    /// Enables source maps generation for the server production bundle.
1137    server_source_maps: Option<bool>,
1138    swc_trace_profiling: Option<bool>,
1139    transition_indicator: Option<bool>,
1140    gesture_transition: Option<bool>,
1141    /// @internal Used by the Next.js internals only.
1142    trust_host_header: Option<bool>,
1143
1144    #[bincode(with = "turbo_bincode::serde_self_describing")]
1145    url_imports: Option<serde_json::Value>,
1146    /// This option is to enable running the Webpack build in a worker thread
1147    /// (doesn't apply to Turbopack).
1148    webpack_build_worker: Option<bool>,
1149    worker_threads: Option<bool>,
1150
1151    turbopack_minify: Option<bool>,
1152    turbopack_module_ids: Option<ModuleIds>,
1153    turbopack_source_maps: Option<bool>,
1154    turbopack_input_source_maps: Option<bool>,
1155    turbopack_tree_shaking: Option<bool>,
1156    turbopack_scope_hoisting: Option<bool>,
1157    turbopack_client_side_nested_async_chunking: Option<bool>,
1158    turbopack_server_side_nested_async_chunking: Option<bool>,
1159    turbopack_import_type_bytes: Option<bool>,
1160    turbopack_import_type_text: Option<bool>,
1161    /// Disable automatic configuration of the sass loader.
1162    #[serde(default)]
1163    turbopack_use_builtin_sass: Option<bool>,
1164    /// Disable automatic configuration of the babel loader when a babel configuration file is
1165    /// present.
1166    #[serde(default)]
1167    turbopack_use_builtin_babel: Option<bool>,
1168    // Whether to enable the global-not-found convention
1169    global_not_found: Option<bool>,
1170    /// Defaults to false in development mode, true in production mode.
1171    turbopack_remove_unused_imports: Option<bool>,
1172    /// Defaults to false in development mode, true in production mode.
1173    turbopack_remove_unused_exports: Option<bool>,
1174    /// Enable local analysis to infer side effect free modules. Defaults to true.
1175    turbopack_infer_module_side_effects: Option<bool>,
1176    /// Devtool option for the segment explorer.
1177    devtool_segment_explorer: Option<bool>,
1178    /// Whether to report inlined system environment variables as warnings or errors.
1179    report_system_env_inlining: Option<String>,
1180    // Use project.is_persistent_caching() instead
1181    // turbopack_file_system_cache_for_dev: Option<bool>,
1182    // turbopack_file_system_cache_for_build: Option<bool>,
1183}
1184
1185#[derive(
1186    Clone,
1187    Debug,
1188    PartialEq,
1189    Eq,
1190    Deserialize,
1191    TraceRawVcs,
1192    NonLocalValue,
1193    OperationValue,
1194    Encode,
1195    Decode,
1196)]
1197#[serde(rename_all = "camelCase")]
1198pub struct SubResourceIntegrity {
1199    pub algorithm: Option<RcStr>,
1200}
1201
1202#[derive(
1203    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1204)]
1205#[serde(untagged)]
1206pub enum ServerActionsOrLegacyBool {
1207    /// The current way to configure server actions sub behaviors.
1208    ServerActionsConfig(ServerActions),
1209
1210    /// The legacy way to disable server actions. This is no longer used, server
1211    /// actions is always enabled.
1212    LegacyBool(bool),
1213}
1214
1215#[derive(
1216    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1217)]
1218#[serde(rename_all = "kebab-case")]
1219pub enum EsmExternalsValue {
1220    Loose,
1221}
1222
1223#[derive(
1224    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1225)]
1226#[serde(untagged)]
1227pub enum EsmExternals {
1228    Loose(EsmExternalsValue),
1229    Bool(bool),
1230}
1231
1232// Test for esm externals deserialization.
1233#[test]
1234fn test_esm_externals_deserialization() {
1235    let json = serde_json::json!({
1236        "esmExternals": true
1237    });
1238    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1239    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1240
1241    let json = serde_json::json!({
1242        "esmExternals": "loose"
1243    });
1244    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1245    assert_eq!(
1246        config.esm_externals,
1247        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1248    );
1249}
1250
1251#[derive(
1252    Clone,
1253    Debug,
1254    Default,
1255    PartialEq,
1256    Eq,
1257    Deserialize,
1258    TraceRawVcs,
1259    NonLocalValue,
1260    OperationValue,
1261    Encode,
1262    Decode,
1263)]
1264#[serde(rename_all = "camelCase")]
1265pub struct ServerActions {
1266    /// Allows adjusting body parser size limit for server actions.
1267    pub body_size_limit: Option<SizeLimit>,
1268}
1269
1270#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1271#[serde(untagged)]
1272pub enum SizeLimit {
1273    Number(f64),
1274    WithUnit(String),
1275}
1276
1277// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
1278// implement Eq.
1279impl PartialEq for SizeLimit {
1280    fn eq(&self, other: &Self) -> bool {
1281        match (self, other) {
1282            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1283            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1284            _ => false,
1285        }
1286    }
1287}
1288
1289impl Eq for SizeLimit {}
1290
1291#[derive(
1292    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1293)]
1294#[serde(rename_all = "kebab-case")]
1295pub enum MiddlewarePrefetchType {
1296    Strict,
1297    Flexible,
1298}
1299
1300#[derive(
1301    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1302)]
1303#[serde(untagged)]
1304pub enum EmotionTransformOptionsOrBoolean {
1305    Boolean(bool),
1306    Options(EmotionTransformConfig),
1307}
1308
1309impl EmotionTransformOptionsOrBoolean {
1310    pub fn is_enabled(&self) -> bool {
1311        match self {
1312            Self::Boolean(enabled) => *enabled,
1313            _ => true,
1314        }
1315    }
1316}
1317
1318#[derive(
1319    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1320)]
1321#[serde(untagged)]
1322pub enum StyledComponentsTransformOptionsOrBoolean {
1323    Boolean(bool),
1324    Options(StyledComponentsTransformConfig),
1325}
1326
1327impl StyledComponentsTransformOptionsOrBoolean {
1328    pub fn is_enabled(&self) -> bool {
1329        match self {
1330            Self::Boolean(enabled) => *enabled,
1331            _ => true,
1332        }
1333    }
1334}
1335
1336#[turbo_tasks::value(eq = "manual")]
1337#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1338#[serde(rename_all = "camelCase")]
1339pub struct CompilerConfig {
1340    pub react_remove_properties: Option<ReactRemoveProperties>,
1341    pub relay: Option<RelayConfig>,
1342    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1343    pub remove_console: Option<RemoveConsoleConfig>,
1344    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1345}
1346
1347#[derive(
1348    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1349)]
1350#[serde(untagged, rename_all = "camelCase")]
1351pub enum ReactRemoveProperties {
1352    Boolean(bool),
1353    Config { properties: Option<Vec<String>> },
1354}
1355
1356impl ReactRemoveProperties {
1357    pub fn is_enabled(&self) -> bool {
1358        match self {
1359            Self::Boolean(enabled) => *enabled,
1360            _ => true,
1361        }
1362    }
1363}
1364
1365#[derive(
1366    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1367)]
1368#[serde(untagged)]
1369pub enum RemoveConsoleConfig {
1370    Boolean(bool),
1371    Config { exclude: Option<Vec<String>> },
1372}
1373
1374impl RemoveConsoleConfig {
1375    pub fn is_enabled(&self) -> bool {
1376        match self {
1377            Self::Boolean(enabled) => *enabled,
1378            _ => true,
1379        }
1380    }
1381}
1382
1383#[turbo_tasks::value(transparent)]
1384pub struct ResolveExtensions(Option<Vec<RcStr>>);
1385
1386#[turbo_tasks::value(transparent)]
1387pub struct SwcPlugins(
1388    #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1389);
1390
1391#[turbo_tasks::value(transparent)]
1392pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1393
1394#[turbo_tasks::value(transparent)]
1395
1396pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1397
1398#[turbo_tasks::value(transparent)]
1399pub struct OptionFileSystemPath(Option<FileSystemPath>);
1400
1401#[turbo_tasks::value(transparent)]
1402pub struct OptionServerActions(Option<ServerActions>);
1403
1404#[turbo_tasks::value(transparent)]
1405pub struct IgnoreIssues(Vec<IgnoreIssue>);
1406
1407#[turbo_tasks::value(transparent)]
1408pub struct OptionJsonValue(
1409    #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1410);
1411
1412fn turbopack_config_documentation_link() -> RcStr {
1413    rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1414}
1415
1416#[turbo_tasks::value(shared)]
1417struct InvalidLoaderRuleRenameAsIssue {
1418    glob: RcStr,
1419    rename_as: RcStr,
1420    config_file_path: FileSystemPath,
1421}
1422
1423#[turbo_tasks::value_impl]
1424impl Issue for InvalidLoaderRuleRenameAsIssue {
1425    #[turbo_tasks::function]
1426    async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1427        Ok(self.config_file_path.clone().cell())
1428    }
1429
1430    #[turbo_tasks::function]
1431    fn stage(&self) -> Vc<IssueStage> {
1432        IssueStage::Config.cell()
1433    }
1434
1435    #[turbo_tasks::function]
1436    async fn title(&self) -> Result<Vc<StyledString>> {
1437        Ok(
1438            StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1439                .cell(),
1440        )
1441    }
1442
1443    #[turbo_tasks::function]
1444    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1445        Ok(Vc::cell(Some(
1446            StyledString::Text(RcStr::from(format!(
1447                "The extension {} contains a wildcard, but the `as` option does not: {}",
1448                self.glob, self.rename_as,
1449            )))
1450            .resolved_cell(),
1451        )))
1452    }
1453
1454    #[turbo_tasks::function]
1455    fn documentation_link(&self) -> Vc<RcStr> {
1456        Vc::cell(turbopack_config_documentation_link())
1457    }
1458}
1459
1460#[turbo_tasks::value(shared)]
1461struct InvalidLoaderRuleConditionIssue {
1462    error_string: RcStr,
1463    condition: ConfigConditionItem,
1464    config_file_path: FileSystemPath,
1465}
1466
1467#[turbo_tasks::value_impl]
1468impl Issue for InvalidLoaderRuleConditionIssue {
1469    #[turbo_tasks::function]
1470    async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1471        Ok(self.await?.config_file_path.clone().cell())
1472    }
1473
1474    #[turbo_tasks::function]
1475    fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1476        IssueStage::Config.cell()
1477    }
1478
1479    #[turbo_tasks::function]
1480    async fn title(&self) -> Result<Vc<StyledString>> {
1481        Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1482    }
1483
1484    #[turbo_tasks::function]
1485    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1486        Ok(Vc::cell(Some(
1487            StyledString::Stack(vec![
1488                StyledString::Line(vec![
1489                    StyledString::Text(rcstr!("Encountered the following error: ")),
1490                    StyledString::Code(self.error_string.clone()),
1491                ]),
1492                StyledString::Text(rcstr!("While processing the condition:")),
1493                StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1494            ])
1495            .resolved_cell(),
1496        )))
1497    }
1498
1499    #[turbo_tasks::function]
1500    fn documentation_link(&self) -> Vc<RcStr> {
1501        Vc::cell(turbopack_config_documentation_link())
1502    }
1503}
1504
1505#[turbo_tasks::value_impl]
1506impl NextConfig {
1507    #[turbo_tasks::function]
1508    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1509        let string = string.await?;
1510        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1511        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1512            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1513        Ok(config.cell())
1514    }
1515
1516    #[turbo_tasks::function]
1517    pub async fn config_file_path(
1518        &self,
1519        project_path: FileSystemPath,
1520    ) -> Result<Vc<FileSystemPath>> {
1521        Ok(project_path.join(&self.config_file_name)?.cell())
1522    }
1523
1524    #[turbo_tasks::function]
1525    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1526        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1527    }
1528
1529    #[turbo_tasks::function]
1530    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1531        Vc::cell(self.react_production_profiling.unwrap_or_default())
1532    }
1533
1534    #[turbo_tasks::function]
1535    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1536        Vc::cell(
1537            self.server_external_packages
1538                .as_ref()
1539                .cloned()
1540                .unwrap_or_default(),
1541        )
1542    }
1543
1544    #[turbo_tasks::function]
1545    pub fn is_standalone(&self) -> Vc<bool> {
1546        Vc::cell(self.output == Some(OutputType::Standalone))
1547    }
1548
1549    #[turbo_tasks::function]
1550    pub fn base_path(&self) -> Vc<Option<RcStr>> {
1551        Vc::cell(self.base_path.clone())
1552    }
1553
1554    #[turbo_tasks::function]
1555    pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1556        if let Some(handler) = &self.cache_handler {
1557            Ok(Vc::cell(Some(project_path.join(handler)?)))
1558        } else {
1559            Ok(Vc::cell(None))
1560        }
1561    }
1562
1563    #[turbo_tasks::function]
1564    pub fn compiler(&self) -> Vc<CompilerConfig> {
1565        self.compiler.clone().unwrap_or_default().cell()
1566    }
1567
1568    #[turbo_tasks::function]
1569    pub fn env(&self) -> Vc<EnvMap> {
1570        // The value expected for env is Record<String, String>, but config itself
1571        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1572        // then stringifies it. We do the interop here as well.
1573        let env = self
1574            .env
1575            .iter()
1576            .map(|(k, v)| {
1577                (
1578                    k.as_str().into(),
1579                    if let JsonValue::String(s) = v {
1580                        // A string value is kept, calling `to_string` would wrap in to quotes.
1581                        s.as_str().into()
1582                    } else {
1583                        v.to_string().into()
1584                    },
1585                )
1586            })
1587            .collect();
1588
1589        Vc::cell(env)
1590    }
1591
1592    #[turbo_tasks::function]
1593    pub fn image_config(&self) -> Vc<ImageConfig> {
1594        self.images.clone().cell()
1595    }
1596
1597    #[turbo_tasks::function]
1598    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1599        // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
1600        // which just builds a regex alternative, which greedily matches the longest
1601        // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
1602        let mut extensions = self.page_extensions.clone();
1603        extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1604        Vc::cell(extensions)
1605    }
1606
1607    #[turbo_tasks::function]
1608    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1609        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1610    }
1611
1612    #[turbo_tasks::function]
1613    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1614        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1615    }
1616
1617    #[turbo_tasks::function]
1618    pub async fn webpack_rules(
1619        self: Vc<Self>,
1620        project_path: FileSystemPath,
1621    ) -> Result<Vc<WebpackRules>> {
1622        let this = self.await?;
1623        let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1624            return Ok(Vc::cell(Vec::new()));
1625        };
1626        if turbo_rules.is_empty() {
1627            return Ok(Vc::cell(Vec::new()));
1628        }
1629        let mut rules = Vec::new();
1630        for (glob, rule_collection) in turbo_rules.iter() {
1631            fn transform_loaders(
1632                loaders: &mut dyn Iterator<Item = &LoaderItem>,
1633            ) -> ResolvedVc<WebpackLoaderItems> {
1634                ResolvedVc::cell(
1635                    loaders
1636                        .map(|item| match item {
1637                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1638                                loader: name.clone(),
1639                                options: Default::default(),
1640                            },
1641                            LoaderItem::LoaderOptions(options) => options.clone(),
1642                        })
1643                        .collect(),
1644                )
1645            }
1646            for item in &rule_collection.0 {
1647                match item {
1648                    RuleConfigCollectionItem::Shorthand(loaders) => {
1649                        rules.push((
1650                            glob.clone(),
1651                            LoaderRuleItem {
1652                                loaders: transform_loaders(&mut [loaders].into_iter()),
1653                                rename_as: None,
1654                                condition: None,
1655                                module_type: None,
1656                            },
1657                        ));
1658                    }
1659                    RuleConfigCollectionItem::Full(RuleConfigItem {
1660                        loaders,
1661                        rename_as,
1662                        condition,
1663                        module_type,
1664                    }) => {
1665                        // If the extension contains a wildcard, and the rename_as does not,
1666                        // emit an issue to prevent users from encountering duplicate module
1667                        // names.
1668                        if glob.contains("*")
1669                            && let Some(rename_as) = rename_as.as_ref()
1670                            && !rename_as.contains("*")
1671                        {
1672                            InvalidLoaderRuleRenameAsIssue {
1673                                glob: glob.clone(),
1674                                config_file_path: self
1675                                    .config_file_path(project_path.clone())
1676                                    .owned()
1677                                    .await?,
1678                                rename_as: rename_as.clone(),
1679                            }
1680                            .resolved_cell()
1681                            .emit();
1682                        }
1683
1684                        // convert from Next.js-specific condition type to internal Turbopack
1685                        // condition type
1686                        let condition = if let Some(condition) = condition {
1687                            match ConditionItem::try_from(condition.clone()) {
1688                                Ok(cond) => Some(cond),
1689                                Err(err) => {
1690                                    InvalidLoaderRuleConditionIssue {
1691                                        error_string: RcStr::from(err.to_string()),
1692                                        condition: condition.clone(),
1693                                        config_file_path: self
1694                                            .config_file_path(project_path.clone())
1695                                            .owned()
1696                                            .await?,
1697                                    }
1698                                    .resolved_cell()
1699                                    .emit();
1700                                    None
1701                                }
1702                            }
1703                        } else {
1704                            None
1705                        };
1706                        rules.push((
1707                            glob.clone(),
1708                            LoaderRuleItem {
1709                                loaders: transform_loaders(&mut loaders.iter()),
1710                                rename_as: rename_as.clone(),
1711                                condition,
1712                                module_type: module_type.clone(),
1713                            },
1714                        ));
1715                    }
1716                }
1717            }
1718        }
1719        Ok(Vc::cell(rules))
1720    }
1721
1722    #[turbo_tasks::function]
1723    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1724        let Some(resolve_alias) = self
1725            .turbopack
1726            .as_ref()
1727            .and_then(|t| t.resolve_alias.as_ref())
1728        else {
1729            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1730        };
1731        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1732        Ok(alias_map.cell())
1733    }
1734
1735    #[turbo_tasks::function]
1736    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1737        let Some(resolve_extensions) = self
1738            .turbopack
1739            .as_ref()
1740            .and_then(|t| t.resolve_extensions.as_ref())
1741        else {
1742            return Vc::cell(None);
1743        };
1744        Vc::cell(Some(resolve_extensions.clone()))
1745    }
1746
1747    #[turbo_tasks::function]
1748    pub fn import_externals(&self) -> Result<Vc<bool>> {
1749        Ok(Vc::cell(match self.experimental.esm_externals {
1750            Some(EsmExternals::Bool(b)) => b,
1751            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1752            None => true,
1753        }))
1754    }
1755
1756    #[turbo_tasks::function]
1757    pub fn inline_css(&self) -> Vc<bool> {
1758        Vc::cell(self.experimental.inline_css.unwrap_or(false))
1759    }
1760
1761    #[turbo_tasks::function]
1762    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1763        let options = &self.experimental.mdx_rs;
1764
1765        let options = match options {
1766            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1767                MdxTransformOptions {
1768                    provider_import_source: Some(mdx_import_source_file()),
1769                    ..Default::default()
1770                }
1771                .resolved_cell(),
1772            )),
1773            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1774                MdxTransformOptions {
1775                    provider_import_source: Some(
1776                        options
1777                            .provider_import_source
1778                            .clone()
1779                            .unwrap_or(mdx_import_source_file()),
1780                    ),
1781                    ..options.clone()
1782                }
1783                .resolved_cell(),
1784            )),
1785            _ => OptionalMdxTransformOptions(None),
1786        };
1787
1788        options.cell()
1789    }
1790
1791    #[turbo_tasks::function]
1792    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1793        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1794    }
1795
1796    #[turbo_tasks::function]
1797    pub fn dist_dir(&self) -> Vc<RcStr> {
1798        Vc::cell(self.dist_dir.clone())
1799    }
1800    #[turbo_tasks::function]
1801    pub fn dist_dir_root(&self) -> Vc<RcStr> {
1802        Vc::cell(self.dist_dir_root.clone())
1803    }
1804
1805    #[turbo_tasks::function]
1806    pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1807        if let Some(handlers) = &self.cache_handlers {
1808            Ok(Vc::cell(
1809                handlers
1810                    .values()
1811                    .map(|h| project_path.join(h))
1812                    .collect::<Result<Vec<_>>>()?,
1813            ))
1814        } else {
1815            Ok(Vc::cell(vec![]))
1816        }
1817    }
1818
1819    #[turbo_tasks::function]
1820    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1821        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1822    }
1823
1824    #[turbo_tasks::function]
1825    pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1826        Vc::cell(self.experimental.sri.clone())
1827    }
1828
1829    #[turbo_tasks::function]
1830    pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1831        Vc::cell(self.experimental.turbopack_use_builtin_babel)
1832    }
1833
1834    #[turbo_tasks::function]
1835    pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1836        Vc::cell(self.experimental.turbopack_use_builtin_sass)
1837    }
1838
1839    #[turbo_tasks::function]
1840    pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1841        let options = &self.react_compiler;
1842
1843        let options = match options {
1844            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1845                OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1846            }
1847            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1848                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1849            ),
1850            _ => OptionalReactCompilerOptions(None),
1851        };
1852
1853        options.cell()
1854    }
1855
1856    #[turbo_tasks::function]
1857    pub fn sass_config(&self) -> Vc<JsonValue> {
1858        Vc::cell(self.sass_options.clone().unwrap_or_default())
1859    }
1860
1861    #[turbo_tasks::function]
1862    pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1863        Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1864    }
1865
1866    #[turbo_tasks::function]
1867    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1868        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1869    }
1870
1871    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
1872    /// Otherwise, the basePath is used.
1873    #[turbo_tasks::function]
1874    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1875        let this = self.await?;
1876
1877        Ok(Vc::cell(
1878            format!(
1879                "{}/_next/",
1880                if let Some(asset_prefix) = &this.asset_prefix {
1881                    asset_prefix
1882                } else {
1883                    this.base_path.as_ref().map_or("", |b| b.as_str())
1884                }
1885                .trim_end_matches('/')
1886            )
1887            .into(),
1888        ))
1889    }
1890
1891    /// Returns the suffix to use for chunk loading.
1892    #[turbo_tasks::function]
1893    pub fn asset_suffix_path(&self) -> Vc<Option<RcStr>> {
1894        let id = self
1895            .experimental
1896            .immutable_asset_token
1897            .as_ref()
1898            .or(self.deployment_id.as_ref());
1899
1900        Vc::cell(id.as_ref().map(|id| format!("?dpl={id}").into()))
1901    }
1902
1903    /// Whether to enable immutable assets, which uses a different asset suffix, and writes a
1904    /// .next/immutable-static-hashes.json manifest.
1905    #[turbo_tasks::function]
1906    pub fn enable_immutable_assets(&self) -> Vc<bool> {
1907        Vc::cell(self.experimental.immutable_asset_token.is_some())
1908    }
1909
1910    #[turbo_tasks::function]
1911    pub fn enable_taint(&self) -> Vc<bool> {
1912        Vc::cell(self.experimental.taint.unwrap_or(false))
1913    }
1914
1915    #[turbo_tasks::function]
1916    pub fn enable_transition_indicator(&self) -> Vc<bool> {
1917        Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1918    }
1919
1920    #[turbo_tasks::function]
1921    pub fn enable_gesture_transition(&self) -> Vc<bool> {
1922        Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
1923    }
1924
1925    #[turbo_tasks::function]
1926    pub fn enable_app_new_scroll_handler(&self) -> Vc<bool> {
1927        Vc::cell(self.experimental.app_new_scroll_handler.unwrap_or(false))
1928    }
1929
1930    #[turbo_tasks::function]
1931    pub fn enable_cache_components(&self) -> Vc<bool> {
1932        Vc::cell(self.cache_components.unwrap_or(false))
1933    }
1934
1935    #[turbo_tasks::function]
1936    pub fn enable_use_cache(&self) -> Vc<bool> {
1937        Vc::cell(
1938            self.experimental
1939                .use_cache
1940                // "use cache" was originally implicitly enabled with the
1941                // cacheComponents flag, so we transfer the value for cacheComponents to the
1942                // explicit useCache flag to ensure backwards compatibility.
1943                .unwrap_or(self.cache_components.unwrap_or(false)),
1944        )
1945    }
1946
1947    #[turbo_tasks::function]
1948    pub fn enable_root_params(&self) -> Vc<bool> {
1949        Vc::cell(
1950            self.experimental
1951                .root_params
1952                // rootParams should be enabled implicitly in cacheComponents.
1953                .unwrap_or(self.cache_components.unwrap_or(false)),
1954        )
1955    }
1956
1957    #[turbo_tasks::function]
1958    pub fn runtime_server_deployment_id_available(&self) -> Vc<bool> {
1959        Vc::cell(
1960            self.experimental
1961                .runtime_server_deployment_id
1962                .unwrap_or(false),
1963        )
1964    }
1965
1966    #[turbo_tasks::function]
1967    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1968        let mut cache_kinds = CacheKinds::default();
1969
1970        if let Some(handlers) = self.cache_handlers.as_ref() {
1971            cache_kinds.extend(handlers.keys().cloned());
1972        }
1973
1974        cache_kinds.cell()
1975    }
1976
1977    #[turbo_tasks::function]
1978    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1979        Vc::cell(
1980            self.experimental
1981                .optimize_package_imports
1982                .clone()
1983                .unwrap_or_default(),
1984        )
1985    }
1986
1987    #[turbo_tasks::function]
1988    pub fn tree_shaking_mode_for_foreign_code(
1989        &self,
1990        _is_development: bool,
1991    ) -> Vc<OptionTreeShaking> {
1992        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1993            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1994            Some(true) => Some(TreeShakingMode::ModuleFragments),
1995            None => Some(TreeShakingMode::ReexportsOnly),
1996        })
1997        .cell()
1998    }
1999
2000    #[turbo_tasks::function]
2001    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
2002        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2003            Some(false) => Some(TreeShakingMode::ReexportsOnly),
2004            Some(true) => Some(TreeShakingMode::ModuleFragments),
2005            None => Some(TreeShakingMode::ReexportsOnly),
2006        })
2007        .cell()
2008    }
2009
2010    #[turbo_tasks::function]
2011    pub async fn turbopack_remove_unused_imports(
2012        self: Vc<Self>,
2013        mode: Vc<NextMode>,
2014    ) -> Result<Vc<bool>> {
2015        let remove_unused_imports = self
2016            .await?
2017            .experimental
2018            .turbopack_remove_unused_imports
2019            .unwrap_or(matches!(*mode.await?, NextMode::Build));
2020
2021        if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2022            bail!(
2023                "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2024                 enabling `experimental.turbopackRemoveUnusedExports`"
2025            );
2026        }
2027
2028        Ok(Vc::cell(remove_unused_imports))
2029    }
2030
2031    #[turbo_tasks::function]
2032    pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2033        Ok(Vc::cell(
2034            self.experimental
2035                .turbopack_remove_unused_exports
2036                .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2037        ))
2038    }
2039
2040    #[turbo_tasks::function]
2041    pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2042        Vc::cell(
2043            self.experimental
2044                .turbopack_infer_module_side_effects
2045                .unwrap_or(true),
2046        )
2047    }
2048
2049    #[turbo_tasks::function]
2050    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2051        Ok(match *mode.await? {
2052            // Ignore configuration in development mode, HMR only works with `named`
2053            NextMode::Development => ModuleIds::Named.cell(),
2054            NextMode::Build => self
2055                .experimental
2056                .turbopack_module_ids
2057                .unwrap_or(ModuleIds::Deterministic)
2058                .cell(),
2059        })
2060    }
2061
2062    #[turbo_tasks::function]
2063    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2064        let minify = self.experimental.turbopack_minify;
2065        Ok(Vc::cell(
2066            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2067        ))
2068    }
2069
2070    #[turbo_tasks::function]
2071    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2072        Ok(Vc::cell(match *mode.await? {
2073            // Ignore configuration in development mode to not break HMR
2074            NextMode::Development => false,
2075            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2076        }))
2077    }
2078
2079    #[turbo_tasks::function]
2080    pub async fn turbo_nested_async_chunking(
2081        &self,
2082        mode: Vc<NextMode>,
2083        client_side: bool,
2084    ) -> Result<Vc<bool>> {
2085        let option = if client_side {
2086            self.experimental
2087                .turbopack_client_side_nested_async_chunking
2088        } else {
2089            self.experimental
2090                .turbopack_server_side_nested_async_chunking
2091        };
2092        Ok(Vc::cell(if let Some(value) = option {
2093            value
2094        } else {
2095            match *mode.await? {
2096                NextMode::Development => false,
2097                NextMode::Build => client_side,
2098            }
2099        }))
2100    }
2101
2102    #[turbo_tasks::function]
2103    pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2104        Vc::cell(
2105            self.experimental
2106                .turbopack_import_type_bytes
2107                .unwrap_or(false),
2108        )
2109    }
2110
2111    #[turbo_tasks::function]
2112    pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2113        Vc::cell(
2114            self.experimental
2115                .turbopack_import_type_text
2116                .unwrap_or(false),
2117        )
2118    }
2119
2120    #[turbo_tasks::function]
2121    pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2122        let input_source_maps = self
2123            .experimental
2124            .turbopack_input_source_maps
2125            .unwrap_or(true);
2126        let source_maps = self
2127            .experimental
2128            .turbopack_source_maps
2129            .unwrap_or(match &*mode.await? {
2130                NextMode::Development => true,
2131                NextMode::Build => self.production_browser_source_maps,
2132            });
2133        Ok(match (source_maps, input_source_maps) {
2134            (true, true) => SourceMapsType::Full,
2135            (true, false) => SourceMapsType::Partial,
2136            (false, _) => SourceMapsType::None,
2137        }
2138        .cell())
2139    }
2140
2141    #[turbo_tasks::function]
2142    pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2143        let input_source_maps = self
2144            .experimental
2145            .turbopack_input_source_maps
2146            .unwrap_or(true);
2147        let source_maps = self
2148            .experimental
2149            .turbopack_source_maps
2150            .or(self.experimental.server_source_maps)
2151            .unwrap_or(true);
2152        Ok(match (source_maps, input_source_maps) {
2153            (true, true) => SourceMapsType::Full,
2154            (true, false) => SourceMapsType::Partial,
2155            (false, _) => SourceMapsType::None,
2156        }
2157        .cell())
2158    }
2159
2160    #[turbo_tasks::function]
2161    pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2162        Vc::cell(
2163            self.turbopack
2164                .as_ref()
2165                .and_then(|turbopack| turbopack.debug_ids)
2166                .unwrap_or(false),
2167        )
2168    }
2169
2170    #[turbo_tasks::function]
2171    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2172        Ok(Vc::cell(
2173            self.typescript
2174                .tsconfig_path
2175                .as_ref()
2176                .map(|path| path.to_owned().into()),
2177        ))
2178    }
2179
2180    #[turbo_tasks::function]
2181    pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2182        Vc::cell(self.cross_origin.clone())
2183    }
2184
2185    #[turbo_tasks::function]
2186    pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2187        Vc::cell(self.i18n.clone())
2188    }
2189
2190    #[turbo_tasks::function]
2191    pub fn output(&self) -> Vc<OptionOutputType> {
2192        Vc::cell(self.output.clone())
2193    }
2194
2195    #[turbo_tasks::function]
2196    pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2197        Vc::cell(self.output_file_tracing_includes.clone())
2198    }
2199
2200    #[turbo_tasks::function]
2201    pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2202        Vc::cell(self.output_file_tracing_excludes.clone())
2203    }
2204
2205    #[turbo_tasks::function]
2206    pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2207        FetchClientConfig::default().cell()
2208    }
2209
2210    #[turbo_tasks::function]
2211    pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2212        match self.experimental.report_system_env_inlining.as_deref() {
2213            None => Ok(IssueSeverity::Suggestion.cell()),
2214            Some("warn") => Ok(IssueSeverity::Warning.cell()),
2215            Some("error") => Ok(IssueSeverity::Error.cell()),
2216            _ => bail!(
2217                "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2218            ),
2219        }
2220    }
2221
2222    /// Returns the list of ignore-issue rules from the turbopack config,
2223    /// converted to the `IgnoreIssue` type used by `IssueFilter`.
2224    #[turbo_tasks::function]
2225    pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2226        let rules = self
2227            .turbopack
2228            .as_ref()
2229            .and_then(|tp| tp.ignore_issue.as_deref())
2230            .unwrap_or_default()
2231            .iter()
2232            .map(|rule| {
2233                Ok(IgnoreIssue {
2234                    path: rule.path.to_ignore_pattern()?,
2235                    title: rule
2236                        .title
2237                        .as_ref()
2238                        .map(|t| t.to_ignore_pattern())
2239                        .transpose()?,
2240                    description: rule
2241                        .description
2242                        .as_ref()
2243                        .map(|d| d.to_ignore_pattern())
2244                        .transpose()?,
2245                })
2246            })
2247            .collect::<Result<Vec<_>>>()?;
2248        Ok(Vc::cell(rules))
2249    }
2250}
2251
2252/// A subset of ts/jsconfig that next.js implicitly
2253/// interops with.
2254#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2255#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2256#[serde(rename_all = "camelCase")]
2257pub struct JsConfig {
2258    #[bincode(with = "turbo_bincode::serde_self_describing")]
2259    compiler_options: Option<serde_json::Value>,
2260}
2261
2262#[turbo_tasks::value_impl]
2263impl JsConfig {
2264    #[turbo_tasks::function]
2265    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2266        let string = string.await?;
2267        let config: JsConfig = serde_json::from_str(&string)
2268            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2269
2270        Ok(config.cell())
2271    }
2272
2273    #[turbo_tasks::function]
2274    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2275        Vc::cell(self.compiler_options.clone().unwrap_or_default())
2276    }
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281    use super::*;
2282
2283    #[test]
2284    fn test_serde_rule_config_item_options() {
2285        let json_value = serde_json::json!({
2286            "loaders": [],
2287            "as": "*.js",
2288            "condition": {
2289                "all": [
2290                    "production",
2291                    {"not": "foreign"},
2292                    {"any": [
2293                        "browser",
2294                        {
2295                            "path": { "type": "glob", "value": "*.svg"},
2296                            "query": {
2297                                "type": "regex",
2298                                "value": {
2299                                    "source": "@someQuery",
2300                                    "flags": ""
2301                                }
2302                            },
2303                            "content": {
2304                                "source": "@someTag",
2305                                "flags": ""
2306                            }
2307                        }
2308                    ]},
2309                ],
2310            }
2311        });
2312
2313        let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2314
2315        assert_eq!(
2316            rule_config,
2317            RuleConfigItem {
2318                loaders: vec![],
2319                rename_as: Some(rcstr!("*.js")),
2320                module_type: None,
2321                condition: Some(ConfigConditionItem::All(
2322                    [
2323                        ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2324                        ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2325                            WebpackLoaderBuiltinCondition::Foreign
2326                        ))),
2327                        ConfigConditionItem::Any(
2328                            vec![
2329                                ConfigConditionItem::Builtin(
2330                                    WebpackLoaderBuiltinCondition::Browser
2331                                ),
2332                                ConfigConditionItem::Base {
2333                                    path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2334                                    content: Some(RegexComponents {
2335                                        source: rcstr!("@someTag"),
2336                                        flags: rcstr!(""),
2337                                    }),
2338                                    query: Some(ConfigConditionQuery::Regex(RegexComponents {
2339                                        source: rcstr!("@someQuery"),
2340                                        flags: rcstr!(""),
2341                                    })),
2342                                    content_type: None,
2343                                },
2344                            ]
2345                            .into(),
2346                        ),
2347                    ]
2348                    .into(),
2349                )),
2350            }
2351        );
2352    }
2353}