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, 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_persistent_caching: Option<bool>,
1076 turbopack_source_maps: Option<bool>,
1077 turbopack_input_source_maps: Option<bool>,
1078 turbopack_tree_shaking: Option<bool>,
1079 turbopack_scope_hoisting: Option<bool>,
1080 turbopack_client_side_nested_async_chunking: Option<bool>,
1081 turbopack_server_side_nested_async_chunking: Option<bool>,
1082 turbopack_import_type_bytes: Option<bool>,
1083 #[serde(default)]
1085 turbopack_use_builtin_sass: Option<bool>,
1086 #[serde(default)]
1089 turbopack_use_builtin_babel: Option<bool>,
1090 global_not_found: Option<bool>,
1092 turbopack_remove_unused_imports: Option<bool>,
1094 turbopack_remove_unused_exports: Option<bool>,
1096 turbopack_infer_module_side_effects: Option<bool>,
1098 devtool_segment_explorer: Option<bool>,
1100}
1101
1102#[derive(
1103 Clone,
1104 Debug,
1105 PartialEq,
1106 Eq,
1107 Deserialize,
1108 TraceRawVcs,
1109 NonLocalValue,
1110 OperationValue,
1111 Encode,
1112 Decode,
1113)]
1114#[serde(rename_all = "camelCase")]
1115pub struct SubResourceIntegrity {
1116 pub algorithm: Option<RcStr>,
1117}
1118
1119#[derive(
1120 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1121)]
1122#[serde(untagged)]
1123pub enum ServerActionsOrLegacyBool {
1124 ServerActionsConfig(ServerActions),
1126
1127 LegacyBool(bool),
1130}
1131
1132#[derive(
1133 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1134)]
1135#[serde(rename_all = "kebab-case")]
1136pub enum EsmExternalsValue {
1137 Loose,
1138}
1139
1140#[derive(
1141 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1142)]
1143#[serde(untagged)]
1144pub enum EsmExternals {
1145 Loose(EsmExternalsValue),
1146 Bool(bool),
1147}
1148
1149#[test]
1151fn test_esm_externals_deserialization() {
1152 let json = serde_json::json!({
1153 "esmExternals": true
1154 });
1155 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1156 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1157
1158 let json = serde_json::json!({
1159 "esmExternals": "loose"
1160 });
1161 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1162 assert_eq!(
1163 config.esm_externals,
1164 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1165 );
1166}
1167
1168#[derive(
1169 Clone,
1170 Debug,
1171 Default,
1172 PartialEq,
1173 Eq,
1174 Deserialize,
1175 TraceRawVcs,
1176 NonLocalValue,
1177 OperationValue,
1178 Encode,
1179 Decode,
1180)]
1181#[serde(rename_all = "camelCase")]
1182pub struct ServerActions {
1183 pub body_size_limit: Option<SizeLimit>,
1185}
1186
1187#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1188#[serde(untagged)]
1189pub enum SizeLimit {
1190 Number(f64),
1191 WithUnit(String),
1192}
1193
1194impl PartialEq for SizeLimit {
1197 fn eq(&self, other: &Self) -> bool {
1198 match (self, other) {
1199 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1200 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1201 _ => false,
1202 }
1203 }
1204}
1205
1206impl Eq for SizeLimit {}
1207
1208#[derive(
1209 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1210)]
1211#[serde(rename_all = "kebab-case")]
1212pub enum MiddlewarePrefetchType {
1213 Strict,
1214 Flexible,
1215}
1216
1217#[derive(
1218 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1219)]
1220#[serde(untagged)]
1221pub enum EmotionTransformOptionsOrBoolean {
1222 Boolean(bool),
1223 Options(EmotionTransformConfig),
1224}
1225
1226impl EmotionTransformOptionsOrBoolean {
1227 pub fn is_enabled(&self) -> bool {
1228 match self {
1229 Self::Boolean(enabled) => *enabled,
1230 _ => true,
1231 }
1232 }
1233}
1234
1235#[derive(
1236 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1237)]
1238#[serde(untagged)]
1239pub enum StyledComponentsTransformOptionsOrBoolean {
1240 Boolean(bool),
1241 Options(StyledComponentsTransformConfig),
1242}
1243
1244impl StyledComponentsTransformOptionsOrBoolean {
1245 pub fn is_enabled(&self) -> bool {
1246 match self {
1247 Self::Boolean(enabled) => *enabled,
1248 _ => true,
1249 }
1250 }
1251}
1252
1253#[turbo_tasks::value(eq = "manual")]
1254#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1255#[serde(rename_all = "camelCase")]
1256pub struct CompilerConfig {
1257 pub react_remove_properties: Option<ReactRemoveProperties>,
1258 pub relay: Option<RelayConfig>,
1259 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1260 pub remove_console: Option<RemoveConsoleConfig>,
1261 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1262}
1263
1264#[derive(
1265 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1266)]
1267#[serde(untagged, rename_all = "camelCase")]
1268pub enum ReactRemoveProperties {
1269 Boolean(bool),
1270 Config { properties: Option<Vec<String>> },
1271}
1272
1273impl ReactRemoveProperties {
1274 pub fn is_enabled(&self) -> bool {
1275 match self {
1276 Self::Boolean(enabled) => *enabled,
1277 _ => true,
1278 }
1279 }
1280}
1281
1282#[derive(
1283 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1284)]
1285#[serde(untagged)]
1286pub enum RemoveConsoleConfig {
1287 Boolean(bool),
1288 Config { exclude: Option<Vec<String>> },
1289}
1290
1291impl RemoveConsoleConfig {
1292 pub fn is_enabled(&self) -> bool {
1293 match self {
1294 Self::Boolean(enabled) => *enabled,
1295 _ => true,
1296 }
1297 }
1298}
1299
1300#[turbo_tasks::value(transparent)]
1301pub struct ResolveExtensions(Option<Vec<RcStr>>);
1302
1303#[turbo_tasks::value(transparent)]
1304pub struct SwcPlugins(
1305 #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1306);
1307
1308#[turbo_tasks::value(transparent)]
1309pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1310
1311#[turbo_tasks::value(transparent)]
1312
1313pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1314
1315#[turbo_tasks::value(transparent)]
1316pub struct OptionFileSystemPath(Option<FileSystemPath>);
1317
1318#[turbo_tasks::value(transparent)]
1319pub struct OptionServerActions(Option<ServerActions>);
1320
1321#[turbo_tasks::value(transparent)]
1322pub struct OptionJsonValue(
1323 #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1324);
1325
1326fn turbopack_config_documentation_link() -> RcStr {
1327 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1328}
1329
1330#[turbo_tasks::value(shared)]
1331struct InvalidLoaderRuleRenameAsIssue {
1332 glob: RcStr,
1333 rename_as: RcStr,
1334 config_file_path: FileSystemPath,
1335}
1336
1337#[turbo_tasks::value_impl]
1338impl Issue for InvalidLoaderRuleRenameAsIssue {
1339 #[turbo_tasks::function]
1340 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1341 Ok(self.config_file_path.clone().cell())
1342 }
1343
1344 #[turbo_tasks::function]
1345 fn stage(&self) -> Vc<IssueStage> {
1346 IssueStage::Config.cell()
1347 }
1348
1349 #[turbo_tasks::function]
1350 async fn title(&self) -> Result<Vc<StyledString>> {
1351 Ok(
1352 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1353 .cell(),
1354 )
1355 }
1356
1357 #[turbo_tasks::function]
1358 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1359 Ok(Vc::cell(Some(
1360 StyledString::Text(RcStr::from(format!(
1361 "The extension {} contains a wildcard, but the `as` option does not: {}",
1362 self.glob, self.rename_as,
1363 )))
1364 .resolved_cell(),
1365 )))
1366 }
1367
1368 #[turbo_tasks::function]
1369 fn documentation_link(&self) -> Vc<RcStr> {
1370 Vc::cell(turbopack_config_documentation_link())
1371 }
1372}
1373
1374#[turbo_tasks::value(shared)]
1375struct InvalidLoaderRuleConditionIssue {
1376 error_string: RcStr,
1377 condition: ConfigConditionItem,
1378 config_file_path: FileSystemPath,
1379}
1380
1381#[turbo_tasks::value_impl]
1382impl Issue for InvalidLoaderRuleConditionIssue {
1383 #[turbo_tasks::function]
1384 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1385 Ok(self.await?.config_file_path.clone().cell())
1386 }
1387
1388 #[turbo_tasks::function]
1389 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1390 IssueStage::Config.cell()
1391 }
1392
1393 #[turbo_tasks::function]
1394 async fn title(&self) -> Result<Vc<StyledString>> {
1395 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1396 }
1397
1398 #[turbo_tasks::function]
1399 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1400 Ok(Vc::cell(Some(
1401 StyledString::Stack(vec![
1402 StyledString::Line(vec![
1403 StyledString::Text(rcstr!("Encountered the following error: ")),
1404 StyledString::Code(self.error_string.clone()),
1405 ]),
1406 StyledString::Text(rcstr!("While processing the condition:")),
1407 StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1408 ])
1409 .resolved_cell(),
1410 )))
1411 }
1412
1413 #[turbo_tasks::function]
1414 fn documentation_link(&self) -> Vc<RcStr> {
1415 Vc::cell(turbopack_config_documentation_link())
1416 }
1417}
1418
1419#[turbo_tasks::value_impl]
1420impl NextConfig {
1421 #[turbo_tasks::function]
1422 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1423 let string = string.await?;
1424 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1425 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1426 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1427 Ok(config.cell())
1428 }
1429
1430 #[turbo_tasks::function]
1431 pub async fn config_file_path(
1432 &self,
1433 project_path: FileSystemPath,
1434 ) -> Result<Vc<FileSystemPath>> {
1435 Ok(project_path.join(&self.config_file_name)?.cell())
1436 }
1437
1438 #[turbo_tasks::function]
1439 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1440 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1441 }
1442
1443 #[turbo_tasks::function]
1444 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1445 Vc::cell(self.react_production_profiling.unwrap_or_default())
1446 }
1447
1448 #[turbo_tasks::function]
1449 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1450 Vc::cell(
1451 self.server_external_packages
1452 .as_ref()
1453 .cloned()
1454 .unwrap_or_default(),
1455 )
1456 }
1457
1458 #[turbo_tasks::function]
1459 pub fn is_standalone(&self) -> Vc<bool> {
1460 Vc::cell(self.output == Some(OutputType::Standalone))
1461 }
1462
1463 #[turbo_tasks::function]
1464 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1465 Vc::cell(self.base_path.clone())
1466 }
1467
1468 #[turbo_tasks::function]
1469 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1470 if let Some(handler) = &self.cache_handler {
1471 Ok(Vc::cell(Some(project_path.join(handler)?)))
1472 } else {
1473 Ok(Vc::cell(None))
1474 }
1475 }
1476
1477 #[turbo_tasks::function]
1478 pub fn compiler(&self) -> Vc<CompilerConfig> {
1479 self.compiler.clone().unwrap_or_default().cell()
1480 }
1481
1482 #[turbo_tasks::function]
1483 pub fn env(&self) -> Vc<EnvMap> {
1484 let env = self
1488 .env
1489 .iter()
1490 .map(|(k, v)| {
1491 (
1492 k.as_str().into(),
1493 if let JsonValue::String(s) = v {
1494 s.as_str().into()
1496 } else {
1497 v.to_string().into()
1498 },
1499 )
1500 })
1501 .collect();
1502
1503 Vc::cell(env)
1504 }
1505
1506 #[turbo_tasks::function]
1507 pub fn image_config(&self) -> Vc<ImageConfig> {
1508 self.images.clone().cell()
1509 }
1510
1511 #[turbo_tasks::function]
1512 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1513 let mut extensions = self.page_extensions.clone();
1517 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1518 Vc::cell(extensions)
1519 }
1520
1521 #[turbo_tasks::function]
1522 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1523 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1524 }
1525
1526 #[turbo_tasks::function]
1527 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1528 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1529 }
1530
1531 #[turbo_tasks::function]
1532 pub async fn webpack_rules(
1533 self: Vc<Self>,
1534 project_path: FileSystemPath,
1535 ) -> Result<Vc<WebpackRules>> {
1536 let this = self.await?;
1537 let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1538 return Ok(Vc::cell(Vec::new()));
1539 };
1540 if turbo_rules.is_empty() {
1541 return Ok(Vc::cell(Vec::new()));
1542 }
1543 let mut rules = Vec::new();
1544 for (glob, rule_collection) in turbo_rules.iter() {
1545 fn transform_loaders(
1546 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1547 ) -> ResolvedVc<WebpackLoaderItems> {
1548 ResolvedVc::cell(
1549 loaders
1550 .map(|item| match item {
1551 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1552 loader: name.clone(),
1553 options: Default::default(),
1554 },
1555 LoaderItem::LoaderOptions(options) => options.clone(),
1556 })
1557 .collect(),
1558 )
1559 }
1560 for item in &rule_collection.0 {
1561 match item {
1562 RuleConfigCollectionItem::Shorthand(loaders) => {
1563 rules.push((
1564 glob.clone(),
1565 LoaderRuleItem {
1566 loaders: transform_loaders(&mut [loaders].into_iter()),
1567 rename_as: None,
1568 condition: None,
1569 module_type: None,
1570 },
1571 ));
1572 }
1573 RuleConfigCollectionItem::Full(RuleConfigItem {
1574 loaders,
1575 rename_as,
1576 condition,
1577 module_type,
1578 }) => {
1579 if glob.contains("*")
1583 && let Some(rename_as) = rename_as.as_ref()
1584 && !rename_as.contains("*")
1585 {
1586 InvalidLoaderRuleRenameAsIssue {
1587 glob: glob.clone(),
1588 config_file_path: self
1589 .config_file_path(project_path.clone())
1590 .owned()
1591 .await?,
1592 rename_as: rename_as.clone(),
1593 }
1594 .resolved_cell()
1595 .emit();
1596 }
1597
1598 let condition = if let Some(condition) = condition {
1601 match ConditionItem::try_from(condition.clone()) {
1602 Ok(cond) => Some(cond),
1603 Err(err) => {
1604 InvalidLoaderRuleConditionIssue {
1605 error_string: RcStr::from(err.to_string()),
1606 condition: condition.clone(),
1607 config_file_path: self
1608 .config_file_path(project_path.clone())
1609 .owned()
1610 .await?,
1611 }
1612 .resolved_cell()
1613 .emit();
1614 None
1615 }
1616 }
1617 } else {
1618 None
1619 };
1620 rules.push((
1621 glob.clone(),
1622 LoaderRuleItem {
1623 loaders: transform_loaders(&mut loaders.iter()),
1624 rename_as: rename_as.clone(),
1625 condition,
1626 module_type: module_type.clone(),
1627 },
1628 ));
1629 }
1630 }
1631 }
1632 }
1633 Ok(Vc::cell(rules))
1634 }
1635
1636 #[turbo_tasks::function]
1637 pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1638 Ok(Vc::cell(
1639 self.experimental
1640 .turbopack_persistent_caching
1641 .unwrap_or_default(),
1642 ))
1643 }
1644
1645 #[turbo_tasks::function]
1646 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1647 let Some(resolve_alias) = self
1648 .turbopack
1649 .as_ref()
1650 .and_then(|t| t.resolve_alias.as_ref())
1651 else {
1652 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1653 };
1654 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1655 Ok(alias_map.cell())
1656 }
1657
1658 #[turbo_tasks::function]
1659 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1660 let Some(resolve_extensions) = self
1661 .turbopack
1662 .as_ref()
1663 .and_then(|t| t.resolve_extensions.as_ref())
1664 else {
1665 return Vc::cell(None);
1666 };
1667 Vc::cell(Some(resolve_extensions.clone()))
1668 }
1669
1670 #[turbo_tasks::function]
1671 pub fn import_externals(&self) -> Result<Vc<bool>> {
1672 Ok(Vc::cell(match self.experimental.esm_externals {
1673 Some(EsmExternals::Bool(b)) => b,
1674 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1675 None => true,
1676 }))
1677 }
1678
1679 #[turbo_tasks::function]
1680 pub fn inline_css(&self) -> Vc<bool> {
1681 Vc::cell(self.experimental.inline_css.unwrap_or(false))
1682 }
1683
1684 #[turbo_tasks::function]
1685 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1686 let options = &self.experimental.mdx_rs;
1687
1688 let options = match options {
1689 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1690 MdxTransformOptions {
1691 provider_import_source: Some(mdx_import_source_file()),
1692 ..Default::default()
1693 }
1694 .resolved_cell(),
1695 )),
1696 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1697 MdxTransformOptions {
1698 provider_import_source: Some(
1699 options
1700 .provider_import_source
1701 .clone()
1702 .unwrap_or(mdx_import_source_file()),
1703 ),
1704 ..options.clone()
1705 }
1706 .resolved_cell(),
1707 )),
1708 _ => OptionalMdxTransformOptions(None),
1709 };
1710
1711 options.cell()
1712 }
1713
1714 #[turbo_tasks::function]
1715 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1716 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1717 }
1718
1719 #[turbo_tasks::function]
1720 pub fn dist_dir(&self) -> Vc<RcStr> {
1721 Vc::cell(self.dist_dir.clone())
1722 }
1723 #[turbo_tasks::function]
1724 pub fn dist_dir_root(&self) -> Vc<RcStr> {
1725 Vc::cell(self.dist_dir_root.clone())
1726 }
1727
1728 #[turbo_tasks::function]
1729 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1730 if let Some(handlers) = &self.cache_handlers {
1731 Ok(Vc::cell(
1732 handlers
1733 .values()
1734 .map(|h| project_path.join(h))
1735 .collect::<Result<Vec<_>>>()?,
1736 ))
1737 } else {
1738 Ok(Vc::cell(vec![]))
1739 }
1740 }
1741
1742 #[turbo_tasks::function]
1743 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1744 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1745 }
1746
1747 #[turbo_tasks::function]
1754 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1755 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1756 }
1757
1758 #[turbo_tasks::function]
1759 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1760 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1761 }
1762
1763 #[turbo_tasks::function]
1764 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1765 let options = &self.react_compiler;
1766
1767 let options = match options {
1768 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1769 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1770 }
1771 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1772 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1773 ),
1774 _ => OptionalReactCompilerOptions(None),
1775 };
1776
1777 options.cell()
1778 }
1779
1780 #[turbo_tasks::function]
1781 pub fn sass_config(&self) -> Vc<JsonValue> {
1782 Vc::cell(self.sass_options.clone().unwrap_or_default())
1783 }
1784
1785 #[turbo_tasks::function]
1786 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1787 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1788 }
1789
1790 #[turbo_tasks::function]
1791 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1792 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1793 }
1794
1795 #[turbo_tasks::function]
1798 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1799 let this = self.await?;
1800
1801 Ok(Vc::cell(
1802 format!(
1803 "{}/_next/",
1804 if let Some(asset_prefix) = &this.asset_prefix {
1805 asset_prefix
1806 } else {
1807 this.base_path.as_ref().map_or("", |b| b.as_str())
1808 }
1809 .trim_end_matches('/')
1810 )
1811 .into(),
1812 ))
1813 }
1814
1815 #[turbo_tasks::function]
1817 pub async fn asset_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1818 let this = self.await?;
1819
1820 match &this.deployment_id {
1821 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1822 None => Ok(Vc::cell(None)),
1823 }
1824 }
1825
1826 #[turbo_tasks::function]
1827 pub fn enable_taint(&self) -> Vc<bool> {
1828 Vc::cell(self.experimental.taint.unwrap_or(false))
1829 }
1830
1831 #[turbo_tasks::function]
1832 pub fn enable_transition_indicator(&self) -> Vc<bool> {
1833 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1834 }
1835
1836 #[turbo_tasks::function]
1837 pub fn enable_gesture_transition(&self) -> Vc<bool> {
1838 Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
1839 }
1840
1841 #[turbo_tasks::function]
1842 pub fn enable_cache_components(&self) -> Vc<bool> {
1843 Vc::cell(self.cache_components.unwrap_or(false))
1844 }
1845
1846 #[turbo_tasks::function]
1847 pub fn enable_use_cache(&self) -> Vc<bool> {
1848 Vc::cell(
1849 self.experimental
1850 .use_cache
1851 .unwrap_or(self.cache_components.unwrap_or(false)),
1855 )
1856 }
1857
1858 #[turbo_tasks::function]
1859 pub fn enable_root_params(&self) -> Vc<bool> {
1860 Vc::cell(
1861 self.experimental
1862 .root_params
1863 .unwrap_or(self.cache_components.unwrap_or(false)),
1865 )
1866 }
1867
1868 #[turbo_tasks::function]
1869 pub fn runtime_server_deployment_id_available(&self) -> Vc<bool> {
1870 Vc::cell(
1871 self.experimental
1872 .runtime_server_deployment_id
1873 .unwrap_or(false),
1874 )
1875 }
1876
1877 #[turbo_tasks::function]
1878 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1879 let mut cache_kinds = CacheKinds::default();
1880
1881 if let Some(handlers) = self.cache_handlers.as_ref() {
1882 cache_kinds.extend(handlers.keys().cloned());
1883 }
1884
1885 cache_kinds.cell()
1886 }
1887
1888 #[turbo_tasks::function]
1889 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1890 Vc::cell(
1891 self.experimental
1892 .optimize_package_imports
1893 .clone()
1894 .unwrap_or_default(),
1895 )
1896 }
1897
1898 #[turbo_tasks::function]
1899 pub fn tree_shaking_mode_for_foreign_code(
1900 &self,
1901 _is_development: bool,
1902 ) -> Vc<OptionTreeShaking> {
1903 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1904 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1905 Some(true) => Some(TreeShakingMode::ModuleFragments),
1906 None => Some(TreeShakingMode::ReexportsOnly),
1907 })
1908 .cell()
1909 }
1910
1911 #[turbo_tasks::function]
1912 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1913 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1914 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1915 Some(true) => Some(TreeShakingMode::ModuleFragments),
1916 None => Some(TreeShakingMode::ReexportsOnly),
1917 })
1918 .cell()
1919 }
1920
1921 #[turbo_tasks::function]
1922 pub async fn turbopack_remove_unused_imports(
1923 self: Vc<Self>,
1924 mode: Vc<NextMode>,
1925 ) -> Result<Vc<bool>> {
1926 let remove_unused_imports = self
1927 .await?
1928 .experimental
1929 .turbopack_remove_unused_imports
1930 .unwrap_or(matches!(*mode.await?, NextMode::Build));
1931
1932 if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
1933 bail!(
1934 "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
1935 enabling `experimental.turbopackRemoveUnusedExports`"
1936 );
1937 }
1938
1939 Ok(Vc::cell(remove_unused_imports))
1940 }
1941
1942 #[turbo_tasks::function]
1943 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1944 Ok(Vc::cell(
1945 self.experimental
1946 .turbopack_remove_unused_exports
1947 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1948 ))
1949 }
1950
1951 #[turbo_tasks::function]
1952 pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
1953 Vc::cell(
1954 self.experimental
1955 .turbopack_infer_module_side_effects
1956 .unwrap_or(true),
1957 )
1958 }
1959
1960 #[turbo_tasks::function]
1961 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1962 Ok(match *mode.await? {
1963 NextMode::Development => ModuleIds::Named.cell(),
1965 NextMode::Build => self
1966 .experimental
1967 .turbopack_module_ids
1968 .unwrap_or(ModuleIds::Deterministic)
1969 .cell(),
1970 })
1971 }
1972
1973 #[turbo_tasks::function]
1974 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1975 let minify = self.experimental.turbopack_minify;
1976 Ok(Vc::cell(
1977 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1978 ))
1979 }
1980
1981 #[turbo_tasks::function]
1982 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1983 Ok(Vc::cell(match *mode.await? {
1984 NextMode::Development => false,
1986 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1987 }))
1988 }
1989
1990 #[turbo_tasks::function]
1991 pub async fn turbo_nested_async_chunking(
1992 &self,
1993 mode: Vc<NextMode>,
1994 client_side: bool,
1995 ) -> Result<Vc<bool>> {
1996 let option = if client_side {
1997 self.experimental
1998 .turbopack_client_side_nested_async_chunking
1999 } else {
2000 self.experimental
2001 .turbopack_server_side_nested_async_chunking
2002 };
2003 Ok(Vc::cell(if let Some(value) = option {
2004 value
2005 } else {
2006 match *mode.await? {
2007 NextMode::Development => false,
2008 NextMode::Build => client_side,
2009 }
2010 }))
2011 }
2012
2013 #[turbo_tasks::function]
2014 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2015 Vc::cell(
2016 self.experimental
2017 .turbopack_import_type_bytes
2018 .unwrap_or(false),
2019 )
2020 }
2021
2022 #[turbo_tasks::function]
2023 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2024 let input_source_maps = self
2025 .experimental
2026 .turbopack_input_source_maps
2027 .unwrap_or(true);
2028 let source_maps = self
2029 .experimental
2030 .turbopack_source_maps
2031 .unwrap_or(match &*mode.await? {
2032 NextMode::Development => true,
2033 NextMode::Build => self.production_browser_source_maps,
2034 });
2035 Ok(match (source_maps, input_source_maps) {
2036 (true, true) => SourceMapsType::Full,
2037 (true, false) => SourceMapsType::Partial,
2038 (false, _) => SourceMapsType::None,
2039 }
2040 .cell())
2041 }
2042
2043 #[turbo_tasks::function]
2044 pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2045 let input_source_maps = self
2046 .experimental
2047 .turbopack_input_source_maps
2048 .unwrap_or(true);
2049 let source_maps = self
2050 .experimental
2051 .turbopack_source_maps
2052 .or(self.experimental.server_source_maps)
2053 .unwrap_or(true);
2054 Ok(match (source_maps, input_source_maps) {
2055 (true, true) => SourceMapsType::Full,
2056 (true, false) => SourceMapsType::Partial,
2057 (false, _) => SourceMapsType::None,
2058 }
2059 .cell())
2060 }
2061
2062 #[turbo_tasks::function]
2063 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2064 Vc::cell(
2065 self.turbopack
2066 .as_ref()
2067 .and_then(|turbopack| turbopack.debug_ids)
2068 .unwrap_or(false),
2069 )
2070 }
2071
2072 #[turbo_tasks::function]
2073 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2074 Ok(Vc::cell(
2075 self.typescript
2076 .tsconfig_path
2077 .as_ref()
2078 .map(|path| path.to_owned().into()),
2079 ))
2080 }
2081
2082 #[turbo_tasks::function]
2083 pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2084 Vc::cell(self.cross_origin.clone())
2085 }
2086
2087 #[turbo_tasks::function]
2088 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2089 Vc::cell(self.i18n.clone())
2090 }
2091
2092 #[turbo_tasks::function]
2093 pub fn output(&self) -> Vc<OptionOutputType> {
2094 Vc::cell(self.output.clone())
2095 }
2096
2097 #[turbo_tasks::function]
2098 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2099 Vc::cell(self.output_file_tracing_includes.clone())
2100 }
2101
2102 #[turbo_tasks::function]
2103 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2104 Vc::cell(self.output_file_tracing_excludes.clone())
2105 }
2106
2107 #[turbo_tasks::function]
2108 pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2109 FetchClientConfig::default().cell()
2110 }
2111}
2112
2113#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2116#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2117#[serde(rename_all = "camelCase")]
2118pub struct JsConfig {
2119 #[bincode(with = "turbo_bincode::serde_self_describing")]
2120 compiler_options: Option<serde_json::Value>,
2121}
2122
2123#[turbo_tasks::value_impl]
2124impl JsConfig {
2125 #[turbo_tasks::function]
2126 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2127 let string = string.await?;
2128 let config: JsConfig = serde_json::from_str(&string)
2129 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2130
2131 Ok(config.cell())
2132 }
2133
2134 #[turbo_tasks::function]
2135 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2136 Vc::cell(self.compiler_options.clone().unwrap_or_default())
2137 }
2138}
2139
2140#[cfg(test)]
2141mod tests {
2142 use super::*;
2143
2144 #[test]
2145 fn test_serde_rule_config_item_options() {
2146 let json_value = serde_json::json!({
2147 "loaders": [],
2148 "as": "*.js",
2149 "condition": {
2150 "all": [
2151 "production",
2152 {"not": "foreign"},
2153 {"any": [
2154 "browser",
2155 {
2156 "path": { "type": "glob", "value": "*.svg"},
2157 "query": {
2158 "type": "regex",
2159 "value": {
2160 "source": "@someQuery",
2161 "flags": ""
2162 }
2163 },
2164 "content": {
2165 "source": "@someTag",
2166 "flags": ""
2167 }
2168 }
2169 ]},
2170 ],
2171 }
2172 });
2173
2174 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2175
2176 assert_eq!(
2177 rule_config,
2178 RuleConfigItem {
2179 loaders: vec![],
2180 rename_as: Some(rcstr!("*.js")),
2181 module_type: None,
2182 condition: Some(ConfigConditionItem::All(
2183 [
2184 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2185 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2186 WebpackLoaderBuiltinCondition::Foreign
2187 ))),
2188 ConfigConditionItem::Any(
2189 vec![
2190 ConfigConditionItem::Builtin(
2191 WebpackLoaderBuiltinCondition::Browser
2192 ),
2193 ConfigConditionItem::Base {
2194 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2195 content: Some(RegexComponents {
2196 source: rcstr!("@someTag"),
2197 flags: rcstr!(""),
2198 }),
2199 query: Some(ConfigConditionQuery::Regex(RegexComponents {
2200 source: rcstr!("@someQuery"),
2201 flags: rcstr!(""),
2202 })),
2203 content_type: None,
2204 },
2205 ]
2206 .into(),
2207 ),
2208 ]
2209 .into(),
2210 )),
2211 }
2212 );
2213 }
2214}