1use anyhow::{Context, Result, bail};
2use rustc_hash::FxHashSet;
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value as JsonValue;
5use turbo_rcstr::RcStr;
6use turbo_tasks::{
7 FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
8 trace::TraceRawVcs,
9};
10use turbo_tasks_env::EnvMap;
11use turbo_tasks_fs::FileSystemPath;
12use turbopack::module_options::{
13 LoaderRuleItem, OptionWebpackRules,
14 module_options_context::{
15 ConditionItem, ConditionPath, MdxTransformOptions, OptionWebpackConditions,
16 },
17};
18use turbopack_core::{
19 issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString},
20 resolve::ResolveAliasMap,
21};
22use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
23use turbopack_ecmascript_plugins::transform::{
24 emotion::EmotionTransformConfig, relay::RelayConfig,
25 styled_components::StyledComponentsTransformConfig,
26};
27use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
28
29use crate::{
30 mode::NextMode, next_import_map::mdx_import_source_file,
31 next_shared::transforms::ModularizeImportPackageConfig,
32};
33
34#[turbo_tasks::value]
35struct NextConfigAndCustomRoutes {
36 config: ResolvedVc<NextConfig>,
37 custom_routes: ResolvedVc<CustomRoutes>,
38}
39
40#[turbo_tasks::value]
41struct CustomRoutes {
42 rewrites: ResolvedVc<Rewrites>,
43}
44
45#[turbo_tasks::value(transparent)]
46pub struct ModularizeImports(FxIndexMap<String, ModularizeImportPackageConfig>);
47
48#[turbo_tasks::value(transparent)]
49#[derive(Clone, Debug)]
50pub struct CacheKinds(FxHashSet<RcStr>);
51
52impl CacheKinds {
53 pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
54 self.0.extend(iter);
55 }
56}
57
58impl Default for CacheKinds {
59 fn default() -> Self {
60 CacheKinds(["default", "remote"].iter().map(|&s| s.into()).collect())
61 }
62}
63
64#[turbo_tasks::value(serialization = "custom", eq = "manual")]
65#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, OperationValue)]
66#[serde(rename_all = "camelCase")]
67pub struct NextConfig {
68 pub config_file: Option<RcStr>,
71 pub config_file_name: RcStr,
72
73 pub cache_max_memory_size: Option<f64>,
77 pub cache_handler: Option<RcStr>,
79
80 pub env: FxIndexMap<String, JsonValue>,
81 pub experimental: ExperimentalConfig,
82 pub images: ImageConfig,
83 pub page_extensions: Vec<RcStr>,
84 pub react_production_profiling: Option<bool>,
85 pub react_strict_mode: Option<bool>,
86 pub transpile_packages: Option<Vec<RcStr>>,
87 pub modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
88 pub dist_dir: Option<RcStr>,
89 pub deployment_id: Option<RcStr>,
90 sass_options: Option<serde_json::Value>,
91 pub trailing_slash: Option<bool>,
92 pub asset_prefix: Option<RcStr>,
93 pub base_path: Option<RcStr>,
94 pub skip_middleware_url_normalize: Option<bool>,
95 pub skip_trailing_slash_redirect: Option<bool>,
96 pub i18n: Option<I18NConfig>,
97 pub cross_origin: Option<CrossOriginConfig>,
98 pub dev_indicators: Option<DevIndicatorsConfig>,
99 pub output: Option<OutputType>,
100 pub turbopack: Option<TurbopackConfig>,
101 production_browser_source_maps: bool,
102
103 pub bundle_pages_router_dependencies: Option<bool>,
108
109 pub server_external_packages: Option<Vec<RcStr>>,
114
115 #[serde(rename = "_originalRedirects")]
116 pub original_redirects: Option<Vec<Redirect>>,
117
118 pub compiler: Option<CompilerConfig>,
120
121 pub optimize_fonts: Option<bool>,
122
123 amp: AmpConfig,
125 clean_dist_dir: bool,
126 compress: bool,
127 eslint: EslintConfig,
128 exclude_default_moment_locales: bool,
129 export_path_map: Option<serde_json::Value>,
131 generate_build_id: Option<serde_json::Value>,
133 generate_etags: bool,
134 http_agent_options: HttpAgentConfig,
135 on_demand_entries: OnDemandEntriesConfig,
136 powered_by_header: bool,
137 public_runtime_config: FxIndexMap<String, serde_json::Value>,
138 server_runtime_config: FxIndexMap<String, serde_json::Value>,
139 static_page_generation_timeout: f64,
140 target: Option<String>,
141 typescript: TypeScriptConfig,
142 use_file_system_public_routes: bool,
143 webpack: Option<serde_json::Value>,
144}
145
146#[derive(
147 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
148)]
149#[serde(rename_all = "kebab-case")]
150pub enum CrossOriginConfig {
151 Anonymous,
152 UseCredentials,
153}
154
155#[derive(
156 Clone,
157 Debug,
158 Default,
159 PartialEq,
160 Serialize,
161 Deserialize,
162 TraceRawVcs,
163 NonLocalValue,
164 OperationValue,
165)]
166#[serde(rename_all = "camelCase")]
167struct AmpConfig {
168 canonical_base: Option<String>,
169}
170
171#[derive(
172 Clone,
173 Debug,
174 Default,
175 PartialEq,
176 Serialize,
177 Deserialize,
178 TraceRawVcs,
179 NonLocalValue,
180 OperationValue,
181)]
182#[serde(rename_all = "camelCase")]
183struct EslintConfig {
184 dirs: Option<Vec<String>>,
185 ignore_during_builds: Option<bool>,
186}
187
188#[derive(
189 Clone,
190 Debug,
191 Default,
192 PartialEq,
193 Serialize,
194 Deserialize,
195 TraceRawVcs,
196 NonLocalValue,
197 OperationValue,
198)]
199#[serde(rename_all = "kebab-case")]
200pub enum BuildActivityPositions {
201 #[default]
202 BottomRight,
203 BottomLeft,
204 TopRight,
205 TopLeft,
206}
207
208#[derive(
209 Clone,
210 Debug,
211 Default,
212 PartialEq,
213 Serialize,
214 Deserialize,
215 TraceRawVcs,
216 NonLocalValue,
217 OperationValue,
218)]
219#[serde(rename_all = "camelCase")]
220pub struct DevIndicatorsOptions {
221 pub build_activity_position: Option<BuildActivityPositions>,
222 pub position: Option<BuildActivityPositions>,
223}
224
225#[derive(
226 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
227)]
228#[serde(untagged)]
229pub enum DevIndicatorsConfig {
230 WithOptions(DevIndicatorsOptions),
231 Boolean(bool),
232}
233
234#[derive(
235 Clone,
236 Debug,
237 Default,
238 PartialEq,
239 Serialize,
240 Deserialize,
241 TraceRawVcs,
242 NonLocalValue,
243 OperationValue,
244)]
245#[serde(rename_all = "camelCase")]
246struct OnDemandEntriesConfig {
247 max_inactive_age: f64,
248 pages_buffer_length: f64,
249}
250
251#[derive(
252 Clone,
253 Debug,
254 Default,
255 PartialEq,
256 Serialize,
257 Deserialize,
258 TraceRawVcs,
259 NonLocalValue,
260 OperationValue,
261)]
262#[serde(rename_all = "camelCase")]
263struct HttpAgentConfig {
264 keep_alive: bool,
265}
266
267#[derive(
268 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
269)]
270#[serde(rename_all = "camelCase")]
271pub struct DomainLocale {
272 pub default_locale: String,
273 pub domain: String,
274 pub http: Option<bool>,
275 pub locales: Option<Vec<String>>,
276}
277
278#[derive(
279 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
280)]
281#[serde(rename_all = "camelCase")]
282pub struct I18NConfig {
283 pub default_locale: String,
284 pub domains: Option<Vec<DomainLocale>>,
285 pub locale_detection: Option<bool>,
286 pub locales: Vec<String>,
287}
288
289#[derive(
290 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
291)]
292#[serde(rename_all = "kebab-case")]
293pub enum OutputType {
294 Standalone,
295 Export,
296}
297
298#[derive(
299 Debug,
300 Clone,
301 Hash,
302 Eq,
303 PartialEq,
304 Ord,
305 PartialOrd,
306 TaskInput,
307 TraceRawVcs,
308 Serialize,
309 Deserialize,
310 NonLocalValue,
311 OperationValue,
312)]
313#[serde(tag = "type", rename_all = "kebab-case")]
314pub enum RouteHas {
315 Header {
316 key: RcStr,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 value: Option<RcStr>,
319 },
320 Cookie {
321 key: RcStr,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 value: Option<RcStr>,
324 },
325 Query {
326 key: RcStr,
327 #[serde(skip_serializing_if = "Option::is_none")]
328 value: Option<RcStr>,
329 },
330 Host {
331 value: RcStr,
332 },
333}
334
335#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
336#[serde(rename_all = "camelCase")]
337pub struct HeaderValue {
338 pub key: RcStr,
339 pub value: RcStr,
340}
341
342#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
343#[serde(rename_all = "camelCase")]
344pub struct Header {
345 pub source: String,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub base_path: Option<bool>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub locale: Option<bool>,
350 pub headers: Vec<HeaderValue>,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub has: Option<Vec<RouteHas>>,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 pub missing: Option<Vec<RouteHas>>,
355}
356
357#[derive(
358 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
359)]
360#[serde(rename_all = "camelCase")]
361pub enum RedirectStatus {
362 StatusCode(f64),
363 Permanent(bool),
364}
365
366#[derive(
367 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
368)]
369#[serde(rename_all = "camelCase")]
370pub struct Redirect {
371 pub source: String,
372 pub destination: String,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub base_path: Option<bool>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub locale: Option<bool>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub has: Option<Vec<RouteHas>>,
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub missing: Option<Vec<RouteHas>>,
381
382 #[serde(flatten)]
383 pub status: RedirectStatus,
384}
385
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
387#[serde(rename_all = "camelCase")]
388pub struct Rewrite {
389 pub source: String,
390 pub destination: String,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub base_path: Option<bool>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub locale: Option<bool>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub has: Option<Vec<RouteHas>>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub missing: Option<Vec<RouteHas>>,
399}
400
401#[turbo_tasks::value(eq = "manual")]
402#[derive(Clone, Debug, Default, PartialEq)]
403#[serde(rename_all = "camelCase")]
404pub struct Rewrites {
405 pub before_files: Vec<Rewrite>,
406 pub after_files: Vec<Rewrite>,
407 pub fallback: Vec<Rewrite>,
408}
409
410#[derive(
411 Clone,
412 Debug,
413 Default,
414 PartialEq,
415 Serialize,
416 Deserialize,
417 TraceRawVcs,
418 NonLocalValue,
419 OperationValue,
420)]
421#[serde(rename_all = "camelCase")]
422pub struct TypeScriptConfig {
423 pub ignore_build_errors: Option<bool>,
424 pub tsconfig_path: Option<String>,
425}
426
427#[turbo_tasks::value(eq = "manual", operation)]
428#[derive(Clone, Debug, PartialEq)]
429#[serde(rename_all = "camelCase")]
430pub struct ImageConfig {
431 pub device_sizes: Vec<u16>,
432 pub image_sizes: Vec<u16>,
433 pub path: String,
434 pub loader: ImageLoader,
435 #[serde(deserialize_with = "empty_string_is_none")]
436 pub loader_file: Option<String>,
437 pub domains: Vec<String>,
438 pub disable_static_images: bool,
439 #[serde(rename = "minimumCacheTTL")]
440 pub minimum_cache_ttl: u64,
441 pub formats: Vec<ImageFormat>,
442 #[serde(rename = "dangerouslyAllowSVG")]
443 pub dangerously_allow_svg: bool,
444 pub content_security_policy: String,
445 pub remote_patterns: Vec<RemotePattern>,
446 pub unoptimized: bool,
447}
448
449fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
450where
451 D: Deserializer<'de>,
452{
453 let o = Option::<String>::deserialize(deserializer)?;
454 Ok(o.filter(|s| !s.is_empty()))
455}
456
457impl Default for ImageConfig {
458 fn default() -> Self {
459 Self {
461 device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
462 image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384],
463 path: "/_next/image".to_string(),
464 loader: ImageLoader::Default,
465 loader_file: None,
466 domains: vec![],
467 disable_static_images: false,
468 minimum_cache_ttl: 60,
469 formats: vec![ImageFormat::Webp],
470 dangerously_allow_svg: false,
471 content_security_policy: "".to_string(),
472 remote_patterns: vec![],
473 unoptimized: false,
474 }
475 }
476}
477
478#[derive(
479 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
480)]
481#[serde(rename_all = "kebab-case")]
482pub enum ImageLoader {
483 Default,
484 Imgix,
485 Cloudinary,
486 Akamai,
487 Custom,
488}
489
490#[derive(
491 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
492)]
493pub enum ImageFormat {
494 #[serde(rename = "image/webp")]
495 Webp,
496 #[serde(rename = "image/avif")]
497 Avif,
498}
499
500#[derive(
501 Clone,
502 Debug,
503 Default,
504 PartialEq,
505 Serialize,
506 Deserialize,
507 TraceRawVcs,
508 NonLocalValue,
509 OperationValue,
510)]
511#[serde(rename_all = "camelCase")]
512pub struct RemotePattern {
513 pub hostname: String,
514 #[serde(skip_serializing_if = "Option::is_none")]
515 pub protocol: Option<RemotePatternProtocal>,
516 #[serde(skip_serializing_if = "Option::is_none")]
517 pub port: Option<String>,
518 #[serde(skip_serializing_if = "Option::is_none")]
519 pub pathname: Option<String>,
520}
521
522#[derive(
523 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
524)]
525#[serde(rename_all = "kebab-case")]
526pub enum RemotePatternProtocal {
527 Http,
528 Https,
529}
530
531#[derive(
532 Clone,
533 Debug,
534 Default,
535 PartialEq,
536 Serialize,
537 Deserialize,
538 TraceRawVcs,
539 NonLocalValue,
540 OperationValue,
541)]
542#[serde(rename_all = "camelCase")]
543pub struct TurbopackConfig {
544 pub loaders: Option<JsonValue>,
546 pub rules: Option<FxIndexMap<RcStr, RuleConfigItemOrShortcut>>,
547 #[turbo_tasks(trace_ignore)]
548 pub conditions: Option<FxIndexMap<RcStr, ConfigConditionItem>>,
549 pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
550 pub resolve_extensions: Option<Vec<RcStr>>,
551 pub module_ids: Option<ModuleIds>,
552}
553
554#[derive(Clone, Debug, PartialEq, Serialize, TraceRawVcs, NonLocalValue)]
555pub struct ConfigConditionItem(ConditionItem);
556
557impl<'de> Deserialize<'de> for ConfigConditionItem {
558 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
559 where
560 D: Deserializer<'de>,
561 {
562 #[derive(Deserialize)]
563 struct RegexComponents {
564 source: RcStr,
565 flags: RcStr,
566 }
567
568 #[derive(Deserialize)]
569 struct ConfigPath {
570 path: RegexOrGlob,
571 }
572
573 #[derive(Deserialize)]
574 #[serde(tag = "type", rename_all = "lowercase")]
575 enum RegexOrGlob {
576 Regexp { value: RegexComponents },
577 Glob { value: String },
578 }
579
580 let config_path = ConfigPath::deserialize(deserializer)?;
581 let condition_item = match config_path.path {
582 RegexOrGlob::Regexp { value } => {
583 let regex = turbo_esregex::EsRegex::new(&value.source, &value.flags)
584 .map_err(serde::de::Error::custom)?;
585 ConditionItem {
586 path: ConditionPath::Regex(regex.resolved_cell()),
587 }
588 }
589 RegexOrGlob::Glob { value } => ConditionItem {
590 path: ConditionPath::Glob(value.into()),
591 },
592 };
593
594 Ok(ConfigConditionItem(condition_item))
595 }
596}
597
598#[derive(
599 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
600)]
601#[serde(rename_all = "camelCase")]
602pub struct RuleConfigItemOptions {
603 pub loaders: Vec<LoaderItem>,
604 #[serde(default, alias = "as")]
605 pub rename_as: Option<RcStr>,
606}
607
608#[derive(
609 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
610)]
611#[serde(rename_all = "camelCase", untagged)]
612pub enum RuleConfigItemOrShortcut {
613 Loaders(Vec<LoaderItem>),
614 Advanced(RuleConfigItem),
615}
616
617#[derive(
618 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
619)]
620#[serde(rename_all = "camelCase", untagged)]
621pub enum RuleConfigItem {
622 Options(RuleConfigItemOptions),
623 Conditional(FxIndexMap<RcStr, RuleConfigItem>),
624 Boolean(bool),
625}
626
627#[derive(
628 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
629)]
630#[serde(untagged)]
631pub enum LoaderItem {
632 LoaderName(RcStr),
633 LoaderOptions(WebpackLoaderItem),
634}
635
636#[turbo_tasks::value(operation)]
637#[derive(Copy, Clone, Debug)]
638#[serde(rename_all = "camelCase")]
639pub enum ModuleIds {
640 Named,
641 Deterministic,
642}
643
644#[turbo_tasks::value(transparent)]
645pub struct OptionModuleIds(pub Option<ModuleIds>);
646
647#[derive(
648 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
649)]
650#[serde(untagged)]
651pub enum MdxRsOptions {
652 Boolean(bool),
653 Option(MdxTransformOptions),
654}
655
656#[turbo_tasks::value(shared, operation)]
657#[derive(Clone, Debug)]
658#[serde(rename_all = "camelCase")]
659pub enum ReactCompilerMode {
660 Infer,
661 Annotation,
662 All,
663}
664
665#[turbo_tasks::value(shared, operation)]
667#[derive(Clone, Debug)]
668#[serde(rename_all = "camelCase")]
669pub struct ReactCompilerOptions {
670 #[serde(skip_serializing_if = "Option::is_none")]
671 pub compilation_mode: Option<ReactCompilerMode>,
672 #[serde(skip_serializing_if = "Option::is_none")]
673 pub panic_threshold: Option<RcStr>,
674}
675
676#[derive(
677 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
678)]
679#[serde(untagged)]
680pub enum ReactCompilerOptionsOrBoolean {
681 Boolean(bool),
682 Option(ReactCompilerOptions),
683}
684
685#[turbo_tasks::value(transparent)]
686pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
687
688#[derive(
689 Clone,
690 Debug,
691 Default,
692 PartialEq,
693 Serialize,
694 Deserialize,
695 TraceRawVcs,
696 ValueDebugFormat,
697 NonLocalValue,
698 OperationValue,
699)]
700#[serde(rename_all = "camelCase")]
701pub struct ExperimentalConfig {
702 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
705 client_router_filter: Option<bool>,
706 client_router_filter_allowed_rate: Option<f64>,
709 client_router_filter_redirects: Option<bool>,
710 fetch_cache_key_prefix: Option<RcStr>,
711 isr_flush_to_disk: Option<bool>,
712 mdx_rs: Option<MdxRsOptions>,
715 strict_next_head: Option<bool>,
716 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
717 external_middleware_rewrites_resolve: Option<bool>,
718 scroll_restoration: Option<bool>,
719 manual_client_base_path: Option<bool>,
720 optimistic_client_cache: Option<bool>,
721 middleware_prefetch: Option<MiddlewarePrefetchType>,
722 optimize_css: Option<serde_json::Value>,
725 next_script_workers: Option<bool>,
726 web_vitals_attribution: Option<Vec<RcStr>>,
727 server_actions: Option<ServerActionsOrLegacyBool>,
728 sri: Option<SubResourceIntegrity>,
729 react_compiler: Option<ReactCompilerOptionsOrBoolean>,
730 #[serde(rename = "dynamicIO")]
731 dynamic_io: Option<bool>,
732 use_cache: Option<bool>,
733 adjust_font_fallbacks: Option<bool>,
737 adjust_font_fallbacks_with_size_adjust: Option<bool>,
738 after: Option<bool>,
739 amp: Option<serde_json::Value>,
740 app_document_preloading: Option<bool>,
741 cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
742 cache_life: Option<FxIndexMap<String, CacheLifeProfile>>,
743 case_sensitive_routes: Option<bool>,
744 cpus: Option<f64>,
745 cra_compat: Option<bool>,
746 disable_optimized_loading: Option<bool>,
747 disable_postcss_preset_env: Option<bool>,
748 esm_externals: Option<EsmExternals>,
749 extension_alias: Option<serde_json::Value>,
750 external_dir: Option<bool>,
751 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
756 fully_specified: Option<bool>,
757 gzip_size: Option<bool>,
758
759 pub inline_css: Option<bool>,
760 instrumentation_hook: Option<bool>,
761 client_trace_metadata: Option<Vec<String>>,
762 large_page_data_bytes: Option<f64>,
763 logging: Option<serde_json::Value>,
764 memory_based_workers_count: Option<bool>,
765 optimize_server_react: Option<bool>,
767 optimize_package_imports: Option<Vec<RcStr>>,
770 output_file_tracing_ignores: Option<Vec<RcStr>>,
771 output_file_tracing_includes: Option<serde_json::Value>,
772 output_file_tracing_root: Option<RcStr>,
773 ppr: Option<ExperimentalPartialPrerendering>,
776 taint: Option<bool>,
777 #[serde(rename = "routerBFCache")]
778 router_bfcache: Option<bool>,
779 proxy_timeout: Option<f64>,
780 server_minification: Option<bool>,
782 server_source_maps: Option<bool>,
784 swc_trace_profiling: Option<bool>,
785 trust_host_header: Option<bool>,
787 typed_routes: Option<bool>,
791 url_imports: Option<serde_json::Value>,
792 view_transition: Option<bool>,
793 webpack_build_worker: Option<bool>,
796 worker_threads: Option<bool>,
797
798 turbopack_minify: Option<bool>,
799 turbopack_persistent_caching: Option<bool>,
800 turbopack_source_maps: Option<bool>,
801 turbopack_tree_shaking: Option<bool>,
802 global_not_found: Option<bool>,
804}
805
806#[derive(
807 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
808)]
809#[serde(rename_all = "camelCase")]
810pub struct CacheLifeProfile {
811 #[serde(skip_serializing_if = "Option::is_none")]
812 pub stale: Option<u32>,
813 #[serde(skip_serializing_if = "Option::is_none")]
814 pub revalidate: Option<u32>,
815 #[serde(skip_serializing_if = "Option::is_none")]
816 pub expire: Option<u32>,
817}
818
819#[test]
820fn test_cache_life_profiles() {
821 let json = serde_json::json!({
822 "cacheLife": {
823 "frequent": {
824 "stale": 19,
825 "revalidate": 100,
826 },
827 }
828 });
829
830 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
831 let mut expected_cache_life = FxIndexMap::default();
832
833 expected_cache_life.insert(
834 "frequent".to_string(),
835 CacheLifeProfile {
836 stale: Some(19),
837 revalidate: Some(100),
838 expire: None,
839 },
840 );
841
842 assert_eq!(config.cache_life, Some(expected_cache_life));
843}
844
845#[test]
846fn test_cache_life_profiles_invalid() {
847 let json = serde_json::json!({
848 "cacheLife": {
849 "invalid": {
850 "stale": "invalid_value",
851 },
852 }
853 });
854
855 let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
856
857 assert!(
858 result.is_err(),
859 "Deserialization should fail due to invalid 'stale' value type"
860 );
861}
862
863#[derive(
864 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
865)]
866#[serde(rename_all = "lowercase")]
867pub enum ExperimentalPartialPrerenderingIncrementalValue {
868 Incremental,
869}
870
871#[derive(
872 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
873)]
874#[serde(untagged)]
875pub enum ExperimentalPartialPrerendering {
876 Boolean(bool),
877 Incremental(ExperimentalPartialPrerenderingIncrementalValue),
878}
879
880#[test]
881fn test_parse_experimental_partial_prerendering() {
882 let json = serde_json::json!({
883 "ppr": "incremental"
884 });
885 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
886 assert_eq!(
887 config.ppr,
888 Some(ExperimentalPartialPrerendering::Incremental(
889 ExperimentalPartialPrerenderingIncrementalValue::Incremental
890 ))
891 );
892
893 let json = serde_json::json!({
894 "ppr": true
895 });
896 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
897 assert_eq!(
898 config.ppr,
899 Some(ExperimentalPartialPrerendering::Boolean(true))
900 );
901
902 let json = serde_json::json!({
904 "ppr": "random"
905 });
906 let config = serde_json::from_value::<ExperimentalConfig>(json);
907 assert!(config.is_err());
908}
909
910#[derive(
911 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
912)]
913#[serde(rename_all = "camelCase")]
914pub struct SubResourceIntegrity {
915 pub algorithm: Option<RcStr>,
916}
917
918#[derive(
919 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
920)]
921#[serde(untagged)]
922pub enum ServerActionsOrLegacyBool {
923 ServerActionsConfig(ServerActions),
925
926 LegacyBool(bool),
929}
930
931#[derive(
932 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
933)]
934#[serde(rename_all = "kebab-case")]
935pub enum EsmExternalsValue {
936 Loose,
937}
938
939#[derive(
940 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
941)]
942#[serde(untagged)]
943pub enum EsmExternals {
944 Loose(EsmExternalsValue),
945 Bool(bool),
946}
947
948#[test]
950fn test_esm_externals_deserialization() {
951 let json = serde_json::json!({
952 "esmExternals": true
953 });
954 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
955 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
956
957 let json = serde_json::json!({
958 "esmExternals": "loose"
959 });
960 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
961 assert_eq!(
962 config.esm_externals,
963 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
964 );
965}
966
967#[derive(
968 Clone,
969 Debug,
970 Default,
971 PartialEq,
972 Eq,
973 Deserialize,
974 Serialize,
975 TraceRawVcs,
976 NonLocalValue,
977 OperationValue,
978)]
979#[serde(rename_all = "camelCase")]
980pub struct ServerActions {
981 pub body_size_limit: Option<SizeLimit>,
983}
984
985#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
986#[serde(untagged)]
987pub enum SizeLimit {
988 Number(f64),
989 WithUnit(String),
990}
991
992impl PartialEq for SizeLimit {
995 fn eq(&self, other: &Self) -> bool {
996 match (self, other) {
997 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
998 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
999 _ => false,
1000 }
1001 }
1002}
1003
1004impl Eq for SizeLimit {}
1005
1006#[derive(
1007 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1008)]
1009#[serde(rename_all = "kebab-case")]
1010pub enum MiddlewarePrefetchType {
1011 Strict,
1012 Flexible,
1013}
1014
1015#[derive(
1016 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1017)]
1018#[serde(untagged)]
1019pub enum EmotionTransformOptionsOrBoolean {
1020 Boolean(bool),
1021 Options(EmotionTransformConfig),
1022}
1023
1024impl EmotionTransformOptionsOrBoolean {
1025 pub fn is_enabled(&self) -> bool {
1026 match self {
1027 Self::Boolean(enabled) => *enabled,
1028 _ => true,
1029 }
1030 }
1031}
1032
1033#[derive(
1034 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1035)]
1036#[serde(untagged)]
1037pub enum StyledComponentsTransformOptionsOrBoolean {
1038 Boolean(bool),
1039 Options(StyledComponentsTransformConfig),
1040}
1041
1042impl StyledComponentsTransformOptionsOrBoolean {
1043 pub fn is_enabled(&self) -> bool {
1044 match self {
1045 Self::Boolean(enabled) => *enabled,
1046 _ => true,
1047 }
1048 }
1049}
1050
1051#[turbo_tasks::value(eq = "manual")]
1052#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1053#[serde(rename_all = "camelCase")]
1054pub struct CompilerConfig {
1055 pub react_remove_properties: Option<ReactRemoveProperties>,
1056 pub relay: Option<RelayConfig>,
1057 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1058 pub remove_console: Option<RemoveConsoleConfig>,
1059 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1060}
1061
1062#[derive(
1063 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1064)]
1065#[serde(untagged, rename_all = "camelCase")]
1066pub enum ReactRemoveProperties {
1067 Boolean(bool),
1068 Config { properties: Option<Vec<String>> },
1069}
1070
1071impl ReactRemoveProperties {
1072 pub fn is_enabled(&self) -> bool {
1073 match self {
1074 Self::Boolean(enabled) => *enabled,
1075 _ => true,
1076 }
1077 }
1078}
1079
1080#[derive(
1081 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1082)]
1083#[serde(untagged)]
1084pub enum RemoveConsoleConfig {
1085 Boolean(bool),
1086 Config { exclude: Option<Vec<String>> },
1087}
1088
1089impl RemoveConsoleConfig {
1090 pub fn is_enabled(&self) -> bool {
1091 match self {
1092 Self::Boolean(enabled) => *enabled,
1093 _ => true,
1094 }
1095 }
1096}
1097
1098#[turbo_tasks::value(transparent)]
1099pub struct ResolveExtensions(Option<Vec<RcStr>>);
1100
1101#[turbo_tasks::value(transparent)]
1102pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1103
1104#[turbo_tasks::value(transparent)]
1105pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1106
1107#[turbo_tasks::value(transparent)]
1108
1109pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1110
1111#[turbo_tasks::value(transparent)]
1112pub struct OptionServerActions(Option<ServerActions>);
1113
1114#[turbo_tasks::value_impl]
1115impl NextConfig {
1116 #[turbo_tasks::function]
1117 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1118 let string = string.await?;
1119 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1120 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1121 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1122 Ok(config.cell())
1123 }
1124
1125 #[turbo_tasks::function]
1126 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1127 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1128 }
1129
1130 #[turbo_tasks::function]
1131 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1132 Vc::cell(self.react_production_profiling.unwrap_or_default())
1133 }
1134
1135 #[turbo_tasks::function]
1136 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1137 Vc::cell(
1138 self.server_external_packages
1139 .as_ref()
1140 .cloned()
1141 .unwrap_or_default(),
1142 )
1143 }
1144
1145 #[turbo_tasks::function]
1146 pub fn is_standalone(&self) -> Vc<bool> {
1147 Vc::cell(self.output == Some(OutputType::Standalone))
1148 }
1149
1150 #[turbo_tasks::function]
1151 pub fn cache_handler(&self) -> Vc<Option<RcStr>> {
1152 Vc::cell(self.cache_handler.clone())
1153 }
1154
1155 #[turbo_tasks::function]
1156 pub fn compiler(&self) -> Vc<CompilerConfig> {
1157 self.compiler.clone().unwrap_or_default().cell()
1158 }
1159
1160 #[turbo_tasks::function]
1161 pub fn env(&self) -> Vc<EnvMap> {
1162 let env = self
1166 .env
1167 .iter()
1168 .map(|(k, v)| {
1169 (
1170 k.as_str().into(),
1171 if let JsonValue::String(s) = v {
1172 s.as_str().into()
1174 } else {
1175 v.to_string().into()
1176 },
1177 )
1178 })
1179 .collect();
1180
1181 Vc::cell(env)
1182 }
1183
1184 #[turbo_tasks::function]
1185 pub fn image_config(&self) -> Vc<ImageConfig> {
1186 self.images.clone().cell()
1187 }
1188
1189 #[turbo_tasks::function]
1190 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1191 Vc::cell(self.page_extensions.clone())
1192 }
1193
1194 #[turbo_tasks::function]
1195 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1196 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1197 }
1198
1199 #[turbo_tasks::function]
1200 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1201 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1202 }
1203
1204 #[turbo_tasks::function]
1205 pub fn webpack_rules(&self, active_conditions: Vec<RcStr>) -> Vc<OptionWebpackRules> {
1206 let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1207 return Vc::cell(None);
1208 };
1209 if turbo_rules.is_empty() {
1210 return Vc::cell(None);
1211 }
1212 let active_conditions = active_conditions.into_iter().collect::<FxHashSet<_>>();
1213 let mut rules = FxIndexMap::default();
1214 for (ext, rule) in turbo_rules.iter() {
1215 fn transform_loaders(loaders: &[LoaderItem]) -> ResolvedVc<WebpackLoaderItems> {
1216 ResolvedVc::cell(
1217 loaders
1218 .iter()
1219 .map(|item| match item {
1220 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1221 loader: name.clone(),
1222 options: Default::default(),
1223 },
1224 LoaderItem::LoaderOptions(options) => options.clone(),
1225 })
1226 .collect(),
1227 )
1228 }
1229 enum FindRuleResult<'a> {
1230 Found(&'a RuleConfigItemOptions),
1231 NotFound,
1232 Break,
1233 }
1234 fn find_rule<'a>(
1235 rule: &'a RuleConfigItem,
1236 active_conditions: &FxHashSet<RcStr>,
1237 ) -> FindRuleResult<'a> {
1238 match rule {
1239 RuleConfigItem::Options(rule) => FindRuleResult::Found(rule),
1240 RuleConfigItem::Conditional(map) => {
1241 for (condition, rule) in map.iter() {
1242 if condition == "default" || active_conditions.contains(condition) {
1243 match find_rule(rule, active_conditions) {
1244 FindRuleResult::Found(rule) => {
1245 return FindRuleResult::Found(rule);
1246 }
1247 FindRuleResult::Break => {
1248 return FindRuleResult::Break;
1249 }
1250 FindRuleResult::NotFound => {}
1251 }
1252 }
1253 }
1254 FindRuleResult::NotFound
1255 }
1256 RuleConfigItem::Boolean(_) => FindRuleResult::Break,
1257 }
1258 }
1259 match rule {
1260 RuleConfigItemOrShortcut::Loaders(loaders) => {
1261 rules.insert(
1262 ext.clone(),
1263 LoaderRuleItem {
1264 loaders: transform_loaders(loaders),
1265 rename_as: None,
1266 },
1267 );
1268 }
1269 RuleConfigItemOrShortcut::Advanced(rule) => {
1270 if let FindRuleResult::Found(RuleConfigItemOptions { loaders, rename_as }) =
1271 find_rule(rule, &active_conditions)
1272 {
1273 rules.insert(
1274 ext.clone(),
1275 LoaderRuleItem {
1276 loaders: transform_loaders(loaders),
1277 rename_as: rename_as.clone(),
1278 },
1279 );
1280 }
1281 }
1282 }
1283 }
1284 Vc::cell(Some(ResolvedVc::cell(rules)))
1285 }
1286
1287 #[turbo_tasks::function]
1288 pub fn webpack_conditions(&self) -> Vc<OptionWebpackConditions> {
1289 let Some(config_conditions) = self.turbopack.as_ref().and_then(|t| t.conditions.as_ref())
1290 else {
1291 return Vc::cell(None);
1292 };
1293
1294 let conditions = FxIndexMap::from_iter(
1295 config_conditions
1296 .iter()
1297 .map(|(k, v)| (k.clone(), v.0.clone())),
1298 );
1299
1300 Vc::cell(Some(ResolvedVc::cell(conditions)))
1301 }
1302
1303 #[turbo_tasks::function]
1304 pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1305 Ok(Vc::cell(
1306 self.experimental
1307 .turbopack_persistent_caching
1308 .unwrap_or_default(),
1309 ))
1310 }
1311
1312 #[turbo_tasks::function]
1313 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1314 let Some(resolve_alias) = self
1315 .turbopack
1316 .as_ref()
1317 .and_then(|t| t.resolve_alias.as_ref())
1318 else {
1319 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1320 };
1321 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1322 Ok(alias_map.cell())
1323 }
1324
1325 #[turbo_tasks::function]
1326 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1327 let Some(resolve_extensions) = self
1328 .turbopack
1329 .as_ref()
1330 .and_then(|t| t.resolve_extensions.as_ref())
1331 else {
1332 return Vc::cell(None);
1333 };
1334 Vc::cell(Some(resolve_extensions.clone()))
1335 }
1336
1337 #[turbo_tasks::function]
1338 pub async fn import_externals(&self) -> Result<Vc<bool>> {
1339 Ok(Vc::cell(match self.experimental.esm_externals {
1340 Some(EsmExternals::Bool(b)) => b,
1341 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1342 None => true,
1343 }))
1344 }
1345
1346 #[turbo_tasks::function]
1347 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1348 let options = &self.experimental.mdx_rs;
1349
1350 let options = match options {
1351 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1352 MdxTransformOptions {
1353 provider_import_source: Some(mdx_import_source_file()),
1354 ..Default::default()
1355 }
1356 .resolved_cell(),
1357 )),
1358 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1359 MdxTransformOptions {
1360 provider_import_source: Some(
1361 options
1362 .provider_import_source
1363 .clone()
1364 .unwrap_or(mdx_import_source_file()),
1365 ),
1366 ..options.clone()
1367 }
1368 .resolved_cell(),
1369 )),
1370 _ => OptionalMdxTransformOptions(None),
1371 };
1372
1373 options.cell()
1374 }
1375
1376 #[turbo_tasks::function]
1377 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1378 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1379 }
1380
1381 #[turbo_tasks::function]
1382 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1383 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1384 }
1385
1386 #[turbo_tasks::function]
1387 pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1388 Vc::cell(self.experimental.sri.clone())
1389 }
1390
1391 #[turbo_tasks::function]
1392 pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1393 Vc::cell(match self.experimental.server_actions.as_ref() {
1394 Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1395 Some(server_actions.clone())
1396 }
1397 Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1398 _ => None,
1399 })
1400 }
1401
1402 #[turbo_tasks::function]
1403 pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
1404 let options = &self.experimental.react_compiler;
1405
1406 let options = match options {
1407 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1408 OptionalReactCompilerOptions(Some(
1409 ReactCompilerOptions {
1410 compilation_mode: None,
1411 panic_threshold: None,
1412 }
1413 .resolved_cell(),
1414 ))
1415 }
1416 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1417 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1418 ),
1419 _ => OptionalReactCompilerOptions(None),
1420 };
1421
1422 options.cell()
1423 }
1424
1425 #[turbo_tasks::function]
1426 pub fn sass_config(&self) -> Vc<JsonValue> {
1427 Vc::cell(self.sass_options.clone().unwrap_or_default())
1428 }
1429
1430 #[turbo_tasks::function]
1431 pub fn skip_middleware_url_normalize(&self) -> Vc<bool> {
1432 Vc::cell(self.skip_middleware_url_normalize.unwrap_or(false))
1433 }
1434
1435 #[turbo_tasks::function]
1436 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1437 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1438 }
1439
1440 #[turbo_tasks::function]
1443 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1444 let this = self.await?;
1445
1446 Ok(Vc::cell(Some(
1447 format!(
1448 "{}/_next/",
1449 if let Some(asset_prefix) = &this.asset_prefix {
1450 asset_prefix
1451 } else {
1452 this.base_path.as_ref().map_or("", |b| b.as_str())
1453 }
1454 .trim_end_matches('/')
1455 )
1456 .into(),
1457 )))
1458 }
1459
1460 #[turbo_tasks::function]
1462 pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1463 let this = self.await?;
1464
1465 match &this.deployment_id {
1466 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1467 None => Ok(Vc::cell(None)),
1468 }
1469 }
1470
1471 #[turbo_tasks::function]
1472 pub fn enable_ppr(&self) -> Vc<bool> {
1473 Vc::cell(
1474 self.experimental
1475 .ppr
1476 .as_ref()
1477 .map(|ppr| match ppr {
1478 ExperimentalPartialPrerendering::Incremental(
1479 ExperimentalPartialPrerenderingIncrementalValue::Incremental,
1480 ) => true,
1481 ExperimentalPartialPrerendering::Boolean(b) => *b,
1482 })
1483 .unwrap_or(false),
1484 )
1485 }
1486
1487 #[turbo_tasks::function]
1488 pub fn enable_taint(&self) -> Vc<bool> {
1489 Vc::cell(self.experimental.taint.unwrap_or(false))
1490 }
1491
1492 #[turbo_tasks::function]
1493 pub fn enable_router_bfcache(&self) -> Vc<bool> {
1494 Vc::cell(self.experimental.router_bfcache.unwrap_or(false))
1495 }
1496
1497 #[turbo_tasks::function]
1498 pub fn enable_view_transition(&self) -> Vc<bool> {
1499 Vc::cell(self.experimental.view_transition.unwrap_or(false))
1500 }
1501
1502 #[turbo_tasks::function]
1503 pub fn enable_dynamic_io(&self) -> Vc<bool> {
1504 Vc::cell(self.experimental.dynamic_io.unwrap_or(false))
1505 }
1506
1507 #[turbo_tasks::function]
1508 pub fn enable_use_cache(&self) -> Vc<bool> {
1509 Vc::cell(
1510 self.experimental
1511 .use_cache
1512 .unwrap_or(self.experimental.dynamic_io.unwrap_or(false)),
1516 )
1517 }
1518
1519 #[turbo_tasks::function]
1520 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1521 let mut cache_kinds = CacheKinds::default();
1522
1523 if let Some(handlers) = self.experimental.cache_handlers.as_ref() {
1524 cache_kinds.extend(handlers.keys().cloned());
1525 }
1526
1527 cache_kinds.cell()
1528 }
1529
1530 #[turbo_tasks::function]
1531 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1532 Vc::cell(
1533 self.experimental
1534 .optimize_package_imports
1535 .clone()
1536 .unwrap_or_default(),
1537 )
1538 }
1539
1540 #[turbo_tasks::function]
1541 pub fn tree_shaking_mode_for_foreign_code(
1542 &self,
1543 _is_development: bool,
1544 ) -> Vc<OptionTreeShaking> {
1545 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1546 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1547 Some(true) => Some(TreeShakingMode::ModuleFragments),
1548 None => Some(TreeShakingMode::ReexportsOnly),
1549 })
1550 .cell()
1551 }
1552
1553 #[turbo_tasks::function]
1554 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1555 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1556 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1557 Some(true) => Some(TreeShakingMode::ModuleFragments),
1558 None => Some(TreeShakingMode::ReexportsOnly),
1559 })
1560 .cell()
1561 }
1562
1563 #[turbo_tasks::function]
1564 pub fn module_ids(&self) -> Vc<OptionModuleIds> {
1565 let Some(module_ids) = self.turbopack.as_ref().and_then(|t| t.module_ids) else {
1566 return Vc::cell(None);
1567 };
1568 Vc::cell(Some(module_ids))
1569 }
1570
1571 #[turbo_tasks::function]
1572 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1573 let minify = self.experimental.turbopack_minify;
1574 Ok(Vc::cell(
1575 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1576 ))
1577 }
1578
1579 #[turbo_tasks::function]
1580 pub async fn client_source_maps(&self, _mode: Vc<NextMode>) -> Result<Vc<bool>> {
1581 let source_maps = self.experimental.turbopack_source_maps;
1585 Ok(Vc::cell(source_maps.unwrap_or(true)))
1586 }
1587
1588 #[turbo_tasks::function]
1589 pub async fn server_source_maps(&self) -> Result<Vc<bool>> {
1590 let source_maps = self.experimental.turbopack_source_maps;
1591 Ok(Vc::cell(source_maps.unwrap_or(true)))
1592 }
1593
1594 #[turbo_tasks::function]
1595 pub async fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1596 Ok(Vc::cell(
1597 self.typescript
1598 .tsconfig_path
1599 .as_ref()
1600 .map(|path| path.to_owned().into()),
1601 ))
1602 }
1603}
1604
1605#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1608#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1609#[serde(rename_all = "camelCase")]
1610pub struct JsConfig {
1611 compiler_options: Option<serde_json::Value>,
1612}
1613
1614#[turbo_tasks::value_impl]
1615impl JsConfig {
1616 #[turbo_tasks::function]
1617 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1618 let string = string.await?;
1619 let config: JsConfig = serde_json::from_str(&string)
1620 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1621
1622 Ok(config.cell())
1623 }
1624
1625 #[turbo_tasks::function]
1626 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1627 Vc::cell(self.compiler_options.clone().unwrap_or_default())
1628 }
1629}
1630
1631#[turbo_tasks::value]
1632struct OutdatedConfigIssue {
1633 path: ResolvedVc<FileSystemPath>,
1634 old_name: RcStr,
1635 new_name: RcStr,
1636 description: RcStr,
1637}
1638
1639#[turbo_tasks::value_impl]
1640impl Issue for OutdatedConfigIssue {
1641 #[turbo_tasks::function]
1642 fn severity(&self) -> Vc<IssueSeverity> {
1643 IssueSeverity::Error.into()
1644 }
1645
1646 #[turbo_tasks::function]
1647 fn stage(&self) -> Vc<IssueStage> {
1648 IssueStage::Config.into()
1649 }
1650
1651 #[turbo_tasks::function]
1652 fn file_path(&self) -> Vc<FileSystemPath> {
1653 *self.path
1654 }
1655
1656 #[turbo_tasks::function]
1657 fn title(&self) -> Vc<StyledString> {
1658 StyledString::Line(vec![
1659 StyledString::Code(self.old_name.clone()),
1660 StyledString::Text(" has been replaced by ".into()),
1661 StyledString::Code(self.new_name.clone()),
1662 ])
1663 .cell()
1664 }
1665
1666 #[turbo_tasks::function]
1667 fn description(&self) -> Vc<OptionStyledString> {
1668 Vc::cell(Some(
1669 StyledString::Text(self.description.clone()).resolved_cell(),
1670 ))
1671 }
1672}