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