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