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