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