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