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
1094    // ---
1095    // UNSUPPORTED
1096    // ---
1097    adjust_font_fallbacks: Option<bool>,
1098    adjust_font_fallbacks_with_size_adjust: Option<bool>,
1099    after: Option<bool>,
1100    app_document_preloading: Option<bool>,
1101    app_new_scroll_handler: Option<bool>,
1102    case_sensitive_routes: Option<bool>,
1103    cpus: Option<f64>,
1104    cra_compat: Option<bool>,
1105    disable_optimized_loading: Option<bool>,
1106    disable_postcss_preset_env: Option<bool>,
1107    esm_externals: Option<EsmExternals>,
1108    #[bincode(with = "turbo_bincode::serde_self_describing")]
1109    extension_alias: Option<serde_json::Value>,
1110    external_dir: Option<bool>,
1111    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
1112    /// in the browser Full list of old polyfills is accessible here:
1113    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
1114    fallback_node_polyfills: Option<bool>, // false
1115    force_swc_transforms: Option<bool>,
1116    fully_specified: Option<bool>,
1117    gzip_size: Option<bool>,
1118
1119    pub inline_css: Option<bool>,
1120    instrumentation_hook: Option<bool>,
1121    client_trace_metadata: Option<Vec<String>>,
1122    large_page_data_bytes: Option<f64>,
1123    #[bincode(with = "turbo_bincode::serde_self_describing")]
1124    logging: Option<serde_json::Value>,
1125    memory_based_workers_count: Option<bool>,
1126    /// Optimize React APIs for server builds.
1127    optimize_server_react: Option<bool>,
1128    /// Automatically apply the "modularize_imports" optimization to imports of
1129    /// the specified packages.
1130    optimize_package_imports: Option<Vec<RcStr>>,
1131    taint: Option<bool>,
1132    proxy_timeout: Option<f64>,
1133    /// enables the minification of server code.
1134    server_minification: Option<bool>,
1135    /// Enables source maps generation for the server production bundle.
1136    server_source_maps: Option<bool>,
1137    swc_trace_profiling: Option<bool>,
1138    transition_indicator: Option<bool>,
1139    gesture_transition: Option<bool>,
1140    /// @internal Used by the Next.js internals only.
1141    trust_host_header: Option<bool>,
1142
1143    #[bincode(with = "turbo_bincode::serde_self_describing")]
1144    url_imports: Option<serde_json::Value>,
1145    /// This option is to enable running the Webpack build in a worker thread
1146    /// (doesn't apply to Turbopack).
1147    webpack_build_worker: Option<bool>,
1148    worker_threads: Option<bool>,
1149
1150    turbopack_minify: Option<bool>,
1151    turbopack_module_ids: Option<ModuleIds>,
1152    turbopack_source_maps: Option<bool>,
1153    turbopack_input_source_maps: Option<bool>,
1154    turbopack_tree_shaking: Option<bool>,
1155    turbopack_scope_hoisting: Option<bool>,
1156    turbopack_client_side_nested_async_chunking: Option<bool>,
1157    turbopack_server_side_nested_async_chunking: Option<bool>,
1158    turbopack_import_type_bytes: Option<bool>,
1159    turbopack_import_type_text: Option<bool>,
1160    /// Disable automatic configuration of the sass loader.
1161    #[serde(default)]
1162    turbopack_use_builtin_sass: Option<bool>,
1163    /// Disable automatic configuration of the babel loader when a babel configuration file is
1164    /// present.
1165    #[serde(default)]
1166    turbopack_use_builtin_babel: Option<bool>,
1167    // Whether to enable the global-not-found convention
1168    global_not_found: Option<bool>,
1169    /// Defaults to false in development mode, true in production mode.
1170    turbopack_remove_unused_imports: Option<bool>,
1171    /// Defaults to false in development mode, true in production mode.
1172    turbopack_remove_unused_exports: Option<bool>,
1173    /// Enable local analysis to infer side effect free modules. Defaults to true.
1174    turbopack_infer_module_side_effects: Option<bool>,
1175    /// Devtool option for the segment explorer.
1176    devtool_segment_explorer: Option<bool>,
1177    /// Whether to report inlined system environment variables as warnings or errors.
1178    report_system_env_inlining: Option<String>,
1179    // Use project.is_persistent_caching() instead
1180    // turbopack_file_system_cache_for_dev: Option<bool>,
1181    // turbopack_file_system_cache_for_build: Option<bool>,
1182}
1183
1184#[derive(
1185    Clone,
1186    Debug,
1187    PartialEq,
1188    Eq,
1189    Deserialize,
1190    TraceRawVcs,
1191    NonLocalValue,
1192    OperationValue,
1193    Encode,
1194    Decode,
1195)]
1196#[serde(rename_all = "camelCase")]
1197pub struct SubResourceIntegrity {
1198    pub algorithm: Option<RcStr>,
1199}
1200
1201#[derive(
1202    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1203)]
1204#[serde(untagged)]
1205pub enum ServerActionsOrLegacyBool {
1206    /// The current way to configure server actions sub behaviors.
1207    ServerActionsConfig(ServerActions),
1208
1209    /// The legacy way to disable server actions. This is no longer used, server
1210    /// actions is always enabled.
1211    LegacyBool(bool),
1212}
1213
1214#[derive(
1215    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1216)]
1217#[serde(rename_all = "kebab-case")]
1218pub enum EsmExternalsValue {
1219    Loose,
1220}
1221
1222#[derive(
1223    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1224)]
1225#[serde(untagged)]
1226pub enum EsmExternals {
1227    Loose(EsmExternalsValue),
1228    Bool(bool),
1229}
1230
1231// Test for esm externals deserialization.
1232#[test]
1233fn test_esm_externals_deserialization() {
1234    let json = serde_json::json!({
1235        "esmExternals": true
1236    });
1237    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1238    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1239
1240    let json = serde_json::json!({
1241        "esmExternals": "loose"
1242    });
1243    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1244    assert_eq!(
1245        config.esm_externals,
1246        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1247    );
1248}
1249
1250#[derive(
1251    Clone,
1252    Debug,
1253    Default,
1254    PartialEq,
1255    Eq,
1256    Deserialize,
1257    TraceRawVcs,
1258    NonLocalValue,
1259    OperationValue,
1260    Encode,
1261    Decode,
1262)]
1263#[serde(rename_all = "camelCase")]
1264pub struct ServerActions {
1265    /// Allows adjusting body parser size limit for server actions.
1266    pub body_size_limit: Option<SizeLimit>,
1267}
1268
1269#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1270#[serde(untagged)]
1271pub enum SizeLimit {
1272    Number(f64),
1273    WithUnit(String),
1274}
1275
1276// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
1277// implement Eq.
1278impl PartialEq for SizeLimit {
1279    fn eq(&self, other: &Self) -> bool {
1280        match (self, other) {
1281            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1282            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1283            _ => false,
1284        }
1285    }
1286}
1287
1288impl Eq for SizeLimit {}
1289
1290#[derive(
1291    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1292)]
1293#[serde(rename_all = "kebab-case")]
1294pub enum MiddlewarePrefetchType {
1295    Strict,
1296    Flexible,
1297}
1298
1299#[derive(
1300    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1301)]
1302#[serde(untagged)]
1303pub enum EmotionTransformOptionsOrBoolean {
1304    Boolean(bool),
1305    Options(EmotionTransformConfig),
1306}
1307
1308impl EmotionTransformOptionsOrBoolean {
1309    pub fn is_enabled(&self) -> bool {
1310        match self {
1311            Self::Boolean(enabled) => *enabled,
1312            _ => true,
1313        }
1314    }
1315}
1316
1317#[derive(
1318    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1319)]
1320#[serde(untagged)]
1321pub enum StyledComponentsTransformOptionsOrBoolean {
1322    Boolean(bool),
1323    Options(StyledComponentsTransformConfig),
1324}
1325
1326impl StyledComponentsTransformOptionsOrBoolean {
1327    pub fn is_enabled(&self) -> bool {
1328        match self {
1329            Self::Boolean(enabled) => *enabled,
1330            _ => true,
1331        }
1332    }
1333}
1334
1335#[turbo_tasks::value(eq = "manual")]
1336#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1337#[serde(rename_all = "camelCase")]
1338pub struct CompilerConfig {
1339    pub react_remove_properties: Option<ReactRemoveProperties>,
1340    pub relay: Option<RelayConfig>,
1341    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1342    pub remove_console: Option<RemoveConsoleConfig>,
1343    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1344}
1345
1346#[derive(
1347    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1348)]
1349#[serde(untagged, rename_all = "camelCase")]
1350pub enum ReactRemoveProperties {
1351    Boolean(bool),
1352    Config { properties: Option<Vec<String>> },
1353}
1354
1355impl ReactRemoveProperties {
1356    pub fn is_enabled(&self) -> bool {
1357        match self {
1358            Self::Boolean(enabled) => *enabled,
1359            _ => true,
1360        }
1361    }
1362}
1363
1364#[derive(
1365    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1366)]
1367#[serde(untagged)]
1368pub enum RemoveConsoleConfig {
1369    Boolean(bool),
1370    Config { exclude: Option<Vec<String>> },
1371}
1372
1373impl RemoveConsoleConfig {
1374    pub fn is_enabled(&self) -> bool {
1375        match self {
1376            Self::Boolean(enabled) => *enabled,
1377            _ => true,
1378        }
1379    }
1380}
1381
1382#[turbo_tasks::value(transparent)]
1383pub struct ResolveExtensions(Option<Vec<RcStr>>);
1384
1385#[turbo_tasks::value(transparent)]
1386pub struct SwcPlugins(
1387    #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1388);
1389
1390#[turbo_tasks::value(transparent)]
1391pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1392
1393#[turbo_tasks::value(transparent)]
1394
1395pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1396
1397#[turbo_tasks::value(transparent)]
1398pub struct OptionFileSystemPath(Option<FileSystemPath>);
1399
1400#[turbo_tasks::value(transparent)]
1401pub struct OptionServerActions(Option<ServerActions>);
1402
1403#[turbo_tasks::value(transparent)]
1404pub struct IgnoreIssues(Vec<IgnoreIssue>);
1405
1406#[turbo_tasks::value(transparent)]
1407pub struct OptionJsonValue(
1408    #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1409);
1410
1411fn turbopack_config_documentation_link() -> RcStr {
1412    rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1413}
1414
1415#[turbo_tasks::value(shared)]
1416struct InvalidLoaderRuleRenameAsIssue {
1417    glob: RcStr,
1418    rename_as: RcStr,
1419    config_file_path: FileSystemPath,
1420}
1421
1422#[turbo_tasks::value_impl]
1423impl Issue for InvalidLoaderRuleRenameAsIssue {
1424    #[turbo_tasks::function]
1425    async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1426        Ok(self.config_file_path.clone().cell())
1427    }
1428
1429    #[turbo_tasks::function]
1430    fn stage(&self) -> Vc<IssueStage> {
1431        IssueStage::Config.cell()
1432    }
1433
1434    #[turbo_tasks::function]
1435    async fn title(&self) -> Result<Vc<StyledString>> {
1436        Ok(
1437            StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1438                .cell(),
1439        )
1440    }
1441
1442    #[turbo_tasks::function]
1443    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1444        Ok(Vc::cell(Some(
1445            StyledString::Text(RcStr::from(format!(
1446                "The extension {} contains a wildcard, but the `as` option does not: {}",
1447                self.glob, self.rename_as,
1448            )))
1449            .resolved_cell(),
1450        )))
1451    }
1452
1453    #[turbo_tasks::function]
1454    fn documentation_link(&self) -> Vc<RcStr> {
1455        Vc::cell(turbopack_config_documentation_link())
1456    }
1457}
1458
1459#[turbo_tasks::value(shared)]
1460struct InvalidLoaderRuleConditionIssue {
1461    error_string: RcStr,
1462    condition: ConfigConditionItem,
1463    config_file_path: FileSystemPath,
1464}
1465
1466#[turbo_tasks::value_impl]
1467impl Issue for InvalidLoaderRuleConditionIssue {
1468    #[turbo_tasks::function]
1469    async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1470        Ok(self.await?.config_file_path.clone().cell())
1471    }
1472
1473    #[turbo_tasks::function]
1474    fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1475        IssueStage::Config.cell()
1476    }
1477
1478    #[turbo_tasks::function]
1479    async fn title(&self) -> Result<Vc<StyledString>> {
1480        Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1481    }
1482
1483    #[turbo_tasks::function]
1484    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1485        Ok(Vc::cell(Some(
1486            StyledString::Stack(vec![
1487                StyledString::Line(vec![
1488                    StyledString::Text(rcstr!("Encountered the following error: ")),
1489                    StyledString::Code(self.error_string.clone()),
1490                ]),
1491                StyledString::Text(rcstr!("While processing the condition:")),
1492                StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1493            ])
1494            .resolved_cell(),
1495        )))
1496    }
1497
1498    #[turbo_tasks::function]
1499    fn documentation_link(&self) -> Vc<RcStr> {
1500        Vc::cell(turbopack_config_documentation_link())
1501    }
1502}
1503
1504#[turbo_tasks::value_impl]
1505impl NextConfig {
1506    #[turbo_tasks::function]
1507    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1508        let string = string.await?;
1509        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1510        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1511            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1512        Ok(config.cell())
1513    }
1514
1515    #[turbo_tasks::function]
1516    pub async fn config_file_path(
1517        &self,
1518        project_path: FileSystemPath,
1519    ) -> Result<Vc<FileSystemPath>> {
1520        Ok(project_path.join(&self.config_file_name)?.cell())
1521    }
1522
1523    #[turbo_tasks::function]
1524    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1525        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1526    }
1527
1528    #[turbo_tasks::function]
1529    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1530        Vc::cell(self.react_production_profiling.unwrap_or_default())
1531    }
1532
1533    #[turbo_tasks::function]
1534    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1535        Vc::cell(
1536            self.server_external_packages
1537                .as_ref()
1538                .cloned()
1539                .unwrap_or_default(),
1540        )
1541    }
1542
1543    #[turbo_tasks::function]
1544    pub fn is_standalone(&self) -> Vc<bool> {
1545        Vc::cell(self.output == Some(OutputType::Standalone))
1546    }
1547
1548    #[turbo_tasks::function]
1549    pub fn base_path(&self) -> Vc<Option<RcStr>> {
1550        Vc::cell(self.base_path.clone())
1551    }
1552
1553    #[turbo_tasks::function]
1554    pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1555        if let Some(handler) = &self.cache_handler {
1556            Ok(Vc::cell(Some(project_path.join(handler)?)))
1557        } else {
1558            Ok(Vc::cell(None))
1559        }
1560    }
1561
1562    #[turbo_tasks::function]
1563    pub fn compiler(&self) -> Vc<CompilerConfig> {
1564        self.compiler.clone().unwrap_or_default().cell()
1565    }
1566
1567    #[turbo_tasks::function]
1568    pub fn env(&self) -> Vc<EnvMap> {
1569        // The value expected for env is Record<String, String>, but config itself
1570        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1571        // then stringifies it. We do the interop here as well.
1572        let env = self
1573            .env
1574            .iter()
1575            .map(|(k, v)| {
1576                (
1577                    k.as_str().into(),
1578                    if let JsonValue::String(s) = v {
1579                        // A string value is kept, calling `to_string` would wrap in to quotes.
1580                        s.as_str().into()
1581                    } else {
1582                        v.to_string().into()
1583                    },
1584                )
1585            })
1586            .collect();
1587
1588        Vc::cell(env)
1589    }
1590
1591    #[turbo_tasks::function]
1592    pub fn image_config(&self) -> Vc<ImageConfig> {
1593        self.images.clone().cell()
1594    }
1595
1596    #[turbo_tasks::function]
1597    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1598        // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
1599        // which just builds a regex alternative, which greedily matches the longest
1600        // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
1601        let mut extensions = self.page_extensions.clone();
1602        extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1603        Vc::cell(extensions)
1604    }
1605
1606    #[turbo_tasks::function]
1607    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1608        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1609    }
1610
1611    #[turbo_tasks::function]
1612    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1613        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1614    }
1615
1616    #[turbo_tasks::function]
1617    pub async fn webpack_rules(
1618        self: Vc<Self>,
1619        project_path: FileSystemPath,
1620    ) -> Result<Vc<WebpackRules>> {
1621        let this = self.await?;
1622        let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1623            return Ok(Vc::cell(Vec::new()));
1624        };
1625        if turbo_rules.is_empty() {
1626            return Ok(Vc::cell(Vec::new()));
1627        }
1628        let mut rules = Vec::new();
1629        for (glob, rule_collection) in turbo_rules.iter() {
1630            fn transform_loaders(
1631                loaders: &mut dyn Iterator<Item = &LoaderItem>,
1632            ) -> ResolvedVc<WebpackLoaderItems> {
1633                ResolvedVc::cell(
1634                    loaders
1635                        .map(|item| match item {
1636                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1637                                loader: name.clone(),
1638                                options: Default::default(),
1639                            },
1640                            LoaderItem::LoaderOptions(options) => options.clone(),
1641                        })
1642                        .collect(),
1643                )
1644            }
1645            for item in &rule_collection.0 {
1646                match item {
1647                    RuleConfigCollectionItem::Shorthand(loaders) => {
1648                        rules.push((
1649                            glob.clone(),
1650                            LoaderRuleItem {
1651                                loaders: transform_loaders(&mut [loaders].into_iter()),
1652                                rename_as: None,
1653                                condition: None,
1654                                module_type: None,
1655                            },
1656                        ));
1657                    }
1658                    RuleConfigCollectionItem::Full(RuleConfigItem {
1659                        loaders,
1660                        rename_as,
1661                        condition,
1662                        module_type,
1663                    }) => {
1664                        // If the extension contains a wildcard, and the rename_as does not,
1665                        // emit an issue to prevent users from encountering duplicate module
1666                        // names.
1667                        if glob.contains("*")
1668                            && let Some(rename_as) = rename_as.as_ref()
1669                            && !rename_as.contains("*")
1670                        {
1671                            InvalidLoaderRuleRenameAsIssue {
1672                                glob: glob.clone(),
1673                                config_file_path: self
1674                                    .config_file_path(project_path.clone())
1675                                    .owned()
1676                                    .await?,
1677                                rename_as: rename_as.clone(),
1678                            }
1679                            .resolved_cell()
1680                            .emit();
1681                        }
1682
1683                        // convert from Next.js-specific condition type to internal Turbopack
1684                        // condition type
1685                        let condition = if let Some(condition) = condition {
1686                            match ConditionItem::try_from(condition.clone()) {
1687                                Ok(cond) => Some(cond),
1688                                Err(err) => {
1689                                    InvalidLoaderRuleConditionIssue {
1690                                        error_string: RcStr::from(err.to_string()),
1691                                        condition: condition.clone(),
1692                                        config_file_path: self
1693                                            .config_file_path(project_path.clone())
1694                                            .owned()
1695                                            .await?,
1696                                    }
1697                                    .resolved_cell()
1698                                    .emit();
1699                                    None
1700                                }
1701                            }
1702                        } else {
1703                            None
1704                        };
1705                        rules.push((
1706                            glob.clone(),
1707                            LoaderRuleItem {
1708                                loaders: transform_loaders(&mut loaders.iter()),
1709                                rename_as: rename_as.clone(),
1710                                condition,
1711                                module_type: module_type.clone(),
1712                            },
1713                        ));
1714                    }
1715                }
1716            }
1717        }
1718        Ok(Vc::cell(rules))
1719    }
1720
1721    #[turbo_tasks::function]
1722    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1723        let Some(resolve_alias) = self
1724            .turbopack
1725            .as_ref()
1726            .and_then(|t| t.resolve_alias.as_ref())
1727        else {
1728            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1729        };
1730        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1731        Ok(alias_map.cell())
1732    }
1733
1734    #[turbo_tasks::function]
1735    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1736        let Some(resolve_extensions) = self
1737            .turbopack
1738            .as_ref()
1739            .and_then(|t| t.resolve_extensions.as_ref())
1740        else {
1741            return Vc::cell(None);
1742        };
1743        Vc::cell(Some(resolve_extensions.clone()))
1744    }
1745
1746    #[turbo_tasks::function]
1747    pub fn import_externals(&self) -> Result<Vc<bool>> {
1748        Ok(Vc::cell(match self.experimental.esm_externals {
1749            Some(EsmExternals::Bool(b)) => b,
1750            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1751            None => true,
1752        }))
1753    }
1754
1755    #[turbo_tasks::function]
1756    pub fn inline_css(&self) -> Vc<bool> {
1757        Vc::cell(self.experimental.inline_css.unwrap_or(false))
1758    }
1759
1760    #[turbo_tasks::function]
1761    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1762        let options = &self.experimental.mdx_rs;
1763
1764        let options = match options {
1765            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1766                MdxTransformOptions {
1767                    provider_import_source: Some(mdx_import_source_file()),
1768                    ..Default::default()
1769                }
1770                .resolved_cell(),
1771            )),
1772            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1773                MdxTransformOptions {
1774                    provider_import_source: Some(
1775                        options
1776                            .provider_import_source
1777                            .clone()
1778                            .unwrap_or(mdx_import_source_file()),
1779                    ),
1780                    ..options.clone()
1781                }
1782                .resolved_cell(),
1783            )),
1784            _ => OptionalMdxTransformOptions(None),
1785        };
1786
1787        options.cell()
1788    }
1789
1790    #[turbo_tasks::function]
1791    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1792        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1793    }
1794
1795    #[turbo_tasks::function]
1796    pub fn dist_dir(&self) -> Vc<RcStr> {
1797        Vc::cell(self.dist_dir.clone())
1798    }
1799    #[turbo_tasks::function]
1800    pub fn dist_dir_root(&self) -> Vc<RcStr> {
1801        Vc::cell(self.dist_dir_root.clone())
1802    }
1803
1804    #[turbo_tasks::function]
1805    pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1806        if let Some(handlers) = &self.cache_handlers {
1807            Ok(Vc::cell(
1808                handlers
1809                    .values()
1810                    .map(|h| project_path.join(h))
1811                    .collect::<Result<Vec<_>>>()?,
1812            ))
1813        } else {
1814            Ok(Vc::cell(vec![]))
1815        }
1816    }
1817
1818    #[turbo_tasks::function]
1819    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1820        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1821    }
1822
1823    // TODO not implemented yet
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 async fn asset_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1894        let this = self.await?;
1895
1896        match &this.deployment_id {
1897            Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1898            None => Ok(Vc::cell(None)),
1899        }
1900    }
1901
1902    #[turbo_tasks::function]
1903    pub fn enable_taint(&self) -> Vc<bool> {
1904        Vc::cell(self.experimental.taint.unwrap_or(false))
1905    }
1906
1907    #[turbo_tasks::function]
1908    pub fn enable_transition_indicator(&self) -> Vc<bool> {
1909        Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1910    }
1911
1912    #[turbo_tasks::function]
1913    pub fn enable_gesture_transition(&self) -> Vc<bool> {
1914        Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
1915    }
1916
1917    #[turbo_tasks::function]
1918    pub fn enable_app_new_scroll_handler(&self) -> Vc<bool> {
1919        Vc::cell(self.experimental.app_new_scroll_handler.unwrap_or(false))
1920    }
1921
1922    #[turbo_tasks::function]
1923    pub fn enable_cache_components(&self) -> Vc<bool> {
1924        Vc::cell(self.cache_components.unwrap_or(false))
1925    }
1926
1927    #[turbo_tasks::function]
1928    pub fn enable_use_cache(&self) -> Vc<bool> {
1929        Vc::cell(
1930            self.experimental
1931                .use_cache
1932                // "use cache" was originally implicitly enabled with the
1933                // cacheComponents flag, so we transfer the value for cacheComponents to the
1934                // explicit useCache flag to ensure backwards compatibility.
1935                .unwrap_or(self.cache_components.unwrap_or(false)),
1936        )
1937    }
1938
1939    #[turbo_tasks::function]
1940    pub fn enable_root_params(&self) -> Vc<bool> {
1941        Vc::cell(
1942            self.experimental
1943                .root_params
1944                // rootParams should be enabled implicitly in cacheComponents.
1945                .unwrap_or(self.cache_components.unwrap_or(false)),
1946        )
1947    }
1948
1949    #[turbo_tasks::function]
1950    pub fn runtime_server_deployment_id_available(&self) -> Vc<bool> {
1951        Vc::cell(
1952            self.experimental
1953                .runtime_server_deployment_id
1954                .unwrap_or(false),
1955        )
1956    }
1957
1958    #[turbo_tasks::function]
1959    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1960        let mut cache_kinds = CacheKinds::default();
1961
1962        if let Some(handlers) = self.cache_handlers.as_ref() {
1963            cache_kinds.extend(handlers.keys().cloned());
1964        }
1965
1966        cache_kinds.cell()
1967    }
1968
1969    #[turbo_tasks::function]
1970    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1971        Vc::cell(
1972            self.experimental
1973                .optimize_package_imports
1974                .clone()
1975                .unwrap_or_default(),
1976        )
1977    }
1978
1979    #[turbo_tasks::function]
1980    pub fn tree_shaking_mode_for_foreign_code(
1981        &self,
1982        _is_development: bool,
1983    ) -> Vc<OptionTreeShaking> {
1984        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1985            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1986            Some(true) => Some(TreeShakingMode::ModuleFragments),
1987            None => Some(TreeShakingMode::ReexportsOnly),
1988        })
1989        .cell()
1990    }
1991
1992    #[turbo_tasks::function]
1993    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1994        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1995            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1996            Some(true) => Some(TreeShakingMode::ModuleFragments),
1997            None => Some(TreeShakingMode::ReexportsOnly),
1998        })
1999        .cell()
2000    }
2001
2002    #[turbo_tasks::function]
2003    pub async fn turbopack_remove_unused_imports(
2004        self: Vc<Self>,
2005        mode: Vc<NextMode>,
2006    ) -> Result<Vc<bool>> {
2007        let remove_unused_imports = self
2008            .await?
2009            .experimental
2010            .turbopack_remove_unused_imports
2011            .unwrap_or(matches!(*mode.await?, NextMode::Build));
2012
2013        if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2014            bail!(
2015                "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2016                 enabling `experimental.turbopackRemoveUnusedExports`"
2017            );
2018        }
2019
2020        Ok(Vc::cell(remove_unused_imports))
2021    }
2022
2023    #[turbo_tasks::function]
2024    pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2025        Ok(Vc::cell(
2026            self.experimental
2027                .turbopack_remove_unused_exports
2028                .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2029        ))
2030    }
2031
2032    #[turbo_tasks::function]
2033    pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2034        Vc::cell(
2035            self.experimental
2036                .turbopack_infer_module_side_effects
2037                .unwrap_or(true),
2038        )
2039    }
2040
2041    #[turbo_tasks::function]
2042    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2043        Ok(match *mode.await? {
2044            // Ignore configuration in development mode, HMR only works with `named`
2045            NextMode::Development => ModuleIds::Named.cell(),
2046            NextMode::Build => self
2047                .experimental
2048                .turbopack_module_ids
2049                .unwrap_or(ModuleIds::Deterministic)
2050                .cell(),
2051        })
2052    }
2053
2054    #[turbo_tasks::function]
2055    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2056        let minify = self.experimental.turbopack_minify;
2057        Ok(Vc::cell(
2058            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2059        ))
2060    }
2061
2062    #[turbo_tasks::function]
2063    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2064        Ok(Vc::cell(match *mode.await? {
2065            // Ignore configuration in development mode to not break HMR
2066            NextMode::Development => false,
2067            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2068        }))
2069    }
2070
2071    #[turbo_tasks::function]
2072    pub async fn turbo_nested_async_chunking(
2073        &self,
2074        mode: Vc<NextMode>,
2075        client_side: bool,
2076    ) -> Result<Vc<bool>> {
2077        let option = if client_side {
2078            self.experimental
2079                .turbopack_client_side_nested_async_chunking
2080        } else {
2081            self.experimental
2082                .turbopack_server_side_nested_async_chunking
2083        };
2084        Ok(Vc::cell(if let Some(value) = option {
2085            value
2086        } else {
2087            match *mode.await? {
2088                NextMode::Development => false,
2089                NextMode::Build => client_side,
2090            }
2091        }))
2092    }
2093
2094    #[turbo_tasks::function]
2095    pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2096        Vc::cell(
2097            self.experimental
2098                .turbopack_import_type_bytes
2099                .unwrap_or(false),
2100        )
2101    }
2102
2103    #[turbo_tasks::function]
2104    pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2105        Vc::cell(
2106            self.experimental
2107                .turbopack_import_type_text
2108                .unwrap_or(false),
2109        )
2110    }
2111
2112    #[turbo_tasks::function]
2113    pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2114        let input_source_maps = self
2115            .experimental
2116            .turbopack_input_source_maps
2117            .unwrap_or(true);
2118        let source_maps = self
2119            .experimental
2120            .turbopack_source_maps
2121            .unwrap_or(match &*mode.await? {
2122                NextMode::Development => true,
2123                NextMode::Build => self.production_browser_source_maps,
2124            });
2125        Ok(match (source_maps, input_source_maps) {
2126            (true, true) => SourceMapsType::Full,
2127            (true, false) => SourceMapsType::Partial,
2128            (false, _) => SourceMapsType::None,
2129        }
2130        .cell())
2131    }
2132
2133    #[turbo_tasks::function]
2134    pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2135        let input_source_maps = self
2136            .experimental
2137            .turbopack_input_source_maps
2138            .unwrap_or(true);
2139        let source_maps = self
2140            .experimental
2141            .turbopack_source_maps
2142            .or(self.experimental.server_source_maps)
2143            .unwrap_or(true);
2144        Ok(match (source_maps, input_source_maps) {
2145            (true, true) => SourceMapsType::Full,
2146            (true, false) => SourceMapsType::Partial,
2147            (false, _) => SourceMapsType::None,
2148        }
2149        .cell())
2150    }
2151
2152    #[turbo_tasks::function]
2153    pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2154        Vc::cell(
2155            self.turbopack
2156                .as_ref()
2157                .and_then(|turbopack| turbopack.debug_ids)
2158                .unwrap_or(false),
2159        )
2160    }
2161
2162    #[turbo_tasks::function]
2163    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2164        Ok(Vc::cell(
2165            self.typescript
2166                .tsconfig_path
2167                .as_ref()
2168                .map(|path| path.to_owned().into()),
2169        ))
2170    }
2171
2172    #[turbo_tasks::function]
2173    pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2174        Vc::cell(self.cross_origin.clone())
2175    }
2176
2177    #[turbo_tasks::function]
2178    pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2179        Vc::cell(self.i18n.clone())
2180    }
2181
2182    #[turbo_tasks::function]
2183    pub fn output(&self) -> Vc<OptionOutputType> {
2184        Vc::cell(self.output.clone())
2185    }
2186
2187    #[turbo_tasks::function]
2188    pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2189        Vc::cell(self.output_file_tracing_includes.clone())
2190    }
2191
2192    #[turbo_tasks::function]
2193    pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2194        Vc::cell(self.output_file_tracing_excludes.clone())
2195    }
2196
2197    #[turbo_tasks::function]
2198    pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2199        FetchClientConfig::default().cell()
2200    }
2201
2202    #[turbo_tasks::function]
2203    pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2204        match self.experimental.report_system_env_inlining.as_deref() {
2205            None => Ok(IssueSeverity::Suggestion.cell()),
2206            Some("warn") => Ok(IssueSeverity::Warning.cell()),
2207            Some("error") => Ok(IssueSeverity::Error.cell()),
2208            _ => bail!(
2209                "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2210            ),
2211        }
2212    }
2213
2214    /// Returns the list of ignore-issue rules from the turbopack config,
2215    /// converted to the `IgnoreIssue` type used by `IssueFilter`.
2216    #[turbo_tasks::function]
2217    pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2218        let rules = self
2219            .turbopack
2220            .as_ref()
2221            .and_then(|tp| tp.ignore_issue.as_deref())
2222            .unwrap_or_default()
2223            .iter()
2224            .map(|rule| {
2225                Ok(IgnoreIssue {
2226                    path: rule.path.to_ignore_pattern()?,
2227                    title: rule
2228                        .title
2229                        .as_ref()
2230                        .map(|t| t.to_ignore_pattern())
2231                        .transpose()?,
2232                    description: rule
2233                        .description
2234                        .as_ref()
2235                        .map(|d| d.to_ignore_pattern())
2236                        .transpose()?,
2237                })
2238            })
2239            .collect::<Result<Vec<_>>>()?;
2240        Ok(Vc::cell(rules))
2241    }
2242}
2243
2244/// A subset of ts/jsconfig that next.js implicitly
2245/// interops with.
2246#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2247#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2248#[serde(rename_all = "camelCase")]
2249pub struct JsConfig {
2250    #[bincode(with = "turbo_bincode::serde_self_describing")]
2251    compiler_options: Option<serde_json::Value>,
2252}
2253
2254#[turbo_tasks::value_impl]
2255impl JsConfig {
2256    #[turbo_tasks::function]
2257    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2258        let string = string.await?;
2259        let config: JsConfig = serde_json::from_str(&string)
2260            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2261
2262        Ok(config.cell())
2263    }
2264
2265    #[turbo_tasks::function]
2266    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2267        Vc::cell(self.compiler_options.clone().unwrap_or_default())
2268    }
2269}
2270
2271#[cfg(test)]
2272mod tests {
2273    use super::*;
2274
2275    #[test]
2276    fn test_serde_rule_config_item_options() {
2277        let json_value = serde_json::json!({
2278            "loaders": [],
2279            "as": "*.js",
2280            "condition": {
2281                "all": [
2282                    "production",
2283                    {"not": "foreign"},
2284                    {"any": [
2285                        "browser",
2286                        {
2287                            "path": { "type": "glob", "value": "*.svg"},
2288                            "query": {
2289                                "type": "regex",
2290                                "value": {
2291                                    "source": "@someQuery",
2292                                    "flags": ""
2293                                }
2294                            },
2295                            "content": {
2296                                "source": "@someTag",
2297                                "flags": ""
2298                            }
2299                        }
2300                    ]},
2301                ],
2302            }
2303        });
2304
2305        let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2306
2307        assert_eq!(
2308            rule_config,
2309            RuleConfigItem {
2310                loaders: vec![],
2311                rename_as: Some(rcstr!("*.js")),
2312                module_type: None,
2313                condition: Some(ConfigConditionItem::All(
2314                    [
2315                        ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2316                        ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2317                            WebpackLoaderBuiltinCondition::Foreign
2318                        ))),
2319                        ConfigConditionItem::Any(
2320                            vec![
2321                                ConfigConditionItem::Builtin(
2322                                    WebpackLoaderBuiltinCondition::Browser
2323                                ),
2324                                ConfigConditionItem::Base {
2325                                    path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2326                                    content: Some(RegexComponents {
2327                                        source: rcstr!("@someTag"),
2328                                        flags: rcstr!(""),
2329                                    }),
2330                                    query: Some(ConfigConditionQuery::Regex(RegexComponents {
2331                                        source: rcstr!("@someQuery"),
2332                                        flags: rcstr!(""),
2333                                    })),
2334                                    content_type: None,
2335                                },
2336                            ]
2337                            .into(),
2338                        ),
2339                    ]
2340                    .into(),
2341                )),
2342            }
2343        );
2344    }
2345}