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 ConditionItem, ConditionPath, ConditionQuery, LoaderRuleItem, WebpackRules,
18 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 Deserialize,
714 Clone,
715 PartialEq,
716 Eq,
717 Debug,
718 TraceRawVcs,
719 NonLocalValue,
720 OperationValue,
721 Encode,
722 Decode,
723)]
724#[serde(deny_unknown_fields)]
727pub enum ConfigConditionItem {
728 #[serde(rename = "all")]
729 All(Box<[ConfigConditionItem]>),
730 #[serde(rename = "any")]
731 Any(Box<[ConfigConditionItem]>),
732 #[serde(rename = "not")]
733 Not(Box<ConfigConditionItem>),
734 #[serde(untagged)]
735 Builtin(WebpackLoaderBuiltinCondition),
736 #[serde(untagged)]
737 Base {
738 #[serde(default)]
739 path: Option<ConfigConditionPath>,
740 #[serde(default)]
741 content: Option<RegexComponents>,
742 #[serde(default)]
743 query: Option<ConfigConditionQuery>,
744 },
745}
746
747impl TryFrom<ConfigConditionItem> for ConditionItem {
748 type Error = anyhow::Error;
749
750 fn try_from(config: ConfigConditionItem) -> Result<Self> {
751 let try_from_vec = |conds: Box<[_]>| {
752 conds
753 .into_iter()
754 .map(ConditionItem::try_from)
755 .collect::<Result<_>>()
756 };
757 Ok(match config {
758 ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
759 ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
760 ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
761 ConfigConditionItem::Builtin(cond) => {
762 ConditionItem::Builtin(RcStr::from(cond.as_str()))
763 }
764 ConfigConditionItem::Base {
765 path,
766 content,
767 query,
768 } => ConditionItem::Base {
769 path: path.map(ConditionPath::try_from).transpose()?,
770 content: content
771 .map(EsRegex::try_from)
772 .transpose()?
773 .map(EsRegex::resolved_cell),
774 query: query.map(ConditionQuery::try_from).transpose()?,
775 },
776 })
777 }
778}
779
780#[derive(
781 Clone,
782 Debug,
783 PartialEq,
784 Eq,
785 Deserialize,
786 TraceRawVcs,
787 NonLocalValue,
788 OperationValue,
789 Encode,
790 Decode,
791)]
792#[serde(rename_all = "camelCase")]
793pub struct RuleConfigItem {
794 pub loaders: Vec<LoaderItem>,
795 #[serde(default, alias = "as")]
796 pub rename_as: Option<RcStr>,
797 #[serde(default)]
798 pub condition: Option<ConfigConditionItem>,
799}
800
801#[derive(
802 Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
803)]
804pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
805
806impl<'de> Deserialize<'de> for RuleConfigCollection {
807 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
808 where
809 D: Deserializer<'de>,
810 {
811 match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
812 deserializer,
813 )? {
814 Either::Left(collection) => Ok(RuleConfigCollection(collection)),
815 Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
816 item,
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(untagged)]
835pub enum RuleConfigCollectionItem {
836 Shorthand(LoaderItem),
837 Full(RuleConfigItem),
838}
839
840#[derive(
841 Clone,
842 Debug,
843 PartialEq,
844 Eq,
845 Deserialize,
846 TraceRawVcs,
847 NonLocalValue,
848 OperationValue,
849 Encode,
850 Decode,
851)]
852#[serde(untagged)]
853pub enum LoaderItem {
854 LoaderName(RcStr),
855 LoaderOptions(WebpackLoaderItem),
856}
857
858#[turbo_tasks::value(operation)]
859#[derive(Copy, Clone, Debug, Deserialize)]
860#[serde(rename_all = "camelCase")]
861pub enum ModuleIds {
862 Named,
863 Deterministic,
864}
865
866#[turbo_tasks::value(transparent)]
867pub struct OptionModuleIds(pub Option<ModuleIds>);
868
869#[derive(
870 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
871)]
872#[serde(untagged)]
873pub enum MdxRsOptions {
874 Boolean(bool),
875 Option(MdxTransformOptions),
876}
877
878#[turbo_tasks::value(shared, operation)]
879#[derive(Clone, Debug, Default, Serialize, Deserialize)]
880#[serde(rename_all = "camelCase")]
881pub enum ReactCompilerCompilationMode {
882 #[default]
883 Infer,
884 Annotation,
885 All,
886}
887
888#[turbo_tasks::value(shared, operation)]
889#[derive(Clone, Debug, Default, Serialize, Deserialize)]
890#[serde(rename_all = "snake_case")]
891pub enum ReactCompilerPanicThreshold {
892 #[default]
893 None,
894 CriticalErrors,
895 AllErrors,
896}
897
898#[turbo_tasks::value(shared, operation)]
901#[derive(Clone, Debug, Default, Serialize, Deserialize)]
902#[serde(rename_all = "camelCase")]
903pub struct ReactCompilerOptions {
904 #[serde(default)]
905 pub compilation_mode: ReactCompilerCompilationMode,
906 #[serde(default)]
907 pub panic_threshold: ReactCompilerPanicThreshold,
908}
909
910#[derive(
911 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
912)]
913#[serde(untagged)]
914pub enum ReactCompilerOptionsOrBoolean {
915 Boolean(bool),
916 Option(ReactCompilerOptions),
917}
918
919#[turbo_tasks::value(transparent)]
920pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
921
922#[derive(
923 Clone,
924 Debug,
925 Default,
926 PartialEq,
927 Deserialize,
928 TraceRawVcs,
929 ValueDebugFormat,
930 NonLocalValue,
931 OperationValue,
932 Encode,
933 Decode,
934)]
935#[serde(rename_all = "camelCase")]
936pub struct ExperimentalConfig {
937 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
940 client_router_filter: Option<bool>,
941 client_router_filter_allowed_rate: Option<f64>,
944 client_router_filter_redirects: Option<bool>,
945 fetch_cache_key_prefix: Option<RcStr>,
946 isr_flush_to_disk: Option<bool>,
947 mdx_rs: Option<MdxRsOptions>,
950 strict_next_head: Option<bool>,
951 #[bincode(with = "turbo_bincode::serde_self_describing")]
952 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
953 external_middleware_rewrites_resolve: Option<bool>,
954 scroll_restoration: Option<bool>,
955 manual_client_base_path: Option<bool>,
956 optimistic_client_cache: Option<bool>,
957 middleware_prefetch: Option<MiddlewarePrefetchType>,
958 #[bincode(with = "turbo_bincode::serde_self_describing")]
961 optimize_css: Option<serde_json::Value>,
962 next_script_workers: Option<bool>,
963 web_vitals_attribution: Option<Vec<RcStr>>,
964 server_actions: Option<ServerActionsOrLegacyBool>,
965 sri: Option<SubResourceIntegrity>,
966 cache_components: Option<bool>,
969 use_cache: Option<bool>,
970 root_params: Option<bool>,
971 adjust_font_fallbacks: Option<bool>,
975 adjust_font_fallbacks_with_size_adjust: Option<bool>,
976 after: Option<bool>,
977 app_document_preloading: Option<bool>,
978 case_sensitive_routes: Option<bool>,
979 cpus: Option<f64>,
980 cra_compat: Option<bool>,
981 disable_optimized_loading: Option<bool>,
982 disable_postcss_preset_env: Option<bool>,
983 esm_externals: Option<EsmExternals>,
984 #[bincode(with = "turbo_bincode::serde_self_describing")]
985 extension_alias: Option<serde_json::Value>,
986 external_dir: Option<bool>,
987 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
992 fully_specified: Option<bool>,
993 gzip_size: Option<bool>,
994
995 pub inline_css: Option<bool>,
996 instrumentation_hook: Option<bool>,
997 client_trace_metadata: Option<Vec<String>>,
998 large_page_data_bytes: Option<f64>,
999 #[bincode(with = "turbo_bincode::serde_self_describing")]
1000 logging: Option<serde_json::Value>,
1001 memory_based_workers_count: Option<bool>,
1002 optimize_server_react: Option<bool>,
1004 optimize_package_imports: Option<Vec<RcStr>>,
1007 taint: Option<bool>,
1008 proxy_timeout: Option<f64>,
1009 server_minification: Option<bool>,
1011 server_source_maps: Option<bool>,
1013 swc_trace_profiling: Option<bool>,
1014 transition_indicator: Option<bool>,
1015 trust_host_header: Option<bool>,
1017
1018 #[bincode(with = "turbo_bincode::serde_self_describing")]
1019 url_imports: Option<serde_json::Value>,
1020 webpack_build_worker: Option<bool>,
1023 worker_threads: Option<bool>,
1024
1025 turbopack_minify: Option<bool>,
1026 turbopack_module_ids: Option<ModuleIds>,
1027 turbopack_persistent_caching: Option<bool>,
1028 turbopack_source_maps: Option<bool>,
1029 turbopack_input_source_maps: Option<bool>,
1030 turbopack_tree_shaking: Option<bool>,
1031 turbopack_scope_hoisting: Option<bool>,
1032 turbopack_client_side_nested_async_chunking: Option<bool>,
1033 turbopack_server_side_nested_async_chunking: Option<bool>,
1034 turbopack_import_type_bytes: Option<bool>,
1035 #[serde(default)]
1037 turbopack_use_builtin_sass: Option<bool>,
1038 #[serde(default)]
1041 turbopack_use_builtin_babel: Option<bool>,
1042 global_not_found: Option<bool>,
1044 turbopack_remove_unused_imports: Option<bool>,
1046 turbopack_remove_unused_exports: Option<bool>,
1048 turbopack_infer_module_side_effects: Option<bool>,
1050 devtool_segment_explorer: Option<bool>,
1052}
1053
1054#[derive(
1055 Clone,
1056 Debug,
1057 PartialEq,
1058 Eq,
1059 Deserialize,
1060 TraceRawVcs,
1061 NonLocalValue,
1062 OperationValue,
1063 Encode,
1064 Decode,
1065)]
1066#[serde(rename_all = "camelCase")]
1067pub struct SubResourceIntegrity {
1068 pub algorithm: Option<RcStr>,
1069}
1070
1071#[derive(
1072 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1073)]
1074#[serde(untagged)]
1075pub enum ServerActionsOrLegacyBool {
1076 ServerActionsConfig(ServerActions),
1078
1079 LegacyBool(bool),
1082}
1083
1084#[derive(
1085 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1086)]
1087#[serde(rename_all = "kebab-case")]
1088pub enum EsmExternalsValue {
1089 Loose,
1090}
1091
1092#[derive(
1093 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1094)]
1095#[serde(untagged)]
1096pub enum EsmExternals {
1097 Loose(EsmExternalsValue),
1098 Bool(bool),
1099}
1100
1101#[test]
1103fn test_esm_externals_deserialization() {
1104 let json = serde_json::json!({
1105 "esmExternals": true
1106 });
1107 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1108 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1109
1110 let json = serde_json::json!({
1111 "esmExternals": "loose"
1112 });
1113 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1114 assert_eq!(
1115 config.esm_externals,
1116 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1117 );
1118}
1119
1120#[derive(
1121 Clone,
1122 Debug,
1123 Default,
1124 PartialEq,
1125 Eq,
1126 Deserialize,
1127 TraceRawVcs,
1128 NonLocalValue,
1129 OperationValue,
1130 Encode,
1131 Decode,
1132)]
1133#[serde(rename_all = "camelCase")]
1134pub struct ServerActions {
1135 pub body_size_limit: Option<SizeLimit>,
1137}
1138
1139#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1140#[serde(untagged)]
1141pub enum SizeLimit {
1142 Number(f64),
1143 WithUnit(String),
1144}
1145
1146impl PartialEq for SizeLimit {
1149 fn eq(&self, other: &Self) -> bool {
1150 match (self, other) {
1151 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1152 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1153 _ => false,
1154 }
1155 }
1156}
1157
1158impl Eq for SizeLimit {}
1159
1160#[derive(
1161 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1162)]
1163#[serde(rename_all = "kebab-case")]
1164pub enum MiddlewarePrefetchType {
1165 Strict,
1166 Flexible,
1167}
1168
1169#[derive(
1170 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1171)]
1172#[serde(untagged)]
1173pub enum EmotionTransformOptionsOrBoolean {
1174 Boolean(bool),
1175 Options(EmotionTransformConfig),
1176}
1177
1178impl EmotionTransformOptionsOrBoolean {
1179 pub fn is_enabled(&self) -> bool {
1180 match self {
1181 Self::Boolean(enabled) => *enabled,
1182 _ => true,
1183 }
1184 }
1185}
1186
1187#[derive(
1188 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1189)]
1190#[serde(untagged)]
1191pub enum StyledComponentsTransformOptionsOrBoolean {
1192 Boolean(bool),
1193 Options(StyledComponentsTransformConfig),
1194}
1195
1196impl StyledComponentsTransformOptionsOrBoolean {
1197 pub fn is_enabled(&self) -> bool {
1198 match self {
1199 Self::Boolean(enabled) => *enabled,
1200 _ => true,
1201 }
1202 }
1203}
1204
1205#[turbo_tasks::value(eq = "manual")]
1206#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1207#[serde(rename_all = "camelCase")]
1208pub struct CompilerConfig {
1209 pub react_remove_properties: Option<ReactRemoveProperties>,
1210 pub relay: Option<RelayConfig>,
1211 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1212 pub remove_console: Option<RemoveConsoleConfig>,
1213 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1214}
1215
1216#[derive(
1217 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1218)]
1219#[serde(untagged, rename_all = "camelCase")]
1220pub enum ReactRemoveProperties {
1221 Boolean(bool),
1222 Config { properties: Option<Vec<String>> },
1223}
1224
1225impl ReactRemoveProperties {
1226 pub fn is_enabled(&self) -> bool {
1227 match self {
1228 Self::Boolean(enabled) => *enabled,
1229 _ => true,
1230 }
1231 }
1232}
1233
1234#[derive(
1235 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1236)]
1237#[serde(untagged)]
1238pub enum RemoveConsoleConfig {
1239 Boolean(bool),
1240 Config { exclude: Option<Vec<String>> },
1241}
1242
1243impl RemoveConsoleConfig {
1244 pub fn is_enabled(&self) -> bool {
1245 match self {
1246 Self::Boolean(enabled) => *enabled,
1247 _ => true,
1248 }
1249 }
1250}
1251
1252#[turbo_tasks::value(transparent)]
1253pub struct ResolveExtensions(Option<Vec<RcStr>>);
1254
1255#[turbo_tasks::value(transparent)]
1256pub struct SwcPlugins(
1257 #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1258);
1259
1260#[turbo_tasks::value(transparent)]
1261pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1262
1263#[turbo_tasks::value(transparent)]
1264
1265pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1266
1267#[turbo_tasks::value(transparent)]
1268pub struct OptionFileSystemPath(Option<FileSystemPath>);
1269
1270#[turbo_tasks::value(transparent)]
1271pub struct OptionServerActions(Option<ServerActions>);
1272
1273#[turbo_tasks::value(transparent)]
1274pub struct OptionJsonValue(
1275 #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1276);
1277
1278fn turbopack_config_documentation_link() -> RcStr {
1279 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1280}
1281
1282#[turbo_tasks::value(shared)]
1283struct InvalidLoaderRuleRenameAsIssue {
1284 glob: RcStr,
1285 rename_as: RcStr,
1286 config_file_path: FileSystemPath,
1287}
1288
1289#[turbo_tasks::value_impl]
1290impl Issue for InvalidLoaderRuleRenameAsIssue {
1291 #[turbo_tasks::function]
1292 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1293 Ok(self.config_file_path.clone().cell())
1294 }
1295
1296 #[turbo_tasks::function]
1297 fn stage(&self) -> Vc<IssueStage> {
1298 IssueStage::Config.cell()
1299 }
1300
1301 #[turbo_tasks::function]
1302 async fn title(&self) -> Result<Vc<StyledString>> {
1303 Ok(
1304 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1305 .cell(),
1306 )
1307 }
1308
1309 #[turbo_tasks::function]
1310 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1311 Ok(Vc::cell(Some(
1312 StyledString::Text(RcStr::from(format!(
1313 "The extension {} contains a wildcard, but the `as` option does not: {}",
1314 self.glob, self.rename_as,
1315 )))
1316 .resolved_cell(),
1317 )))
1318 }
1319
1320 #[turbo_tasks::function]
1321 fn documentation_link(&self) -> Vc<RcStr> {
1322 Vc::cell(turbopack_config_documentation_link())
1323 }
1324}
1325
1326#[turbo_tasks::value(shared)]
1327struct InvalidLoaderRuleConditionIssue {
1328 error_string: RcStr,
1329 condition: ConfigConditionItem,
1330 config_file_path: FileSystemPath,
1331}
1332
1333#[turbo_tasks::value_impl]
1334impl Issue for InvalidLoaderRuleConditionIssue {
1335 #[turbo_tasks::function]
1336 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1337 Ok(self.await?.config_file_path.clone().cell())
1338 }
1339
1340 #[turbo_tasks::function]
1341 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1342 IssueStage::Config.cell()
1343 }
1344
1345 #[turbo_tasks::function]
1346 async fn title(&self) -> Result<Vc<StyledString>> {
1347 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1348 }
1349
1350 #[turbo_tasks::function]
1351 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1352 Ok(Vc::cell(Some(
1353 StyledString::Stack(vec![
1354 StyledString::Line(vec![
1355 StyledString::Text(rcstr!("Encountered the following error: ")),
1356 StyledString::Code(self.error_string.clone()),
1357 ]),
1358 StyledString::Text(rcstr!("While processing the condition:")),
1359 StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1360 ])
1361 .resolved_cell(),
1362 )))
1363 }
1364
1365 #[turbo_tasks::function]
1366 fn documentation_link(&self) -> Vc<RcStr> {
1367 Vc::cell(turbopack_config_documentation_link())
1368 }
1369}
1370
1371#[turbo_tasks::value_impl]
1372impl NextConfig {
1373 #[turbo_tasks::function]
1374 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1375 let string = string.await?;
1376 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1377 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1378 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1379 Ok(config.cell())
1380 }
1381
1382 #[turbo_tasks::function]
1383 pub async fn config_file_path(
1384 &self,
1385 project_path: FileSystemPath,
1386 ) -> Result<Vc<FileSystemPath>> {
1387 Ok(project_path.join(&self.config_file_name)?.cell())
1388 }
1389
1390 #[turbo_tasks::function]
1391 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1392 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1393 }
1394
1395 #[turbo_tasks::function]
1396 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1397 Vc::cell(self.react_production_profiling.unwrap_or_default())
1398 }
1399
1400 #[turbo_tasks::function]
1401 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1402 Vc::cell(
1403 self.server_external_packages
1404 .as_ref()
1405 .cloned()
1406 .unwrap_or_default(),
1407 )
1408 }
1409
1410 #[turbo_tasks::function]
1411 pub fn is_standalone(&self) -> Vc<bool> {
1412 Vc::cell(self.output == Some(OutputType::Standalone))
1413 }
1414
1415 #[turbo_tasks::function]
1416 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1417 Vc::cell(self.base_path.clone())
1418 }
1419
1420 #[turbo_tasks::function]
1421 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1422 if let Some(handler) = &self.cache_handler {
1423 Ok(Vc::cell(Some(project_path.join(handler)?)))
1424 } else {
1425 Ok(Vc::cell(None))
1426 }
1427 }
1428
1429 #[turbo_tasks::function]
1430 pub fn compiler(&self) -> Vc<CompilerConfig> {
1431 self.compiler.clone().unwrap_or_default().cell()
1432 }
1433
1434 #[turbo_tasks::function]
1435 pub fn env(&self) -> Vc<EnvMap> {
1436 let env = self
1440 .env
1441 .iter()
1442 .map(|(k, v)| {
1443 (
1444 k.as_str().into(),
1445 if let JsonValue::String(s) = v {
1446 s.as_str().into()
1448 } else {
1449 v.to_string().into()
1450 },
1451 )
1452 })
1453 .collect();
1454
1455 Vc::cell(env)
1456 }
1457
1458 #[turbo_tasks::function]
1459 pub fn image_config(&self) -> Vc<ImageConfig> {
1460 self.images.clone().cell()
1461 }
1462
1463 #[turbo_tasks::function]
1464 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1465 let mut extensions = self.page_extensions.clone();
1469 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1470 Vc::cell(extensions)
1471 }
1472
1473 #[turbo_tasks::function]
1474 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1475 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1476 }
1477
1478 #[turbo_tasks::function]
1479 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1480 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1481 }
1482
1483 #[turbo_tasks::function]
1484 pub async fn webpack_rules(
1485 self: Vc<Self>,
1486 project_path: FileSystemPath,
1487 ) -> Result<Vc<WebpackRules>> {
1488 let this = self.await?;
1489 let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1490 return Ok(Vc::cell(Vec::new()));
1491 };
1492 if turbo_rules.is_empty() {
1493 return Ok(Vc::cell(Vec::new()));
1494 }
1495 let mut rules = Vec::new();
1496 for (glob, rule_collection) in turbo_rules.iter() {
1497 fn transform_loaders(
1498 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1499 ) -> ResolvedVc<WebpackLoaderItems> {
1500 ResolvedVc::cell(
1501 loaders
1502 .map(|item| match item {
1503 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1504 loader: name.clone(),
1505 options: Default::default(),
1506 },
1507 LoaderItem::LoaderOptions(options) => options.clone(),
1508 })
1509 .collect(),
1510 )
1511 }
1512 for item in &rule_collection.0 {
1513 match item {
1514 RuleConfigCollectionItem::Shorthand(loaders) => {
1515 rules.push((
1516 glob.clone(),
1517 LoaderRuleItem {
1518 loaders: transform_loaders(&mut [loaders].into_iter()),
1519 rename_as: None,
1520 condition: None,
1521 },
1522 ));
1523 }
1524 RuleConfigCollectionItem::Full(RuleConfigItem {
1525 loaders,
1526 rename_as,
1527 condition,
1528 }) => {
1529 if glob.contains("*")
1533 && let Some(rename_as) = rename_as.as_ref()
1534 && !rename_as.contains("*")
1535 {
1536 InvalidLoaderRuleRenameAsIssue {
1537 glob: glob.clone(),
1538 config_file_path: self
1539 .config_file_path(project_path.clone())
1540 .owned()
1541 .await?,
1542 rename_as: rename_as.clone(),
1543 }
1544 .resolved_cell()
1545 .emit();
1546 }
1547
1548 let condition = if let Some(condition) = condition {
1551 match ConditionItem::try_from(condition.clone()) {
1552 Ok(cond) => Some(cond),
1553 Err(err) => {
1554 InvalidLoaderRuleConditionIssue {
1555 error_string: RcStr::from(err.to_string()),
1556 condition: condition.clone(),
1557 config_file_path: self
1558 .config_file_path(project_path.clone())
1559 .owned()
1560 .await?,
1561 }
1562 .resolved_cell()
1563 .emit();
1564 None
1565 }
1566 }
1567 } else {
1568 None
1569 };
1570 rules.push((
1571 glob.clone(),
1572 LoaderRuleItem {
1573 loaders: transform_loaders(&mut loaders.iter()),
1574 rename_as: rename_as.clone(),
1575 condition,
1576 },
1577 ));
1578 }
1579 }
1580 }
1581 }
1582 Ok(Vc::cell(rules))
1583 }
1584
1585 #[turbo_tasks::function]
1586 pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1587 Ok(Vc::cell(
1588 self.experimental
1589 .turbopack_persistent_caching
1590 .unwrap_or_default(),
1591 ))
1592 }
1593
1594 #[turbo_tasks::function]
1595 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1596 let Some(resolve_alias) = self
1597 .turbopack
1598 .as_ref()
1599 .and_then(|t| t.resolve_alias.as_ref())
1600 else {
1601 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1602 };
1603 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1604 Ok(alias_map.cell())
1605 }
1606
1607 #[turbo_tasks::function]
1608 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1609 let Some(resolve_extensions) = self
1610 .turbopack
1611 .as_ref()
1612 .and_then(|t| t.resolve_extensions.as_ref())
1613 else {
1614 return Vc::cell(None);
1615 };
1616 Vc::cell(Some(resolve_extensions.clone()))
1617 }
1618
1619 #[turbo_tasks::function]
1620 pub fn import_externals(&self) -> Result<Vc<bool>> {
1621 Ok(Vc::cell(match self.experimental.esm_externals {
1622 Some(EsmExternals::Bool(b)) => b,
1623 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1624 None => true,
1625 }))
1626 }
1627
1628 #[turbo_tasks::function]
1629 pub fn inline_css(&self) -> Vc<bool> {
1630 Vc::cell(self.experimental.inline_css.unwrap_or(false))
1631 }
1632
1633 #[turbo_tasks::function]
1634 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1635 let options = &self.experimental.mdx_rs;
1636
1637 let options = match options {
1638 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1639 MdxTransformOptions {
1640 provider_import_source: Some(mdx_import_source_file()),
1641 ..Default::default()
1642 }
1643 .resolved_cell(),
1644 )),
1645 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1646 MdxTransformOptions {
1647 provider_import_source: Some(
1648 options
1649 .provider_import_source
1650 .clone()
1651 .unwrap_or(mdx_import_source_file()),
1652 ),
1653 ..options.clone()
1654 }
1655 .resolved_cell(),
1656 )),
1657 _ => OptionalMdxTransformOptions(None),
1658 };
1659
1660 options.cell()
1661 }
1662
1663 #[turbo_tasks::function]
1664 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1665 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1666 }
1667
1668 #[turbo_tasks::function]
1669 pub fn dist_dir(&self) -> Vc<RcStr> {
1670 Vc::cell(self.dist_dir.clone())
1671 }
1672 #[turbo_tasks::function]
1673 pub fn dist_dir_root(&self) -> Vc<RcStr> {
1674 Vc::cell(self.dist_dir_root.clone())
1675 }
1676
1677 #[turbo_tasks::function]
1678 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1679 if let Some(handlers) = &self.cache_handlers {
1680 Ok(Vc::cell(
1681 handlers
1682 .values()
1683 .map(|h| project_path.join(h))
1684 .collect::<Result<Vec<_>>>()?,
1685 ))
1686 } else {
1687 Ok(Vc::cell(vec![]))
1688 }
1689 }
1690
1691 #[turbo_tasks::function]
1692 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1693 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1694 }
1695
1696 #[turbo_tasks::function]
1703 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1704 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1705 }
1706
1707 #[turbo_tasks::function]
1708 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1709 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1710 }
1711
1712 #[turbo_tasks::function]
1713 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1714 let options = &self.react_compiler;
1715
1716 let options = match options {
1717 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1718 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1719 }
1720 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1721 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1722 ),
1723 _ => OptionalReactCompilerOptions(None),
1724 };
1725
1726 options.cell()
1727 }
1728
1729 #[turbo_tasks::function]
1730 pub fn sass_config(&self) -> Vc<JsonValue> {
1731 Vc::cell(self.sass_options.clone().unwrap_or_default())
1732 }
1733
1734 #[turbo_tasks::function]
1735 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1736 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1737 }
1738
1739 #[turbo_tasks::function]
1740 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1741 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1742 }
1743
1744 #[turbo_tasks::function]
1747 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1748 let this = self.await?;
1749
1750 Ok(Vc::cell(
1751 format!(
1752 "{}/_next/",
1753 if let Some(asset_prefix) = &this.asset_prefix {
1754 asset_prefix
1755 } else {
1756 this.base_path.as_ref().map_or("", |b| b.as_str())
1757 }
1758 .trim_end_matches('/')
1759 )
1760 .into(),
1761 ))
1762 }
1763
1764 #[turbo_tasks::function]
1766 pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1767 let this = self.await?;
1768
1769 match &this.deployment_id {
1770 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1771 None => Ok(Vc::cell(None)),
1772 }
1773 }
1774
1775 #[turbo_tasks::function]
1776 pub fn enable_taint(&self) -> Vc<bool> {
1777 Vc::cell(self.experimental.taint.unwrap_or(false))
1778 }
1779
1780 #[turbo_tasks::function]
1781 pub fn enable_transition_indicator(&self) -> Vc<bool> {
1782 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1783 }
1784
1785 #[turbo_tasks::function]
1786 pub fn enable_cache_components(&self) -> Vc<bool> {
1787 Vc::cell(self.cache_components.unwrap_or(false))
1788 }
1789
1790 #[turbo_tasks::function]
1791 pub fn enable_use_cache(&self) -> Vc<bool> {
1792 Vc::cell(
1793 self.experimental
1794 .use_cache
1795 .unwrap_or(self.cache_components.unwrap_or(false)),
1799 )
1800 }
1801
1802 #[turbo_tasks::function]
1803 pub fn enable_root_params(&self) -> Vc<bool> {
1804 Vc::cell(
1805 self.experimental
1806 .root_params
1807 .unwrap_or(self.cache_components.unwrap_or(false)),
1809 )
1810 }
1811
1812 #[turbo_tasks::function]
1813 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1814 let mut cache_kinds = CacheKinds::default();
1815
1816 if let Some(handlers) = self.cache_handlers.as_ref() {
1817 cache_kinds.extend(handlers.keys().cloned());
1818 }
1819
1820 cache_kinds.cell()
1821 }
1822
1823 #[turbo_tasks::function]
1824 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1825 Vc::cell(
1826 self.experimental
1827 .optimize_package_imports
1828 .clone()
1829 .unwrap_or_default(),
1830 )
1831 }
1832
1833 #[turbo_tasks::function]
1834 pub fn tree_shaking_mode_for_foreign_code(
1835 &self,
1836 _is_development: bool,
1837 ) -> Vc<OptionTreeShaking> {
1838 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1839 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1840 Some(true) => Some(TreeShakingMode::ModuleFragments),
1841 None => Some(TreeShakingMode::ReexportsOnly),
1842 })
1843 .cell()
1844 }
1845
1846 #[turbo_tasks::function]
1847 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1848 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1849 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1850 Some(true) => Some(TreeShakingMode::ModuleFragments),
1851 None => Some(TreeShakingMode::ReexportsOnly),
1852 })
1853 .cell()
1854 }
1855
1856 #[turbo_tasks::function]
1857 pub async fn turbopack_remove_unused_imports(
1858 self: Vc<Self>,
1859 mode: Vc<NextMode>,
1860 ) -> Result<Vc<bool>> {
1861 let remove_unused_imports = self
1862 .await?
1863 .experimental
1864 .turbopack_remove_unused_imports
1865 .unwrap_or(matches!(*mode.await?, NextMode::Build));
1866
1867 if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
1868 bail!(
1869 "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
1870 enabling `experimental.turbopackRemoveUnusedExports`"
1871 );
1872 }
1873
1874 Ok(Vc::cell(remove_unused_imports))
1875 }
1876
1877 #[turbo_tasks::function]
1878 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1879 Ok(Vc::cell(
1880 self.experimental
1881 .turbopack_remove_unused_exports
1882 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1883 ))
1884 }
1885
1886 #[turbo_tasks::function]
1887 pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
1888 Vc::cell(
1889 self.experimental
1890 .turbopack_infer_module_side_effects
1891 .unwrap_or(true),
1892 )
1893 }
1894
1895 #[turbo_tasks::function]
1896 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1897 Ok(match *mode.await? {
1898 NextMode::Development => ModuleIds::Named.cell(),
1900 NextMode::Build => self
1901 .experimental
1902 .turbopack_module_ids
1903 .unwrap_or(ModuleIds::Deterministic)
1904 .cell(),
1905 })
1906 }
1907
1908 #[turbo_tasks::function]
1909 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1910 let minify = self.experimental.turbopack_minify;
1911 Ok(Vc::cell(
1912 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1913 ))
1914 }
1915
1916 #[turbo_tasks::function]
1917 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1918 Ok(Vc::cell(match *mode.await? {
1919 NextMode::Development => false,
1921 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1922 }))
1923 }
1924
1925 #[turbo_tasks::function]
1926 pub async fn turbo_nested_async_chunking(
1927 &self,
1928 mode: Vc<NextMode>,
1929 client_side: bool,
1930 ) -> Result<Vc<bool>> {
1931 let option = if client_side {
1932 self.experimental
1933 .turbopack_client_side_nested_async_chunking
1934 } else {
1935 self.experimental
1936 .turbopack_server_side_nested_async_chunking
1937 };
1938 Ok(Vc::cell(if let Some(value) = option {
1939 value
1940 } else {
1941 match *mode.await? {
1942 NextMode::Development => false,
1943 NextMode::Build => client_side,
1944 }
1945 }))
1946 }
1947
1948 #[turbo_tasks::function]
1949 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
1950 Vc::cell(
1951 self.experimental
1952 .turbopack_import_type_bytes
1953 .unwrap_or(false),
1954 )
1955 }
1956
1957 #[turbo_tasks::function]
1958 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
1959 let input_source_maps = self
1960 .experimental
1961 .turbopack_input_source_maps
1962 .unwrap_or(true);
1963 let source_maps = self
1964 .experimental
1965 .turbopack_source_maps
1966 .unwrap_or(match &*mode.await? {
1967 NextMode::Development => true,
1968 NextMode::Build => self.production_browser_source_maps,
1969 });
1970 Ok(match (source_maps, input_source_maps) {
1971 (true, true) => SourceMapsType::Full,
1972 (true, false) => SourceMapsType::Partial,
1973 (false, _) => SourceMapsType::None,
1974 }
1975 .cell())
1976 }
1977
1978 #[turbo_tasks::function]
1979 pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
1980 let input_source_maps = self
1981 .experimental
1982 .turbopack_input_source_maps
1983 .unwrap_or(true);
1984 let source_maps = self
1985 .experimental
1986 .turbopack_source_maps
1987 .or(self.experimental.server_source_maps)
1988 .unwrap_or(true);
1989 Ok(match (source_maps, input_source_maps) {
1990 (true, true) => SourceMapsType::Full,
1991 (true, false) => SourceMapsType::Partial,
1992 (false, _) => SourceMapsType::None,
1993 }
1994 .cell())
1995 }
1996
1997 #[turbo_tasks::function]
1998 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
1999 Vc::cell(
2000 self.turbopack
2001 .as_ref()
2002 .and_then(|turbopack| turbopack.debug_ids)
2003 .unwrap_or(false),
2004 )
2005 }
2006
2007 #[turbo_tasks::function]
2008 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2009 Ok(Vc::cell(
2010 self.typescript
2011 .tsconfig_path
2012 .as_ref()
2013 .map(|path| path.to_owned().into()),
2014 ))
2015 }
2016
2017 #[turbo_tasks::function]
2018 pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2019 Vc::cell(self.cross_origin.clone())
2020 }
2021
2022 #[turbo_tasks::function]
2023 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2024 Vc::cell(self.i18n.clone())
2025 }
2026
2027 #[turbo_tasks::function]
2028 pub fn output(&self) -> Vc<OptionOutputType> {
2029 Vc::cell(self.output.clone())
2030 }
2031
2032 #[turbo_tasks::function]
2033 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2034 Vc::cell(self.output_file_tracing_includes.clone())
2035 }
2036
2037 #[turbo_tasks::function]
2038 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2039 Vc::cell(self.output_file_tracing_excludes.clone())
2040 }
2041
2042 #[turbo_tasks::function]
2043 pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2044 FetchClientConfig::default().cell()
2045 }
2046}
2047
2048#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2051#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2052#[serde(rename_all = "camelCase")]
2053pub struct JsConfig {
2054 #[bincode(with = "turbo_bincode::serde_self_describing")]
2055 compiler_options: Option<serde_json::Value>,
2056}
2057
2058#[turbo_tasks::value_impl]
2059impl JsConfig {
2060 #[turbo_tasks::function]
2061 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2062 let string = string.await?;
2063 let config: JsConfig = serde_json::from_str(&string)
2064 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2065
2066 Ok(config.cell())
2067 }
2068
2069 #[turbo_tasks::function]
2070 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2071 Vc::cell(self.compiler_options.clone().unwrap_or_default())
2072 }
2073}
2074
2075#[cfg(test)]
2076mod tests {
2077 use super::*;
2078
2079 #[test]
2080 fn test_serde_rule_config_item_options() {
2081 let json_value = serde_json::json!({
2082 "loaders": [],
2083 "as": "*.js",
2084 "condition": {
2085 "all": [
2086 "production",
2087 {"not": "foreign"},
2088 {"any": [
2089 "browser",
2090 {
2091 "path": { "type": "glob", "value": "*.svg"},
2092 "content": {
2093 "source": "@someTag",
2094 "flags": ""
2095 }
2096 }
2097 ]},
2098 ],
2099 }
2100 });
2101
2102 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2103
2104 assert_eq!(
2105 rule_config,
2106 RuleConfigItem {
2107 loaders: vec![],
2108 rename_as: Some(rcstr!("*.js")),
2109 condition: Some(ConfigConditionItem::All(
2110 [
2111 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2112 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2113 WebpackLoaderBuiltinCondition::Foreign
2114 ))),
2115 ConfigConditionItem::Any(
2116 vec![
2117 ConfigConditionItem::Builtin(
2118 WebpackLoaderBuiltinCondition::Browser
2119 ),
2120 ConfigConditionItem::Base {
2121 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2122 content: Some(RegexComponents {
2123 source: rcstr!("@someTag"),
2124 flags: rcstr!(""),
2125 }),
2126 query: None,
2127 },
2128 ]
2129 .into(),
2130 ),
2131 ]
2132 .into(),
2133 )),
2134 }
2135 );
2136 }
2137}