1use anyhow::{Context, Result, bail};
2use either::Either;
3use rustc_hash::FxHashSet;
4use serde::{Deserialize, Deserializer, Serialize};
5use serde_json::Value as JsonValue;
6use turbo_esregex::EsRegex;
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{
9 FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
10 trace::TraceRawVcs,
11};
12use turbo_tasks_env::{EnvMap, ProcessEnv};
13use turbo_tasks_fetch::FetchClient;
14use turbo_tasks_fs::FileSystemPath;
15use turbopack::module_options::{
16 ConditionItem, ConditionPath, LoaderRuleItem, WebpackRules,
17 module_options_context::{MdxTransformOptions, OptionWebpackConditions},
18};
19use turbopack_core::{
20 issue::{Issue, IssueExt, IssueStage, OptionStyledString, StyledString},
21 resolve::ResolveAliasMap,
22};
23use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
24use turbopack_ecmascript_plugins::transform::{
25 emotion::EmotionTransformConfig, relay::RelayConfig,
26 styled_components::StyledComponentsTransformConfig,
27};
28use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
29
30use crate::{
31 app_structure::FileSystemPathVec,
32 mode::NextMode,
33 next_import_map::mdx_import_source_file,
34 next_shared::{
35 transforms::ModularizeImportPackageConfig, webpack_rules::WebpackLoaderBuiltinCondition,
36 },
37};
38
39#[turbo_tasks::value]
40struct NextConfigAndCustomRoutes {
41 config: ResolvedVc<NextConfig>,
42 custom_routes: ResolvedVc<CustomRoutes>,
43}
44
45#[turbo_tasks::value]
46struct CustomRoutes {
47 rewrites: ResolvedVc<Rewrites>,
48}
49
50#[turbo_tasks::value(transparent)]
51pub struct ModularizeImports(FxIndexMap<String, ModularizeImportPackageConfig>);
52
53#[turbo_tasks::value(transparent)]
54#[derive(Clone, Debug)]
55pub struct CacheKinds(FxHashSet<RcStr>);
56
57impl CacheKinds {
58 pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
59 self.0.extend(iter);
60 }
61}
62
63impl Default for CacheKinds {
64 fn default() -> Self {
65 CacheKinds(
66 ["default", "remote", "private"]
67 .iter()
68 .map(|&s| s.into())
69 .collect(),
70 )
71 }
72}
73
74#[turbo_tasks::value(eq = "manual")]
75#[derive(Clone, Debug, Default, PartialEq)]
76#[serde(default, rename_all = "camelCase")]
77pub struct NextConfig {
78 pub config_file: Option<RcStr>,
81 pub config_file_name: RcStr,
82
83 pub cache_max_memory_size: Option<f64>,
87 pub cache_handler: Option<RcStr>,
89
90 pub env: FxIndexMap<String, JsonValue>,
91 pub experimental: ExperimentalConfig,
92 pub images: ImageConfig,
93 pub page_extensions: Vec<RcStr>,
94 pub react_production_profiling: Option<bool>,
95 pub react_strict_mode: Option<bool>,
96 pub transpile_packages: Option<Vec<RcStr>>,
97 pub modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
98 pub dist_dir: Option<RcStr>,
99 pub deployment_id: Option<RcStr>,
100 sass_options: Option<serde_json::Value>,
101 pub trailing_slash: Option<bool>,
102 pub asset_prefix: Option<RcStr>,
103 pub base_path: Option<RcStr>,
104 pub skip_middleware_url_normalize: Option<bool>,
105 pub skip_trailing_slash_redirect: Option<bool>,
106 pub i18n: Option<I18NConfig>,
107 pub cross_origin: Option<CrossOriginConfig>,
108 pub dev_indicators: Option<DevIndicatorsConfig>,
109 pub output: Option<OutputType>,
110 pub turbopack: Option<TurbopackConfig>,
111 production_browser_source_maps: bool,
112 output_file_tracing_includes: Option<serde_json::Value>,
113 output_file_tracing_excludes: Option<serde_json::Value>,
114 output_file_tracing_root: Option<RcStr>,
116
117 pub bundle_pages_router_dependencies: Option<bool>,
122
123 pub server_external_packages: Option<Vec<RcStr>>,
128
129 #[serde(rename = "_originalRedirects")]
130 pub original_redirects: Option<Vec<Redirect>>,
131
132 pub compiler: Option<CompilerConfig>,
134
135 pub optimize_fonts: Option<bool>,
136
137 amp: AmpConfig,
139 clean_dist_dir: bool,
140 compress: bool,
141 eslint: EslintConfig,
142 exclude_default_moment_locales: bool,
143 export_path_map: Option<serde_json::Value>,
145 generate_build_id: Option<serde_json::Value>,
147 generate_etags: bool,
148 http_agent_options: HttpAgentConfig,
149 on_demand_entries: OnDemandEntriesConfig,
150 powered_by_header: bool,
151 public_runtime_config: FxIndexMap<String, serde_json::Value>,
152 server_runtime_config: FxIndexMap<String, serde_json::Value>,
153 static_page_generation_timeout: f64,
154 target: Option<String>,
155 typescript: TypeScriptConfig,
156 use_file_system_public_routes: bool,
157 webpack: Option<serde_json::Value>,
158}
159
160#[derive(
161 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
162)]
163#[serde(rename_all = "kebab-case")]
164pub enum CrossOriginConfig {
165 Anonymous,
166 UseCredentials,
167}
168
169#[derive(
170 Clone,
171 Debug,
172 Default,
173 PartialEq,
174 Serialize,
175 Deserialize,
176 TraceRawVcs,
177 NonLocalValue,
178 OperationValue,
179)]
180#[serde(rename_all = "camelCase")]
181struct AmpConfig {
182 canonical_base: Option<String>,
183}
184
185#[derive(
186 Clone,
187 Debug,
188 Default,
189 PartialEq,
190 Serialize,
191 Deserialize,
192 TraceRawVcs,
193 NonLocalValue,
194 OperationValue,
195)]
196#[serde(rename_all = "camelCase")]
197struct EslintConfig {
198 dirs: Option<Vec<String>>,
199 ignore_during_builds: Option<bool>,
200}
201
202#[derive(
203 Clone,
204 Debug,
205 Default,
206 PartialEq,
207 Serialize,
208 Deserialize,
209 TraceRawVcs,
210 NonLocalValue,
211 OperationValue,
212)]
213#[serde(rename_all = "kebab-case")]
214pub enum BuildActivityPositions {
215 #[default]
216 BottomRight,
217 BottomLeft,
218 TopRight,
219 TopLeft,
220}
221
222#[derive(
223 Clone,
224 Debug,
225 Default,
226 PartialEq,
227 Serialize,
228 Deserialize,
229 TraceRawVcs,
230 NonLocalValue,
231 OperationValue,
232)]
233#[serde(rename_all = "camelCase")]
234pub struct DevIndicatorsOptions {
235 pub build_activity_position: Option<BuildActivityPositions>,
236 pub position: Option<BuildActivityPositions>,
237}
238
239#[derive(
240 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
241)]
242#[serde(untagged)]
243pub enum DevIndicatorsConfig {
244 WithOptions(DevIndicatorsOptions),
245 Boolean(bool),
246}
247
248#[derive(
249 Clone,
250 Debug,
251 Default,
252 PartialEq,
253 Serialize,
254 Deserialize,
255 TraceRawVcs,
256 NonLocalValue,
257 OperationValue,
258)]
259#[serde(rename_all = "camelCase")]
260struct OnDemandEntriesConfig {
261 max_inactive_age: f64,
262 pages_buffer_length: f64,
263}
264
265#[derive(
266 Clone,
267 Debug,
268 Default,
269 PartialEq,
270 Serialize,
271 Deserialize,
272 TraceRawVcs,
273 NonLocalValue,
274 OperationValue,
275)]
276#[serde(rename_all = "camelCase")]
277struct HttpAgentConfig {
278 keep_alive: bool,
279}
280
281#[derive(
282 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
283)]
284#[serde(rename_all = "camelCase")]
285pub struct DomainLocale {
286 pub default_locale: String,
287 pub domain: String,
288 pub http: Option<bool>,
289 pub locales: Option<Vec<String>>,
290}
291
292#[derive(
293 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
294)]
295#[serde(rename_all = "camelCase")]
296pub struct I18NConfig {
297 pub default_locale: String,
298 pub domains: Option<Vec<DomainLocale>>,
299 pub locale_detection: Option<bool>,
300 pub locales: Vec<String>,
301}
302
303#[derive(
304 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
305)]
306#[serde(rename_all = "kebab-case")]
307pub enum OutputType {
308 Standalone,
309 Export,
310}
311
312#[derive(
313 Debug,
314 Clone,
315 Hash,
316 Eq,
317 PartialEq,
318 Ord,
319 PartialOrd,
320 TaskInput,
321 TraceRawVcs,
322 Serialize,
323 Deserialize,
324 NonLocalValue,
325 OperationValue,
326)]
327#[serde(tag = "type", rename_all = "kebab-case")]
328pub enum RouteHas {
329 Header {
330 key: RcStr,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 value: Option<RcStr>,
333 },
334 Cookie {
335 key: RcStr,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 value: Option<RcStr>,
338 },
339 Query {
340 key: RcStr,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 value: Option<RcStr>,
343 },
344 Host {
345 value: RcStr,
346 },
347}
348
349#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
350#[serde(rename_all = "camelCase")]
351pub struct HeaderValue {
352 pub key: RcStr,
353 pub value: RcStr,
354}
355
356#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
357#[serde(rename_all = "camelCase")]
358pub struct Header {
359 pub source: String,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub base_path: Option<bool>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub locale: Option<bool>,
364 pub headers: Vec<HeaderValue>,
365 #[serde(skip_serializing_if = "Option::is_none")]
366 pub has: Option<Vec<RouteHas>>,
367 #[serde(skip_serializing_if = "Option::is_none")]
368 pub missing: Option<Vec<RouteHas>>,
369}
370
371#[derive(
372 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
373)]
374#[serde(rename_all = "camelCase")]
375pub enum RedirectStatus {
376 StatusCode(f64),
377 Permanent(bool),
378}
379
380#[derive(
381 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
382)]
383#[serde(rename_all = "camelCase")]
384pub struct Redirect {
385 pub source: String,
386 pub destination: String,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub base_path: Option<bool>,
389 #[serde(skip_serializing_if = "Option::is_none")]
390 pub locale: Option<bool>,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub has: Option<Vec<RouteHas>>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub missing: Option<Vec<RouteHas>>,
395
396 #[serde(flatten)]
397 pub status: RedirectStatus,
398}
399
400#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
401#[serde(rename_all = "camelCase")]
402pub struct Rewrite {
403 pub source: String,
404 pub destination: String,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub base_path: Option<bool>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub locale: Option<bool>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 pub has: Option<Vec<RouteHas>>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 pub missing: Option<Vec<RouteHas>>,
413}
414
415#[turbo_tasks::value(eq = "manual")]
416#[derive(Clone, Debug, Default, PartialEq)]
417#[serde(rename_all = "camelCase")]
418pub struct Rewrites {
419 pub before_files: Vec<Rewrite>,
420 pub after_files: Vec<Rewrite>,
421 pub fallback: Vec<Rewrite>,
422}
423
424#[derive(
425 Clone,
426 Debug,
427 Default,
428 PartialEq,
429 Serialize,
430 Deserialize,
431 TraceRawVcs,
432 NonLocalValue,
433 OperationValue,
434)]
435#[serde(rename_all = "camelCase")]
436pub struct TypeScriptConfig {
437 pub ignore_build_errors: Option<bool>,
438 pub tsconfig_path: Option<String>,
439}
440
441#[turbo_tasks::value(eq = "manual", operation)]
442#[derive(Clone, Debug, PartialEq)]
443#[serde(rename_all = "camelCase")]
444pub struct ImageConfig {
445 pub device_sizes: Vec<u16>,
446 pub image_sizes: Vec<u16>,
447 pub path: String,
448 pub loader: ImageLoader,
449 #[serde(deserialize_with = "empty_string_is_none")]
450 pub loader_file: Option<String>,
451 pub domains: Vec<String>,
452 pub disable_static_images: bool,
453 #[serde(rename = "minimumCacheTTL")]
454 pub minimum_cache_ttl: u64,
455 pub formats: Vec<ImageFormat>,
456 #[serde(rename = "dangerouslyAllowSVG")]
457 pub dangerously_allow_svg: bool,
458 pub content_security_policy: String,
459 pub remote_patterns: Vec<RemotePattern>,
460 pub unoptimized: bool,
461}
462
463fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
464where
465 D: Deserializer<'de>,
466{
467 let o = Option::<String>::deserialize(deserializer)?;
468 Ok(o.filter(|s| !s.is_empty()))
469}
470
471impl Default for ImageConfig {
472 fn default() -> Self {
473 Self {
475 device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
476 image_sizes: vec![16, 32, 48, 64, 96, 128, 256, 384],
477 path: "/_next/image".to_string(),
478 loader: ImageLoader::Default,
479 loader_file: None,
480 domains: vec![],
481 disable_static_images: false,
482 minimum_cache_ttl: 60,
483 formats: vec![ImageFormat::Webp],
484 dangerously_allow_svg: false,
485 content_security_policy: "".to_string(),
486 remote_patterns: vec![],
487 unoptimized: false,
488 }
489 }
490}
491
492#[derive(
493 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
494)]
495#[serde(rename_all = "kebab-case")]
496pub enum ImageLoader {
497 Default,
498 Imgix,
499 Cloudinary,
500 Akamai,
501 Custom,
502}
503
504#[derive(
505 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
506)]
507pub enum ImageFormat {
508 #[serde(rename = "image/webp")]
509 Webp,
510 #[serde(rename = "image/avif")]
511 Avif,
512}
513
514#[derive(
515 Clone,
516 Debug,
517 Default,
518 PartialEq,
519 Serialize,
520 Deserialize,
521 TraceRawVcs,
522 NonLocalValue,
523 OperationValue,
524)]
525#[serde(rename_all = "camelCase")]
526pub struct RemotePattern {
527 pub hostname: String,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub protocol: Option<RemotePatternProtocol>,
530 #[serde(skip_serializing_if = "Option::is_none")]
531 pub port: Option<String>,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 pub pathname: Option<String>,
534}
535
536#[derive(
537 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
538)]
539#[serde(rename_all = "kebab-case")]
540pub enum RemotePatternProtocol {
541 Http,
542 Https,
543}
544
545#[derive(
546 Clone,
547 Debug,
548 Default,
549 PartialEq,
550 Serialize,
551 Deserialize,
552 TraceRawVcs,
553 NonLocalValue,
554 OperationValue,
555)]
556#[serde(rename_all = "camelCase")]
557pub struct TurbopackConfig {
558 pub loaders: Option<JsonValue>,
560 pub rules: Option<FxIndexMap<RcStr, RuleConfigCollection>>,
561 #[turbo_tasks(trace_ignore)]
562 pub conditions: Option<FxIndexMap<RcStr, ConfigConditionItem>>,
563 pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
564 pub resolve_extensions: Option<Vec<RcStr>>,
565 pub module_ids: Option<ModuleIds>,
566}
567
568#[derive(
569 Serialize, Deserialize, Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, OperationValue,
570)]
571#[serde(deny_unknown_fields)]
572pub struct RegexComponents {
573 source: RcStr,
574 flags: RcStr,
575}
576
577#[derive(
582 Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
583)]
584#[serde(
585 tag = "type",
586 content = "value",
587 rename_all = "camelCase",
588 deny_unknown_fields
589)]
590pub enum ConfigConditionPath {
591 Glob(RcStr),
592 Regex(RegexComponents),
593}
594
595impl TryFrom<ConfigConditionPath> for ConditionPath {
596 type Error = anyhow::Error;
597
598 fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
599 Ok(match config {
600 ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
601 ConfigConditionPath::Regex(path) => {
602 ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
603 }
604 })
605 }
606}
607
608impl TryFrom<RegexComponents> for EsRegex {
609 type Error = anyhow::Error;
610
611 fn try_from(components: RegexComponents) -> Result<EsRegex> {
612 EsRegex::new(&components.source, &components.flags)
613 }
614}
615
616#[derive(
617 Serialize, Deserialize, Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, OperationValue,
618)]
619#[serde(deny_unknown_fields)]
622pub enum ConfigConditionItem {
623 #[serde(rename = "all")]
624 All(Box<[ConfigConditionItem]>),
625 #[serde(rename = "any")]
626 Any(Box<[ConfigConditionItem]>),
627 #[serde(rename = "not")]
628 Not(Box<ConfigConditionItem>),
629 #[serde(untagged)]
630 Builtin(WebpackLoaderBuiltinCondition),
631 #[serde(untagged)]
632 Base {
633 #[serde(default)]
634 path: Option<ConfigConditionPath>,
635 #[serde(default)]
636 content: Option<RegexComponents>,
637 },
638}
639
640impl TryFrom<ConfigConditionItem> for ConditionItem {
641 type Error = anyhow::Error;
642
643 fn try_from(config: ConfigConditionItem) -> Result<Self> {
644 let try_from_vec = |conds: Box<[_]>| {
645 conds
646 .into_iter()
647 .map(ConditionItem::try_from)
648 .collect::<Result<_>>()
649 };
650 Ok(match config {
651 ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
652 ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
653 ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
654 ConfigConditionItem::Builtin(cond) => {
655 ConditionItem::Builtin(RcStr::from(cond.as_str()))
656 }
657 ConfigConditionItem::Base { path, content } => ConditionItem::Base {
658 path: path.map(ConditionPath::try_from).transpose()?,
659 content: content
660 .map(EsRegex::try_from)
661 .transpose()?
662 .map(EsRegex::resolved_cell),
663 },
664 })
665 }
666}
667
668#[derive(
669 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
670)]
671#[serde(rename_all = "camelCase")]
672pub struct RuleConfigItem {
673 pub loaders: Vec<LoaderItem>,
674 #[serde(default, alias = "as")]
675 pub rename_as: Option<RcStr>,
676 #[serde(default)]
677 pub condition: Option<ConfigConditionItem>,
678}
679
680#[derive(Clone, Debug, PartialEq, Eq, Serialize, TraceRawVcs, NonLocalValue, OperationValue)]
681#[serde(transparent)]
682pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
683
684impl<'de> Deserialize<'de> for RuleConfigCollection {
685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686 where
687 D: Deserializer<'de>,
688 {
689 match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
690 deserializer,
691 )? {
692 Either::Left(collection) => Ok(RuleConfigCollection(collection)),
693 Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
694 item,
695 )])),
696 }
697 }
698}
699
700#[derive(
701 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
702)]
703#[serde(untagged)]
704pub enum RuleConfigCollectionItem {
705 Shorthand(LoaderItem),
706 Full(RuleConfigItem),
707}
708
709#[derive(
710 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
711)]
712#[serde(untagged)]
713pub enum LoaderItem {
714 LoaderName(RcStr),
715 LoaderOptions(WebpackLoaderItem),
716}
717
718#[turbo_tasks::value(operation)]
719#[derive(Copy, Clone, Debug)]
720#[serde(rename_all = "camelCase")]
721pub enum ModuleIds {
722 Named,
723 Deterministic,
724}
725
726#[turbo_tasks::value(transparent)]
727pub struct OptionModuleIds(pub Option<ModuleIds>);
728
729#[derive(
730 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
731)]
732#[serde(untagged)]
733pub enum MdxRsOptions {
734 Boolean(bool),
735 Option(MdxTransformOptions),
736}
737
738#[turbo_tasks::value(shared, operation)]
739#[derive(Clone, Debug)]
740#[serde(rename_all = "camelCase")]
741pub enum ReactCompilerMode {
742 Infer,
743 Annotation,
744 All,
745}
746
747#[turbo_tasks::value(shared, operation)]
749#[derive(Clone, Debug)]
750#[serde(rename_all = "camelCase")]
751pub struct ReactCompilerOptions {
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub compilation_mode: Option<ReactCompilerMode>,
754 #[serde(skip_serializing_if = "Option::is_none")]
755 pub panic_threshold: Option<RcStr>,
756}
757
758#[derive(
759 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
760)]
761#[serde(untagged)]
762pub enum ReactCompilerOptionsOrBoolean {
763 Boolean(bool),
764 Option(ReactCompilerOptions),
765}
766
767#[turbo_tasks::value(transparent)]
768pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
769
770#[derive(
771 Clone,
772 Debug,
773 Default,
774 PartialEq,
775 Serialize,
776 Deserialize,
777 TraceRawVcs,
778 ValueDebugFormat,
779 NonLocalValue,
780 OperationValue,
781)]
782#[serde(rename_all = "camelCase")]
783pub struct ExperimentalConfig {
784 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
787 client_router_filter: Option<bool>,
788 client_router_filter_allowed_rate: Option<f64>,
791 client_router_filter_redirects: Option<bool>,
792 fetch_cache_key_prefix: Option<RcStr>,
793 isr_flush_to_disk: Option<bool>,
794 mdx_rs: Option<MdxRsOptions>,
797 strict_next_head: Option<bool>,
798 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
799 external_middleware_rewrites_resolve: Option<bool>,
800 scroll_restoration: Option<bool>,
801 manual_client_base_path: Option<bool>,
802 optimistic_client_cache: Option<bool>,
803 middleware_prefetch: Option<MiddlewarePrefetchType>,
804 optimize_css: Option<serde_json::Value>,
807 next_script_workers: Option<bool>,
808 web_vitals_attribution: Option<Vec<RcStr>>,
809 server_actions: Option<ServerActionsOrLegacyBool>,
810 sri: Option<SubResourceIntegrity>,
811 react_compiler: Option<ReactCompilerOptionsOrBoolean>,
812 cache_components: Option<bool>,
813 use_cache: Option<bool>,
814 root_params: Option<bool>,
815 adjust_font_fallbacks: Option<bool>,
819 adjust_font_fallbacks_with_size_adjust: Option<bool>,
820 after: Option<bool>,
821 amp: Option<serde_json::Value>,
822 app_document_preloading: Option<bool>,
823 cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
824 cache_life: Option<FxIndexMap<String, CacheLifeProfile>>,
825 case_sensitive_routes: Option<bool>,
826 cpus: Option<f64>,
827 cra_compat: Option<bool>,
828 disable_optimized_loading: Option<bool>,
829 disable_postcss_preset_env: Option<bool>,
830 esm_externals: Option<EsmExternals>,
831 extension_alias: Option<serde_json::Value>,
832 external_dir: Option<bool>,
833 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
838 fully_specified: Option<bool>,
839 gzip_size: Option<bool>,
840
841 pub inline_css: Option<bool>,
842 instrumentation_hook: Option<bool>,
843 client_trace_metadata: Option<Vec<String>>,
844 large_page_data_bytes: Option<f64>,
845 logging: Option<serde_json::Value>,
846 memory_based_workers_count: Option<bool>,
847 optimize_server_react: Option<bool>,
849 optimize_package_imports: Option<Vec<RcStr>>,
852 ppr: Option<ExperimentalPartialPrerendering>,
855 taint: Option<bool>,
856 #[serde(rename = "routerBFCache")]
857 router_bfcache: Option<bool>,
858 proxy_timeout: Option<f64>,
859 server_minification: Option<bool>,
861 server_source_maps: Option<bool>,
863 swc_trace_profiling: Option<bool>,
864 trust_host_header: Option<bool>,
866
867 url_imports: Option<serde_json::Value>,
868 view_transition: Option<bool>,
869 webpack_build_worker: Option<bool>,
872 worker_threads: Option<bool>,
873
874 turbopack_minify: Option<bool>,
875 turbopack_persistent_caching: Option<bool>,
876 turbopack_source_maps: Option<bool>,
877 turbopack_tree_shaking: Option<bool>,
878 turbopack_scope_hoisting: Option<bool>,
879 turbopack_use_system_tls_certs: Option<bool>,
880 #[serde(default)]
882 turbopack_use_builtin_sass: Option<bool>,
883 #[serde(default)]
886 turbopack_use_builtin_babel: Option<bool>,
887 global_not_found: Option<bool>,
889 turbopack_remove_unused_exports: Option<bool>,
891 devtool_segment_explorer: Option<bool>,
893}
894
895#[derive(
896 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
897)]
898#[serde(rename_all = "camelCase")]
899pub struct CacheLifeProfile {
900 #[serde(skip_serializing_if = "Option::is_none")]
901 pub stale: Option<u32>,
902 #[serde(skip_serializing_if = "Option::is_none")]
903 pub revalidate: Option<u32>,
904 #[serde(skip_serializing_if = "Option::is_none")]
905 pub expire: Option<u32>,
906}
907
908#[test]
909fn test_cache_life_profiles() {
910 let json = serde_json::json!({
911 "cacheLife": {
912 "frequent": {
913 "stale": 19,
914 "revalidate": 100,
915 },
916 }
917 });
918
919 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
920 let mut expected_cache_life = FxIndexMap::default();
921
922 expected_cache_life.insert(
923 "frequent".to_string(),
924 CacheLifeProfile {
925 stale: Some(19),
926 revalidate: Some(100),
927 expire: None,
928 },
929 );
930
931 assert_eq!(config.cache_life, Some(expected_cache_life));
932}
933
934#[test]
935fn test_cache_life_profiles_invalid() {
936 let json = serde_json::json!({
937 "cacheLife": {
938 "invalid": {
939 "stale": "invalid_value",
940 },
941 }
942 });
943
944 let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
945
946 assert!(
947 result.is_err(),
948 "Deserialization should fail due to invalid 'stale' value type"
949 );
950}
951
952#[derive(
953 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
954)]
955#[serde(rename_all = "lowercase")]
956pub enum ExperimentalPartialPrerenderingIncrementalValue {
957 Incremental,
958}
959
960#[derive(
961 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
962)]
963#[serde(untagged)]
964pub enum ExperimentalPartialPrerendering {
965 Boolean(bool),
966 Incremental(ExperimentalPartialPrerenderingIncrementalValue),
967}
968
969#[test]
970fn test_parse_experimental_partial_prerendering() {
971 let json = serde_json::json!({
972 "ppr": "incremental"
973 });
974 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
975 assert_eq!(
976 config.ppr,
977 Some(ExperimentalPartialPrerendering::Incremental(
978 ExperimentalPartialPrerenderingIncrementalValue::Incremental
979 ))
980 );
981
982 let json = serde_json::json!({
983 "ppr": true
984 });
985 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
986 assert_eq!(
987 config.ppr,
988 Some(ExperimentalPartialPrerendering::Boolean(true))
989 );
990
991 let json = serde_json::json!({
993 "ppr": "random"
994 });
995 let config = serde_json::from_value::<ExperimentalConfig>(json);
996 assert!(config.is_err());
997}
998
999#[derive(
1000 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1001)]
1002#[serde(rename_all = "camelCase")]
1003pub struct SubResourceIntegrity {
1004 pub algorithm: Option<RcStr>,
1005}
1006
1007#[derive(
1008 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
1009)]
1010#[serde(untagged)]
1011pub enum ServerActionsOrLegacyBool {
1012 ServerActionsConfig(ServerActions),
1014
1015 LegacyBool(bool),
1018}
1019
1020#[derive(
1021 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
1022)]
1023#[serde(rename_all = "kebab-case")]
1024pub enum EsmExternalsValue {
1025 Loose,
1026}
1027
1028#[derive(
1029 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
1030)]
1031#[serde(untagged)]
1032pub enum EsmExternals {
1033 Loose(EsmExternalsValue),
1034 Bool(bool),
1035}
1036
1037#[test]
1039fn test_esm_externals_deserialization() {
1040 let json = serde_json::json!({
1041 "esmExternals": true
1042 });
1043 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1044 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1045
1046 let json = serde_json::json!({
1047 "esmExternals": "loose"
1048 });
1049 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1050 assert_eq!(
1051 config.esm_externals,
1052 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1053 );
1054}
1055
1056#[derive(
1057 Clone,
1058 Debug,
1059 Default,
1060 PartialEq,
1061 Eq,
1062 Deserialize,
1063 Serialize,
1064 TraceRawVcs,
1065 NonLocalValue,
1066 OperationValue,
1067)]
1068#[serde(rename_all = "camelCase")]
1069pub struct ServerActions {
1070 pub body_size_limit: Option<SizeLimit>,
1072}
1073
1074#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
1075#[serde(untagged)]
1076pub enum SizeLimit {
1077 Number(f64),
1078 WithUnit(String),
1079}
1080
1081impl PartialEq for SizeLimit {
1084 fn eq(&self, other: &Self) -> bool {
1085 match (self, other) {
1086 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1087 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1088 _ => false,
1089 }
1090 }
1091}
1092
1093impl Eq for SizeLimit {}
1094
1095#[derive(
1096 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1097)]
1098#[serde(rename_all = "kebab-case")]
1099pub enum MiddlewarePrefetchType {
1100 Strict,
1101 Flexible,
1102}
1103
1104#[derive(
1105 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1106)]
1107#[serde(untagged)]
1108pub enum EmotionTransformOptionsOrBoolean {
1109 Boolean(bool),
1110 Options(EmotionTransformConfig),
1111}
1112
1113impl EmotionTransformOptionsOrBoolean {
1114 pub fn is_enabled(&self) -> bool {
1115 match self {
1116 Self::Boolean(enabled) => *enabled,
1117 _ => true,
1118 }
1119 }
1120}
1121
1122#[derive(
1123 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1124)]
1125#[serde(untagged)]
1126pub enum StyledComponentsTransformOptionsOrBoolean {
1127 Boolean(bool),
1128 Options(StyledComponentsTransformConfig),
1129}
1130
1131impl StyledComponentsTransformOptionsOrBoolean {
1132 pub fn is_enabled(&self) -> bool {
1133 match self {
1134 Self::Boolean(enabled) => *enabled,
1135 _ => true,
1136 }
1137 }
1138}
1139
1140#[turbo_tasks::value(eq = "manual")]
1141#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1142#[serde(rename_all = "camelCase")]
1143pub struct CompilerConfig {
1144 pub react_remove_properties: Option<ReactRemoveProperties>,
1145 pub relay: Option<RelayConfig>,
1146 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1147 pub remove_console: Option<RemoveConsoleConfig>,
1148 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1149}
1150
1151#[derive(
1152 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1153)]
1154#[serde(untagged, rename_all = "camelCase")]
1155pub enum ReactRemoveProperties {
1156 Boolean(bool),
1157 Config { properties: Option<Vec<String>> },
1158}
1159
1160impl ReactRemoveProperties {
1161 pub fn is_enabled(&self) -> bool {
1162 match self {
1163 Self::Boolean(enabled) => *enabled,
1164 _ => true,
1165 }
1166 }
1167}
1168
1169#[derive(
1170 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1171)]
1172#[serde(untagged)]
1173pub enum RemoveConsoleConfig {
1174 Boolean(bool),
1175 Config { exclude: Option<Vec<String>> },
1176}
1177
1178impl RemoveConsoleConfig {
1179 pub fn is_enabled(&self) -> bool {
1180 match self {
1181 Self::Boolean(enabled) => *enabled,
1182 _ => true,
1183 }
1184 }
1185}
1186
1187#[turbo_tasks::value(transparent)]
1188pub struct ResolveExtensions(Option<Vec<RcStr>>);
1189
1190#[turbo_tasks::value(transparent)]
1191pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1192
1193#[turbo_tasks::value(transparent)]
1194pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1195
1196#[turbo_tasks::value(transparent)]
1197
1198pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1199
1200#[turbo_tasks::value(transparent)]
1201pub struct OptionFileSystemPath(Option<FileSystemPath>);
1202
1203#[turbo_tasks::value(transparent)]
1204pub struct OptionServerActions(Option<ServerActions>);
1205
1206#[turbo_tasks::value(transparent)]
1207pub struct OptionJsonValue(pub Option<serde_json::Value>);
1208
1209fn turbopack_config_documentation_link() -> RcStr {
1210 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1211}
1212
1213#[turbo_tasks::value(shared)]
1214struct InvalidLoaderRuleRenameAsIssue {
1215 glob: RcStr,
1216 rename_as: RcStr,
1217 config_file_path: FileSystemPath,
1218}
1219
1220#[turbo_tasks::value_impl]
1221impl Issue for InvalidLoaderRuleRenameAsIssue {
1222 #[turbo_tasks::function]
1223 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1224 Ok(self.config_file_path.clone().cell())
1225 }
1226
1227 #[turbo_tasks::function]
1228 fn stage(&self) -> Vc<IssueStage> {
1229 IssueStage::Config.cell()
1230 }
1231
1232 #[turbo_tasks::function]
1233 async fn title(&self) -> Result<Vc<StyledString>> {
1234 Ok(
1235 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1236 .cell(),
1237 )
1238 }
1239
1240 #[turbo_tasks::function]
1241 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1242 Ok(Vc::cell(Some(
1243 StyledString::Text(RcStr::from(format!(
1244 "The extension {} contains a wildcard, but the `as` option does not: {}",
1245 self.glob, self.rename_as,
1246 )))
1247 .resolved_cell(),
1248 )))
1249 }
1250
1251 #[turbo_tasks::function]
1252 fn documentation_link(&self) -> Vc<RcStr> {
1253 Vc::cell(turbopack_config_documentation_link())
1254 }
1255}
1256
1257#[turbo_tasks::value(shared)]
1258struct InvalidLoaderRuleConditionIssue {
1259 condition: ConfigConditionItem,
1260 config_file_path: FileSystemPath,
1261}
1262
1263#[turbo_tasks::value_impl]
1264impl Issue for InvalidLoaderRuleConditionIssue {
1265 #[turbo_tasks::function]
1266 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1267 Ok(self.await?.config_file_path.clone().cell())
1268 }
1269
1270 #[turbo_tasks::function]
1271 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1272 IssueStage::Config.cell()
1273 }
1274
1275 #[turbo_tasks::function]
1276 async fn title(&self) -> Result<Vc<StyledString>> {
1277 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1278 }
1279
1280 #[turbo_tasks::function]
1281 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1282 Ok(Vc::cell(Some(
1283 StyledString::Text(RcStr::from(
1284 serde_json::to_string_pretty(&self.condition)
1285 .expect("condition must be serializable"),
1286 ))
1287 .resolved_cell(),
1288 )))
1289 }
1290
1291 #[turbo_tasks::function]
1292 fn documentation_link(&self) -> Vc<RcStr> {
1293 Vc::cell(turbopack_config_documentation_link())
1294 }
1295}
1296
1297#[turbo_tasks::value_impl]
1298impl NextConfig {
1299 #[turbo_tasks::function]
1300 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1301 let string = string.await?;
1302 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1303 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1304 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1305 Ok(config.cell())
1306 }
1307
1308 #[turbo_tasks::function]
1309 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1310 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1311 }
1312
1313 #[turbo_tasks::function]
1314 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1315 Vc::cell(self.react_production_profiling.unwrap_or_default())
1316 }
1317
1318 #[turbo_tasks::function]
1319 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1320 Vc::cell(
1321 self.server_external_packages
1322 .as_ref()
1323 .cloned()
1324 .unwrap_or_default(),
1325 )
1326 }
1327
1328 #[turbo_tasks::function]
1329 pub fn is_standalone(&self) -> Vc<bool> {
1330 Vc::cell(self.output == Some(OutputType::Standalone))
1331 }
1332
1333 #[turbo_tasks::function]
1334 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1335 if let Some(handler) = &self.cache_handler {
1336 Ok(Vc::cell(Some(project_path.join(handler)?)))
1337 } else {
1338 Ok(Vc::cell(None))
1339 }
1340 }
1341
1342 #[turbo_tasks::function]
1343 pub fn compiler(&self) -> Vc<CompilerConfig> {
1344 self.compiler.clone().unwrap_or_default().cell()
1345 }
1346
1347 #[turbo_tasks::function]
1348 pub fn env(&self) -> Vc<EnvMap> {
1349 let env = self
1353 .env
1354 .iter()
1355 .map(|(k, v)| {
1356 (
1357 k.as_str().into(),
1358 if let JsonValue::String(s) = v {
1359 s.as_str().into()
1361 } else {
1362 v.to_string().into()
1363 },
1364 )
1365 })
1366 .collect();
1367
1368 Vc::cell(env)
1369 }
1370
1371 #[turbo_tasks::function]
1372 pub fn image_config(&self) -> Vc<ImageConfig> {
1373 self.images.clone().cell()
1374 }
1375
1376 #[turbo_tasks::function]
1377 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1378 Vc::cell(self.page_extensions.clone())
1379 }
1380
1381 #[turbo_tasks::function]
1382 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1383 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1384 }
1385
1386 #[turbo_tasks::function]
1387 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1388 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1389 }
1390
1391 #[turbo_tasks::function]
1392 pub async fn webpack_rules(&self, project_path: FileSystemPath) -> Result<Vc<WebpackRules>> {
1393 let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1394 return Ok(Vc::cell(Vec::new()));
1395 };
1396 if turbo_rules.is_empty() {
1397 return Ok(Vc::cell(Vec::new()));
1398 }
1399 let mut rules = Vec::new();
1400 for (glob, rule_collection) in turbo_rules.iter() {
1401 fn transform_loaders(
1402 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1403 ) -> ResolvedVc<WebpackLoaderItems> {
1404 ResolvedVc::cell(
1405 loaders
1406 .map(|item| match item {
1407 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1408 loader: name.clone(),
1409 options: Default::default(),
1410 },
1411 LoaderItem::LoaderOptions(options) => options.clone(),
1412 })
1413 .collect(),
1414 )
1415 }
1416 let config_file_path = || project_path.join(&self.config_file_name);
1417 for item in &rule_collection.0 {
1418 match item {
1419 RuleConfigCollectionItem::Shorthand(loaders) => {
1420 rules.push((
1421 glob.clone(),
1422 LoaderRuleItem {
1423 loaders: transform_loaders(&mut [loaders].into_iter()),
1424 rename_as: None,
1425 condition: None,
1426 },
1427 ));
1428 }
1429 RuleConfigCollectionItem::Full(RuleConfigItem {
1430 loaders,
1431 rename_as,
1432 condition,
1433 }) => {
1434 if glob.contains("*")
1438 && let Some(rename_as) = rename_as.as_ref()
1439 && !rename_as.contains("*")
1440 {
1441 InvalidLoaderRuleRenameAsIssue {
1442 glob: glob.clone(),
1443 config_file_path: config_file_path()?,
1444 rename_as: rename_as.clone(),
1445 }
1446 .resolved_cell()
1447 .emit();
1448 }
1449
1450 let condition = if let Some(condition) = condition {
1453 if let Ok(cond) = ConditionItem::try_from(condition.clone()) {
1454 Some(cond)
1455 } else {
1456 InvalidLoaderRuleConditionIssue {
1457 condition: condition.clone(),
1458 config_file_path: config_file_path()?,
1459 }
1460 .resolved_cell()
1461 .emit();
1462 None
1463 }
1464 } else {
1465 None
1466 };
1467 rules.push((
1468 glob.clone(),
1469 LoaderRuleItem {
1470 loaders: transform_loaders(&mut loaders.iter()),
1471 rename_as: rename_as.clone(),
1472 condition,
1473 },
1474 ));
1475 }
1476 }
1477 }
1478 }
1479 Ok(Vc::cell(rules))
1480 }
1481
1482 #[turbo_tasks::function]
1483 pub fn webpack_conditions(&self) -> Result<Vc<OptionWebpackConditions>> {
1484 let Some(config_conditions) = self.turbopack.as_ref().and_then(|t| t.conditions.as_ref())
1485 else {
1486 return Ok(Vc::cell(None));
1487 };
1488
1489 let conditions = config_conditions
1490 .iter()
1491 .map(|(k, v)| {
1492 let item: Result<ConditionItem> = TryInto::<ConditionItem>::try_into((*v).clone());
1493 item.map(|item| (k.clone(), item))
1494 })
1495 .collect::<Result<FxIndexMap<RcStr, ConditionItem>>>()?;
1496
1497 Ok(Vc::cell(Some(ResolvedVc::cell(conditions))))
1498 }
1499
1500 #[turbo_tasks::function]
1501 pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1502 Ok(Vc::cell(
1503 self.experimental
1504 .turbopack_persistent_caching
1505 .unwrap_or_default(),
1506 ))
1507 }
1508
1509 #[turbo_tasks::function]
1510 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1511 let Some(resolve_alias) = self
1512 .turbopack
1513 .as_ref()
1514 .and_then(|t| t.resolve_alias.as_ref())
1515 else {
1516 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1517 };
1518 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1519 Ok(alias_map.cell())
1520 }
1521
1522 #[turbo_tasks::function]
1523 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1524 let Some(resolve_extensions) = self
1525 .turbopack
1526 .as_ref()
1527 .and_then(|t| t.resolve_extensions.as_ref())
1528 else {
1529 return Vc::cell(None);
1530 };
1531 Vc::cell(Some(resolve_extensions.clone()))
1532 }
1533
1534 #[turbo_tasks::function]
1535 pub fn import_externals(&self) -> Result<Vc<bool>> {
1536 Ok(Vc::cell(match self.experimental.esm_externals {
1537 Some(EsmExternals::Bool(b)) => b,
1538 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1539 None => true,
1540 }))
1541 }
1542
1543 #[turbo_tasks::function]
1544 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1545 let options = &self.experimental.mdx_rs;
1546
1547 let options = match options {
1548 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1549 MdxTransformOptions {
1550 provider_import_source: Some(mdx_import_source_file()),
1551 ..Default::default()
1552 }
1553 .resolved_cell(),
1554 )),
1555 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1556 MdxTransformOptions {
1557 provider_import_source: Some(
1558 options
1559 .provider_import_source
1560 .clone()
1561 .unwrap_or(mdx_import_source_file()),
1562 ),
1563 ..options.clone()
1564 }
1565 .resolved_cell(),
1566 )),
1567 _ => OptionalMdxTransformOptions(None),
1568 };
1569
1570 options.cell()
1571 }
1572
1573 #[turbo_tasks::function]
1574 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1575 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1576 }
1577
1578 #[turbo_tasks::function]
1579 pub fn experimental_cache_handlers(
1580 &self,
1581 project_path: FileSystemPath,
1582 ) -> Result<Vc<FileSystemPathVec>> {
1583 if let Some(handlers) = &self.experimental.cache_handlers {
1584 Ok(Vc::cell(
1585 handlers
1586 .values()
1587 .map(|h| project_path.join(h))
1588 .collect::<Result<Vec<_>>>()?,
1589 ))
1590 } else {
1591 Ok(Vc::cell(vec![]))
1592 }
1593 }
1594
1595 #[turbo_tasks::function]
1596 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1597 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1598 }
1599
1600 #[turbo_tasks::function]
1601 pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1602 Vc::cell(self.experimental.sri.clone())
1603 }
1604
1605 #[turbo_tasks::function]
1606 pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1607 Vc::cell(match self.experimental.server_actions.as_ref() {
1608 Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1609 Some(server_actions.clone())
1610 }
1611 Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1612 _ => None,
1613 })
1614 }
1615
1616 #[turbo_tasks::function]
1617 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1618 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1619 }
1620
1621 #[turbo_tasks::function]
1622 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1623 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1624 }
1625
1626 #[turbo_tasks::function]
1627 pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
1628 let options = &self.experimental.react_compiler;
1629
1630 let options = match options {
1631 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1632 OptionalReactCompilerOptions(Some(
1633 ReactCompilerOptions {
1634 compilation_mode: None,
1635 panic_threshold: None,
1636 }
1637 .resolved_cell(),
1638 ))
1639 }
1640 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1641 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1642 ),
1643 _ => OptionalReactCompilerOptions(None),
1644 };
1645
1646 options.cell()
1647 }
1648
1649 #[turbo_tasks::function]
1650 pub fn sass_config(&self) -> Vc<JsonValue> {
1651 Vc::cell(self.sass_options.clone().unwrap_or_default())
1652 }
1653
1654 #[turbo_tasks::function]
1655 pub fn skip_middleware_url_normalize(&self) -> Vc<bool> {
1656 Vc::cell(self.skip_middleware_url_normalize.unwrap_or(false))
1657 }
1658
1659 #[turbo_tasks::function]
1660 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1661 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1662 }
1663
1664 #[turbo_tasks::function]
1667 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1668 let this = self.await?;
1669
1670 Ok(Vc::cell(Some(
1671 format!(
1672 "{}/_next/",
1673 if let Some(asset_prefix) = &this.asset_prefix {
1674 asset_prefix
1675 } else {
1676 this.base_path.as_ref().map_or("", |b| b.as_str())
1677 }
1678 .trim_end_matches('/')
1679 )
1680 .into(),
1681 )))
1682 }
1683
1684 #[turbo_tasks::function]
1686 pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1687 let this = self.await?;
1688
1689 match &this.deployment_id {
1690 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1691 None => Ok(Vc::cell(None)),
1692 }
1693 }
1694
1695 #[turbo_tasks::function]
1696 pub fn enable_ppr(&self) -> Vc<bool> {
1697 Vc::cell(
1698 self.experimental
1699 .ppr
1700 .as_ref()
1701 .map(|ppr| match ppr {
1702 ExperimentalPartialPrerendering::Incremental(
1703 ExperimentalPartialPrerenderingIncrementalValue::Incremental,
1704 ) => true,
1705 ExperimentalPartialPrerendering::Boolean(b) => *b,
1706 })
1707 .unwrap_or(false),
1708 )
1709 }
1710
1711 #[turbo_tasks::function]
1712 pub fn enable_taint(&self) -> Vc<bool> {
1713 Vc::cell(self.experimental.taint.unwrap_or(false))
1714 }
1715
1716 #[turbo_tasks::function]
1717 pub fn enable_router_bfcache(&self) -> Vc<bool> {
1718 Vc::cell(self.experimental.router_bfcache.unwrap_or(false))
1719 }
1720
1721 #[turbo_tasks::function]
1722 pub fn enable_view_transition(&self) -> Vc<bool> {
1723 Vc::cell(self.experimental.view_transition.unwrap_or(false))
1724 }
1725
1726 #[turbo_tasks::function]
1727 pub fn enable_cache_components(&self) -> Vc<bool> {
1728 Vc::cell(self.experimental.cache_components.unwrap_or(false))
1729 }
1730
1731 #[turbo_tasks::function]
1732 pub fn enable_use_cache(&self) -> Vc<bool> {
1733 Vc::cell(
1734 self.experimental
1735 .use_cache
1736 .unwrap_or(self.experimental.cache_components.unwrap_or(false)),
1740 )
1741 }
1742
1743 #[turbo_tasks::function]
1744 pub fn enable_root_params(&self) -> Vc<bool> {
1745 Vc::cell(
1746 self.experimental
1747 .root_params
1748 .unwrap_or(self.experimental.cache_components.unwrap_or(false)),
1750 )
1751 }
1752
1753 #[turbo_tasks::function]
1754 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1755 let mut cache_kinds = CacheKinds::default();
1756
1757 if let Some(handlers) = self.experimental.cache_handlers.as_ref() {
1758 cache_kinds.extend(handlers.keys().cloned());
1759 }
1760
1761 cache_kinds.cell()
1762 }
1763
1764 #[turbo_tasks::function]
1765 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1766 Vc::cell(
1767 self.experimental
1768 .optimize_package_imports
1769 .clone()
1770 .unwrap_or_default(),
1771 )
1772 }
1773
1774 #[turbo_tasks::function]
1775 pub fn tree_shaking_mode_for_foreign_code(
1776 &self,
1777 _is_development: bool,
1778 ) -> Vc<OptionTreeShaking> {
1779 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1780 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1781 Some(true) => Some(TreeShakingMode::ModuleFragments),
1782 None => Some(TreeShakingMode::ReexportsOnly),
1783 })
1784 .cell()
1785 }
1786
1787 #[turbo_tasks::function]
1788 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1789 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1790 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1791 Some(true) => Some(TreeShakingMode::ModuleFragments),
1792 None => Some(TreeShakingMode::ReexportsOnly),
1793 })
1794 .cell()
1795 }
1796
1797 #[turbo_tasks::function]
1798 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1799 Ok(Vc::cell(
1800 self.experimental
1801 .turbopack_remove_unused_exports
1802 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1803 ))
1804 }
1805
1806 #[turbo_tasks::function]
1807 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1808 Ok(match *mode.await? {
1809 NextMode::Development => ModuleIds::Named.cell(),
1811 NextMode::Build => self
1812 .turbopack
1813 .as_ref()
1814 .and_then(|t| t.module_ids)
1815 .unwrap_or(ModuleIds::Deterministic)
1816 .cell(),
1817 })
1818 }
1819
1820 #[turbo_tasks::function]
1821 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1822 let minify = self.experimental.turbopack_minify;
1823 Ok(Vc::cell(
1824 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1825 ))
1826 }
1827
1828 #[turbo_tasks::function]
1829 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1830 Ok(Vc::cell(match *mode.await? {
1831 NextMode::Development => false,
1833 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1834 }))
1835 }
1836
1837 #[turbo_tasks::function]
1838 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1839 let source_maps = self.experimental.turbopack_source_maps;
1840 Ok(Vc::cell(source_maps.unwrap_or(match &*mode.await? {
1841 NextMode::Development => true,
1842 NextMode::Build => self.production_browser_source_maps,
1843 })))
1844 }
1845
1846 #[turbo_tasks::function]
1847 pub fn server_source_maps(&self) -> Result<Vc<bool>> {
1848 let source_maps = self.experimental.turbopack_source_maps;
1849 Ok(Vc::cell(source_maps.unwrap_or(true)))
1850 }
1851
1852 #[turbo_tasks::function]
1853 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1854 Ok(Vc::cell(
1855 self.typescript
1856 .tsconfig_path
1857 .as_ref()
1858 .map(|path| path.to_owned().into()),
1859 ))
1860 }
1861
1862 #[turbo_tasks::function]
1863 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
1864 Vc::cell(self.output_file_tracing_includes.clone())
1865 }
1866
1867 #[turbo_tasks::function]
1868 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
1869 Vc::cell(self.output_file_tracing_excludes.clone())
1870 }
1871
1872 #[turbo_tasks::function]
1873 pub async fn fetch_client(&self, env: Vc<Box<dyn ProcessEnv>>) -> Result<Vc<FetchClient>> {
1874 let use_system_tls_certs = env
1878 .read(rcstr!("NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS"))
1879 .await?
1880 .as_ref()
1881 .and_then(|env_value| {
1882 (!env_value.is_empty()).then(|| env_value == "1" || env_value == "true")
1884 })
1885 .or(self.experimental.turbopack_use_system_tls_certs)
1886 .unwrap_or(false);
1887 Ok(FetchClient {
1888 tls_built_in_webpki_certs: !use_system_tls_certs,
1889 tls_built_in_native_certs: use_system_tls_certs,
1890 }
1891 .cell())
1892 }
1893}
1894
1895#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1898#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1899#[serde(rename_all = "camelCase")]
1900pub struct JsConfig {
1901 compiler_options: Option<serde_json::Value>,
1902}
1903
1904#[turbo_tasks::value_impl]
1905impl JsConfig {
1906 #[turbo_tasks::function]
1907 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1908 let string = string.await?;
1909 let config: JsConfig = serde_json::from_str(&string)
1910 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1911
1912 Ok(config.cell())
1913 }
1914
1915 #[turbo_tasks::function]
1916 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1917 Vc::cell(self.compiler_options.clone().unwrap_or_default())
1918 }
1919}
1920
1921#[cfg(test)]
1922mod tests {
1923 use super::*;
1924
1925 #[test]
1926 fn test_serde_rule_config_item_options() {
1927 let json_value = serde_json::json!({
1928 "loaders": [],
1929 "as": "*.js",
1930 "condition": {
1931 "all": [
1932 "production",
1933 {"not": "foreign"},
1934 {"any": [
1935 "browser",
1936 {
1937 "path": { "type": "glob", "value": "*.svg"},
1938 "content": {
1939 "source": "@someTag",
1940 "flags": ""
1941 }
1942 }
1943 ]},
1944 ],
1945 }
1946 });
1947
1948 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
1949
1950 assert_eq!(
1951 rule_config,
1952 RuleConfigItem {
1953 loaders: vec![],
1954 rename_as: Some(rcstr!("*.js")),
1955 condition: Some(ConfigConditionItem::All(
1956 [
1957 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
1958 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
1959 WebpackLoaderBuiltinCondition::Foreign
1960 ))),
1961 ConfigConditionItem::Any(
1962 vec![
1963 ConfigConditionItem::Builtin(
1964 WebpackLoaderBuiltinCondition::Browser
1965 ),
1966 ConfigConditionItem::Base {
1967 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
1968 content: Some(RegexComponents {
1969 source: rcstr!("@someTag"),
1970 flags: rcstr!(""),
1971 }),
1972 },
1973 ]
1974 .into(),
1975 ),
1976 ]
1977 .into(),
1978 )),
1979 }
1980 );
1981 }
1982}