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 transition_indicator: Option<bool>,
874 trust_host_header: Option<bool>,
876
877 url_imports: Option<serde_json::Value>,
878 webpack_build_worker: Option<bool>,
881 worker_threads: Option<bool>,
882
883 turbopack_minify: Option<bool>,
884 turbopack_module_ids: Option<ModuleIds>,
885 turbopack_persistent_caching: Option<bool>,
886 turbopack_source_maps: Option<bool>,
887 turbopack_tree_shaking: Option<bool>,
888 turbopack_scope_hoisting: Option<bool>,
889 turbopack_import_type_bytes: Option<bool>,
890 turbopack_use_system_tls_certs: Option<bool>,
891 #[serde(default)]
893 turbopack_use_builtin_sass: Option<bool>,
894 #[serde(default)]
897 turbopack_use_builtin_babel: Option<bool>,
898 global_not_found: Option<bool>,
900 turbopack_remove_unused_exports: Option<bool>,
902 devtool_segment_explorer: Option<bool>,
904}
905
906#[derive(
907 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
908)]
909#[serde(rename_all = "camelCase")]
910pub struct CacheLifeProfile {
911 #[serde(skip_serializing_if = "Option::is_none")]
912 pub stale: Option<u32>,
913 #[serde(skip_serializing_if = "Option::is_none")]
914 pub revalidate: Option<u32>,
915 #[serde(skip_serializing_if = "Option::is_none")]
916 pub expire: Option<u32>,
917}
918
919#[test]
920fn test_cache_life_profiles() {
921 let json = serde_json::json!({
922 "cacheLife": {
923 "frequent": {
924 "stale": 19,
925 "revalidate": 100,
926 },
927 }
928 });
929
930 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
931 let mut expected_cache_life = FxIndexMap::default();
932
933 expected_cache_life.insert(
934 "frequent".to_string(),
935 CacheLifeProfile {
936 stale: Some(19),
937 revalidate: Some(100),
938 expire: None,
939 },
940 );
941
942 assert_eq!(config.cache_life, Some(expected_cache_life));
943}
944
945#[test]
946fn test_cache_life_profiles_invalid() {
947 let json = serde_json::json!({
948 "cacheLife": {
949 "invalid": {
950 "stale": "invalid_value",
951 },
952 }
953 });
954
955 let result: Result<ExperimentalConfig, _> = serde_json::from_value(json);
956
957 assert!(
958 result.is_err(),
959 "Deserialization should fail due to invalid 'stale' value type"
960 );
961}
962
963#[derive(
964 Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
965)]
966#[serde(rename_all = "camelCase")]
967pub struct SubResourceIntegrity {
968 pub algorithm: Option<RcStr>,
969}
970
971#[derive(
972 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
973)]
974#[serde(untagged)]
975pub enum ServerActionsOrLegacyBool {
976 ServerActionsConfig(ServerActions),
978
979 LegacyBool(bool),
982}
983
984#[derive(
985 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
986)]
987#[serde(rename_all = "kebab-case")]
988pub enum EsmExternalsValue {
989 Loose,
990}
991
992#[derive(
993 Clone, Debug, PartialEq, Deserialize, Serialize, TraceRawVcs, NonLocalValue, OperationValue,
994)]
995#[serde(untagged)]
996pub enum EsmExternals {
997 Loose(EsmExternalsValue),
998 Bool(bool),
999}
1000
1001#[test]
1003fn test_esm_externals_deserialization() {
1004 let json = serde_json::json!({
1005 "esmExternals": true
1006 });
1007 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1008 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1009
1010 let json = serde_json::json!({
1011 "esmExternals": "loose"
1012 });
1013 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1014 assert_eq!(
1015 config.esm_externals,
1016 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1017 );
1018}
1019
1020#[derive(
1021 Clone,
1022 Debug,
1023 Default,
1024 PartialEq,
1025 Eq,
1026 Deserialize,
1027 Serialize,
1028 TraceRawVcs,
1029 NonLocalValue,
1030 OperationValue,
1031)]
1032#[serde(rename_all = "camelCase")]
1033pub struct ServerActions {
1034 pub body_size_limit: Option<SizeLimit>,
1036}
1037
1038#[derive(Clone, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue)]
1039#[serde(untagged)]
1040pub enum SizeLimit {
1041 Number(f64),
1042 WithUnit(String),
1043}
1044
1045impl PartialEq for SizeLimit {
1048 fn eq(&self, other: &Self) -> bool {
1049 match (self, other) {
1050 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1051 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1052 _ => false,
1053 }
1054 }
1055}
1056
1057impl Eq for SizeLimit {}
1058
1059#[derive(
1060 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1061)]
1062#[serde(rename_all = "kebab-case")]
1063pub enum MiddlewarePrefetchType {
1064 Strict,
1065 Flexible,
1066}
1067
1068#[derive(
1069 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1070)]
1071#[serde(untagged)]
1072pub enum EmotionTransformOptionsOrBoolean {
1073 Boolean(bool),
1074 Options(EmotionTransformConfig),
1075}
1076
1077impl EmotionTransformOptionsOrBoolean {
1078 pub fn is_enabled(&self) -> bool {
1079 match self {
1080 Self::Boolean(enabled) => *enabled,
1081 _ => true,
1082 }
1083 }
1084}
1085
1086#[derive(
1087 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1088)]
1089#[serde(untagged)]
1090pub enum StyledComponentsTransformOptionsOrBoolean {
1091 Boolean(bool),
1092 Options(StyledComponentsTransformConfig),
1093}
1094
1095impl StyledComponentsTransformOptionsOrBoolean {
1096 pub fn is_enabled(&self) -> bool {
1097 match self {
1098 Self::Boolean(enabled) => *enabled,
1099 _ => true,
1100 }
1101 }
1102}
1103
1104#[turbo_tasks::value(eq = "manual")]
1105#[derive(Clone, Debug, PartialEq, Default, OperationValue)]
1106#[serde(rename_all = "camelCase")]
1107pub struct CompilerConfig {
1108 pub react_remove_properties: Option<ReactRemoveProperties>,
1109 pub relay: Option<RelayConfig>,
1110 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1111 pub remove_console: Option<RemoveConsoleConfig>,
1112 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1113}
1114
1115#[derive(
1116 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1117)]
1118#[serde(untagged, rename_all = "camelCase")]
1119pub enum ReactRemoveProperties {
1120 Boolean(bool),
1121 Config { properties: Option<Vec<String>> },
1122}
1123
1124impl ReactRemoveProperties {
1125 pub fn is_enabled(&self) -> bool {
1126 match self {
1127 Self::Boolean(enabled) => *enabled,
1128 _ => true,
1129 }
1130 }
1131}
1132
1133#[derive(
1134 Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
1135)]
1136#[serde(untagged)]
1137pub enum RemoveConsoleConfig {
1138 Boolean(bool),
1139 Config { exclude: Option<Vec<String>> },
1140}
1141
1142impl RemoveConsoleConfig {
1143 pub fn is_enabled(&self) -> bool {
1144 match self {
1145 Self::Boolean(enabled) => *enabled,
1146 _ => true,
1147 }
1148 }
1149}
1150
1151#[turbo_tasks::value(transparent)]
1152pub struct ResolveExtensions(Option<Vec<RcStr>>);
1153
1154#[turbo_tasks::value(transparent)]
1155pub struct SwcPlugins(Vec<(RcStr, serde_json::Value)>);
1156
1157#[turbo_tasks::value(transparent)]
1158pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1159
1160#[turbo_tasks::value(transparent)]
1161
1162pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1163
1164#[turbo_tasks::value(transparent)]
1165pub struct OptionFileSystemPath(Option<FileSystemPath>);
1166
1167#[turbo_tasks::value(transparent)]
1168pub struct OptionServerActions(Option<ServerActions>);
1169
1170#[turbo_tasks::value(transparent)]
1171pub struct OptionJsonValue(pub Option<serde_json::Value>);
1172
1173fn turbopack_config_documentation_link() -> RcStr {
1174 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1175}
1176
1177#[turbo_tasks::value(shared)]
1178struct InvalidLoaderRuleRenameAsIssue {
1179 glob: RcStr,
1180 rename_as: RcStr,
1181 config_file_path: FileSystemPath,
1182}
1183
1184#[turbo_tasks::value_impl]
1185impl Issue for InvalidLoaderRuleRenameAsIssue {
1186 #[turbo_tasks::function]
1187 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1188 Ok(self.config_file_path.clone().cell())
1189 }
1190
1191 #[turbo_tasks::function]
1192 fn stage(&self) -> Vc<IssueStage> {
1193 IssueStage::Config.cell()
1194 }
1195
1196 #[turbo_tasks::function]
1197 async fn title(&self) -> Result<Vc<StyledString>> {
1198 Ok(
1199 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1200 .cell(),
1201 )
1202 }
1203
1204 #[turbo_tasks::function]
1205 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1206 Ok(Vc::cell(Some(
1207 StyledString::Text(RcStr::from(format!(
1208 "The extension {} contains a wildcard, but the `as` option does not: {}",
1209 self.glob, self.rename_as,
1210 )))
1211 .resolved_cell(),
1212 )))
1213 }
1214
1215 #[turbo_tasks::function]
1216 fn documentation_link(&self) -> Vc<RcStr> {
1217 Vc::cell(turbopack_config_documentation_link())
1218 }
1219}
1220
1221#[turbo_tasks::value(shared)]
1222struct InvalidLoaderRuleConditionIssue {
1223 condition: ConfigConditionItem,
1224 config_file_path: FileSystemPath,
1225}
1226
1227#[turbo_tasks::value_impl]
1228impl Issue for InvalidLoaderRuleConditionIssue {
1229 #[turbo_tasks::function]
1230 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1231 Ok(self.await?.config_file_path.clone().cell())
1232 }
1233
1234 #[turbo_tasks::function]
1235 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1236 IssueStage::Config.cell()
1237 }
1238
1239 #[turbo_tasks::function]
1240 async fn title(&self) -> Result<Vc<StyledString>> {
1241 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1242 }
1243
1244 #[turbo_tasks::function]
1245 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1246 Ok(Vc::cell(Some(
1247 StyledString::Text(RcStr::from(
1248 serde_json::to_string_pretty(&self.condition)
1249 .expect("condition must be serializable"),
1250 ))
1251 .resolved_cell(),
1252 )))
1253 }
1254
1255 #[turbo_tasks::function]
1256 fn documentation_link(&self) -> Vc<RcStr> {
1257 Vc::cell(turbopack_config_documentation_link())
1258 }
1259}
1260
1261#[turbo_tasks::value_impl]
1262impl NextConfig {
1263 #[turbo_tasks::function]
1264 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1265 let string = string.await?;
1266 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1267 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1268 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1269 Ok(config.cell())
1270 }
1271
1272 #[turbo_tasks::function]
1273 pub async fn config_file_path(
1274 &self,
1275 project_path: FileSystemPath,
1276 ) -> Result<Vc<FileSystemPath>> {
1277 Ok(project_path.join(&self.config_file_name)?.cell())
1278 }
1279
1280 #[turbo_tasks::function]
1281 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1282 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1283 }
1284
1285 #[turbo_tasks::function]
1286 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1287 Vc::cell(self.react_production_profiling.unwrap_or_default())
1288 }
1289
1290 #[turbo_tasks::function]
1291 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1292 Vc::cell(
1293 self.server_external_packages
1294 .as_ref()
1295 .cloned()
1296 .unwrap_or_default(),
1297 )
1298 }
1299
1300 #[turbo_tasks::function]
1301 pub fn is_standalone(&self) -> Vc<bool> {
1302 Vc::cell(self.output == Some(OutputType::Standalone))
1303 }
1304
1305 #[turbo_tasks::function]
1306 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1307 Vc::cell(self.base_path.clone())
1308 }
1309
1310 #[turbo_tasks::function]
1311 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1312 if let Some(handler) = &self.cache_handler {
1313 Ok(Vc::cell(Some(project_path.join(handler)?)))
1314 } else {
1315 Ok(Vc::cell(None))
1316 }
1317 }
1318
1319 #[turbo_tasks::function]
1320 pub fn compiler(&self) -> Vc<CompilerConfig> {
1321 self.compiler.clone().unwrap_or_default().cell()
1322 }
1323
1324 #[turbo_tasks::function]
1325 pub fn env(&self) -> Vc<EnvMap> {
1326 let env = self
1330 .env
1331 .iter()
1332 .map(|(k, v)| {
1333 (
1334 k.as_str().into(),
1335 if let JsonValue::String(s) = v {
1336 s.as_str().into()
1338 } else {
1339 v.to_string().into()
1340 },
1341 )
1342 })
1343 .collect();
1344
1345 Vc::cell(env)
1346 }
1347
1348 #[turbo_tasks::function]
1349 pub fn image_config(&self) -> Vc<ImageConfig> {
1350 self.images.clone().cell()
1351 }
1352
1353 #[turbo_tasks::function]
1354 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1355 let mut extensions = self.page_extensions.clone();
1359 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1360 Vc::cell(extensions)
1361 }
1362
1363 #[turbo_tasks::function]
1364 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1365 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1366 }
1367
1368 #[turbo_tasks::function]
1369 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1370 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1371 }
1372
1373 #[turbo_tasks::function]
1374 pub async fn webpack_rules(
1375 self: Vc<Self>,
1376 project_path: FileSystemPath,
1377 ) -> Result<Vc<WebpackRules>> {
1378 let this = self.await?;
1379 let Some(turbo_rules) = this.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1380 return Ok(Vc::cell(Vec::new()));
1381 };
1382 if turbo_rules.is_empty() {
1383 return Ok(Vc::cell(Vec::new()));
1384 }
1385 let mut rules = Vec::new();
1386 for (glob, rule_collection) in turbo_rules.iter() {
1387 fn transform_loaders(
1388 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1389 ) -> ResolvedVc<WebpackLoaderItems> {
1390 ResolvedVc::cell(
1391 loaders
1392 .map(|item| match item {
1393 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1394 loader: name.clone(),
1395 options: Default::default(),
1396 },
1397 LoaderItem::LoaderOptions(options) => options.clone(),
1398 })
1399 .collect(),
1400 )
1401 }
1402 for item in &rule_collection.0 {
1403 match item {
1404 RuleConfigCollectionItem::Shorthand(loaders) => {
1405 rules.push((
1406 glob.clone(),
1407 LoaderRuleItem {
1408 loaders: transform_loaders(&mut [loaders].into_iter()),
1409 rename_as: None,
1410 condition: None,
1411 },
1412 ));
1413 }
1414 RuleConfigCollectionItem::Full(RuleConfigItem {
1415 loaders,
1416 rename_as,
1417 condition,
1418 }) => {
1419 if glob.contains("*")
1423 && let Some(rename_as) = rename_as.as_ref()
1424 && !rename_as.contains("*")
1425 {
1426 InvalidLoaderRuleRenameAsIssue {
1427 glob: glob.clone(),
1428 config_file_path: self
1429 .config_file_path(project_path.clone())
1430 .owned()
1431 .await?,
1432 rename_as: rename_as.clone(),
1433 }
1434 .resolved_cell()
1435 .emit();
1436 }
1437
1438 let condition = if let Some(condition) = condition {
1441 if let Ok(cond) = ConditionItem::try_from(condition.clone()) {
1442 Some(cond)
1443 } else {
1444 InvalidLoaderRuleConditionIssue {
1445 condition: condition.clone(),
1446 config_file_path: self
1447 .config_file_path(project_path.clone())
1448 .owned()
1449 .await?,
1450 }
1451 .resolved_cell()
1452 .emit();
1453 None
1454 }
1455 } else {
1456 None
1457 };
1458 rules.push((
1459 glob.clone(),
1460 LoaderRuleItem {
1461 loaders: transform_loaders(&mut loaders.iter()),
1462 rename_as: rename_as.clone(),
1463 condition,
1464 },
1465 ));
1466 }
1467 }
1468 }
1469 }
1470 Ok(Vc::cell(rules))
1471 }
1472
1473 #[turbo_tasks::function]
1474 pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1475 Ok(Vc::cell(
1476 self.experimental
1477 .turbopack_persistent_caching
1478 .unwrap_or_default(),
1479 ))
1480 }
1481
1482 #[turbo_tasks::function]
1483 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1484 let Some(resolve_alias) = self
1485 .turbopack
1486 .as_ref()
1487 .and_then(|t| t.resolve_alias.as_ref())
1488 else {
1489 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1490 };
1491 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1492 Ok(alias_map.cell())
1493 }
1494
1495 #[turbo_tasks::function]
1496 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1497 let Some(resolve_extensions) = self
1498 .turbopack
1499 .as_ref()
1500 .and_then(|t| t.resolve_extensions.as_ref())
1501 else {
1502 return Vc::cell(None);
1503 };
1504 Vc::cell(Some(resolve_extensions.clone()))
1505 }
1506
1507 #[turbo_tasks::function]
1508 pub fn import_externals(&self) -> Result<Vc<bool>> {
1509 Ok(Vc::cell(match self.experimental.esm_externals {
1510 Some(EsmExternals::Bool(b)) => b,
1511 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1512 None => true,
1513 }))
1514 }
1515
1516 #[turbo_tasks::function]
1517 pub fn inline_css(&self) -> Vc<bool> {
1518 Vc::cell(self.experimental.inline_css.unwrap_or(false))
1519 }
1520
1521 #[turbo_tasks::function]
1522 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1523 let options = &self.experimental.mdx_rs;
1524
1525 let options = match options {
1526 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1527 MdxTransformOptions {
1528 provider_import_source: Some(mdx_import_source_file()),
1529 ..Default::default()
1530 }
1531 .resolved_cell(),
1532 )),
1533 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1534 MdxTransformOptions {
1535 provider_import_source: Some(
1536 options
1537 .provider_import_source
1538 .clone()
1539 .unwrap_or(mdx_import_source_file()),
1540 ),
1541 ..options.clone()
1542 }
1543 .resolved_cell(),
1544 )),
1545 _ => OptionalMdxTransformOptions(None),
1546 };
1547
1548 options.cell()
1549 }
1550
1551 #[turbo_tasks::function]
1552 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1553 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1554 }
1555
1556 #[turbo_tasks::function]
1557 pub fn dist_dir(&self) -> Vc<RcStr> {
1558 Vc::cell(self.dist_dir.clone())
1559 }
1560 #[turbo_tasks::function]
1561 pub fn dist_dir_root(&self) -> Vc<RcStr> {
1562 Vc::cell(self.dist_dir_root.clone())
1563 }
1564
1565 #[turbo_tasks::function]
1566 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1567 if let Some(handlers) = &self.cache_handlers {
1568 Ok(Vc::cell(
1569 handlers
1570 .values()
1571 .map(|h| project_path.join(h))
1572 .collect::<Result<Vec<_>>>()?,
1573 ))
1574 } else {
1575 Ok(Vc::cell(vec![]))
1576 }
1577 }
1578
1579 #[turbo_tasks::function]
1580 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1581 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1582 }
1583
1584 #[turbo_tasks::function]
1585 pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1586 Vc::cell(self.experimental.sri.clone())
1587 }
1588
1589 #[turbo_tasks::function]
1590 pub fn experimental_server_actions(&self) -> Vc<OptionServerActions> {
1591 Vc::cell(match self.experimental.server_actions.as_ref() {
1592 Some(ServerActionsOrLegacyBool::ServerActionsConfig(server_actions)) => {
1593 Some(server_actions.clone())
1594 }
1595 Some(ServerActionsOrLegacyBool::LegacyBool(true)) => Some(ServerActions::default()),
1596 _ => None,
1597 })
1598 }
1599
1600 #[turbo_tasks::function]
1601 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1602 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1603 }
1604
1605 #[turbo_tasks::function]
1606 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1607 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1608 }
1609
1610 #[turbo_tasks::function]
1611 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1612 let options = &self.react_compiler;
1613
1614 let options = match options {
1615 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1616 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1617 }
1618 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1619 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1620 ),
1621 _ => OptionalReactCompilerOptions(None),
1622 };
1623
1624 options.cell()
1625 }
1626
1627 #[turbo_tasks::function]
1628 pub fn sass_config(&self) -> Vc<JsonValue> {
1629 Vc::cell(self.sass_options.clone().unwrap_or_default())
1630 }
1631
1632 #[turbo_tasks::function]
1633 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1634 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1635 }
1636
1637 #[turbo_tasks::function]
1638 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1639 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1640 }
1641
1642 #[turbo_tasks::function]
1645 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1646 let this = self.await?;
1647
1648 Ok(Vc::cell(
1649 format!(
1650 "{}/_next/",
1651 if let Some(asset_prefix) = &this.asset_prefix {
1652 asset_prefix
1653 } else {
1654 this.base_path.as_ref().map_or("", |b| b.as_str())
1655 }
1656 .trim_end_matches('/')
1657 )
1658 .into(),
1659 ))
1660 }
1661
1662 #[turbo_tasks::function]
1664 pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1665 let this = self.await?;
1666
1667 match &this.deployment_id {
1668 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1669 None => Ok(Vc::cell(None)),
1670 }
1671 }
1672
1673 #[turbo_tasks::function]
1674 pub fn enable_taint(&self) -> Vc<bool> {
1675 Vc::cell(self.experimental.taint.unwrap_or(false))
1676 }
1677
1678 #[turbo_tasks::function]
1679 pub fn enable_transition_indicator(&self) -> Vc<bool> {
1680 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1681 }
1682
1683 #[turbo_tasks::function]
1684 pub fn enable_cache_components(&self) -> Vc<bool> {
1685 Vc::cell(self.cache_components.unwrap_or(false))
1686 }
1687
1688 #[turbo_tasks::function]
1689 pub fn enable_use_cache(&self) -> Vc<bool> {
1690 Vc::cell(
1691 self.experimental
1692 .use_cache
1693 .unwrap_or(self.cache_components.unwrap_or(false)),
1697 )
1698 }
1699
1700 #[turbo_tasks::function]
1701 pub fn enable_root_params(&self) -> Vc<bool> {
1702 Vc::cell(
1703 self.experimental
1704 .root_params
1705 .unwrap_or(self.cache_components.unwrap_or(false)),
1707 )
1708 }
1709
1710 #[turbo_tasks::function]
1711 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1712 let mut cache_kinds = CacheKinds::default();
1713
1714 if let Some(handlers) = self.cache_handlers.as_ref() {
1715 cache_kinds.extend(handlers.keys().cloned());
1716 }
1717
1718 cache_kinds.cell()
1719 }
1720
1721 #[turbo_tasks::function]
1722 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1723 Vc::cell(
1724 self.experimental
1725 .optimize_package_imports
1726 .clone()
1727 .unwrap_or_default(),
1728 )
1729 }
1730
1731 #[turbo_tasks::function]
1732 pub fn tree_shaking_mode_for_foreign_code(
1733 &self,
1734 _is_development: bool,
1735 ) -> Vc<OptionTreeShaking> {
1736 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1737 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1738 Some(true) => Some(TreeShakingMode::ModuleFragments),
1739 None => Some(TreeShakingMode::ReexportsOnly),
1740 })
1741 .cell()
1742 }
1743
1744 #[turbo_tasks::function]
1745 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1746 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1747 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1748 Some(true) => Some(TreeShakingMode::ModuleFragments),
1749 None => Some(TreeShakingMode::ReexportsOnly),
1750 })
1751 .cell()
1752 }
1753
1754 #[turbo_tasks::function]
1755 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1756 Ok(Vc::cell(
1757 self.experimental
1758 .turbopack_remove_unused_exports
1759 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1760 ))
1761 }
1762
1763 #[turbo_tasks::function]
1764 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1765 Ok(match *mode.await? {
1766 NextMode::Development => ModuleIds::Named.cell(),
1768 NextMode::Build => self
1769 .experimental
1770 .turbopack_module_ids
1771 .unwrap_or(ModuleIds::Deterministic)
1772 .cell(),
1773 })
1774 }
1775
1776 #[turbo_tasks::function]
1777 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1778 let minify = self.experimental.turbopack_minify;
1779 Ok(Vc::cell(
1780 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1781 ))
1782 }
1783
1784 #[turbo_tasks::function]
1785 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1786 Ok(Vc::cell(match *mode.await? {
1787 NextMode::Development => false,
1789 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1790 }))
1791 }
1792
1793 #[turbo_tasks::function]
1794 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
1795 Vc::cell(
1796 self.experimental
1797 .turbopack_import_type_bytes
1798 .unwrap_or(false),
1799 )
1800 }
1801
1802 #[turbo_tasks::function]
1803 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1804 let source_maps = self.experimental.turbopack_source_maps;
1805 Ok(Vc::cell(source_maps.unwrap_or(match &*mode.await? {
1806 NextMode::Development => true,
1807 NextMode::Build => self.production_browser_source_maps,
1808 })))
1809 }
1810
1811 #[turbo_tasks::function]
1812 pub fn server_source_maps(&self) -> Result<Vc<bool>> {
1813 let source_maps = self.experimental.turbopack_source_maps;
1814 Ok(Vc::cell(source_maps.unwrap_or(true)))
1815 }
1816
1817 #[turbo_tasks::function]
1818 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
1819 Vc::cell(
1820 self.turbopack
1821 .as_ref()
1822 .and_then(|turbopack| turbopack.debug_ids)
1823 .unwrap_or(false),
1824 )
1825 }
1826
1827 #[turbo_tasks::function]
1828 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
1829 Ok(Vc::cell(
1830 self.typescript
1831 .tsconfig_path
1832 .as_ref()
1833 .map(|path| path.to_owned().into()),
1834 ))
1835 }
1836
1837 #[turbo_tasks::function]
1838 pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
1839 Vc::cell(self.cross_origin.clone())
1840 }
1841
1842 #[turbo_tasks::function]
1843 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
1844 Vc::cell(self.i18n.clone())
1845 }
1846
1847 #[turbo_tasks::function]
1848 pub fn output(&self) -> Vc<OptionOutputType> {
1849 Vc::cell(self.output.clone())
1850 }
1851
1852 #[turbo_tasks::function]
1853 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
1854 Vc::cell(self.output_file_tracing_includes.clone())
1855 }
1856
1857 #[turbo_tasks::function]
1858 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
1859 Vc::cell(self.output_file_tracing_excludes.clone())
1860 }
1861
1862 #[turbo_tasks::function]
1863 pub async fn fetch_client(
1864 &self,
1865 env: Vc<Box<dyn ProcessEnv>>,
1866 ) -> Result<Vc<FetchClientConfig>> {
1867 let use_system_tls_certs = env
1871 .read(rcstr!("NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS"))
1872 .await?
1873 .as_ref()
1874 .and_then(|env_value| {
1875 (!env_value.is_empty()).then(|| env_value == "1" || env_value == "true")
1877 })
1878 .or(self.experimental.turbopack_use_system_tls_certs)
1879 .unwrap_or(false);
1880 Ok(FetchClientConfig {
1881 tls_built_in_webpki_certs: !use_system_tls_certs,
1882 tls_built_in_native_certs: use_system_tls_certs,
1883 }
1884 .cell())
1885 }
1886}
1887
1888#[turbo_tasks::value(serialization = "custom", eq = "manual")]
1891#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1892#[serde(rename_all = "camelCase")]
1893pub struct JsConfig {
1894 compiler_options: Option<serde_json::Value>,
1895}
1896
1897#[turbo_tasks::value_impl]
1898impl JsConfig {
1899 #[turbo_tasks::function]
1900 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1901 let string = string.await?;
1902 let config: JsConfig = serde_json::from_str(&string)
1903 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1904
1905 Ok(config.cell())
1906 }
1907
1908 #[turbo_tasks::function]
1909 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
1910 Vc::cell(self.compiler_options.clone().unwrap_or_default())
1911 }
1912}
1913
1914#[cfg(test)]
1915mod tests {
1916 use super::*;
1917
1918 #[test]
1919 fn test_serde_rule_config_item_options() {
1920 let json_value = serde_json::json!({
1921 "loaders": [],
1922 "as": "*.js",
1923 "condition": {
1924 "all": [
1925 "production",
1926 {"not": "foreign"},
1927 {"any": [
1928 "browser",
1929 {
1930 "path": { "type": "glob", "value": "*.svg"},
1931 "content": {
1932 "source": "@someTag",
1933 "flags": ""
1934 }
1935 }
1936 ]},
1937 ],
1938 }
1939 });
1940
1941 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
1942
1943 assert_eq!(
1944 rule_config,
1945 RuleConfigItem {
1946 loaders: vec![],
1947 rename_as: Some(rcstr!("*.js")),
1948 condition: Some(ConfigConditionItem::All(
1949 [
1950 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
1951 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
1952 WebpackLoaderBuiltinCondition::Foreign
1953 ))),
1954 ConfigConditionItem::Any(
1955 vec![
1956 ConfigConditionItem::Builtin(
1957 WebpackLoaderBuiltinCondition::Browser
1958 ),
1959 ConfigConditionItem::Base {
1960 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
1961 content: Some(RegexComponents {
1962 source: rcstr!("@someTag"),
1963 flags: rcstr!(""),
1964 }),
1965 },
1966 ]
1967 .into(),
1968 ),
1969 ]
1970 .into(),
1971 )),
1972 }
1973 );
1974 }
1975}