1use anyhow::{Context, Result, bail};
2use async_trait::async_trait;
3use bincode::{Decode, Encode};
4use either::Either;
5use rustc_hash::FxHashSet;
6use serde::{Deserialize, Deserializer, Serialize};
7use serde_json::Value as JsonValue;
8use turbo_esregex::EsRegex;
9use turbo_rcstr::{RcStr, rcstr};
10use turbo_tasks::{
11 FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TryJoinIterExt, Vc,
12 debug::ValueDebugFormat, trace::TraceRawVcs,
13};
14use turbo_tasks_env::EnvMap;
15use turbo_tasks_fetch::FetchClientConfig;
16use turbo_tasks_fs::{
17 FileSystemPath,
18 glob::{Glob, GlobOptions},
19};
20use turbopack::module_options::{
21 ConditionContentType, ConditionItem, ConditionPath, ConditionQuery, LoaderRuleItem,
22 WebpackRules, module_options_context::MdxTransformOptions,
23};
24use turbopack_core::{
25 chunk::{CrossOrigin, SourceMapsType},
26 issue::{
27 IgnoreIssue, IgnoreIssuePattern, Issue, IssueExt, IssueSeverity, IssueStage, StyledString,
28 },
29 module_graph::style_groups::StyleGroupsAlgorithm,
30 resolve::ResolveAliasMap,
31};
32use turbopack_ecmascript::{
33 OptionTreeShaking, TreeShakingMode,
34 transform::{
35 OptionReactCompilerCompilationMode, ReactCompilerCompilationMode, ReactCompilerTarget,
36 },
37};
38use turbopack_ecmascript_plugins::transform::{
39 emotion::EmotionTransformConfig, relay::RelayConfig,
40 styled_components::StyledComponentsTransformConfig,
41};
42use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
43
44use crate::{
45 app_structure::FileSystemPathVec,
46 mode::NextMode,
47 next_import_map::mdx_import_source_file,
48 next_shared::{
49 transforms::ModularizeImportPackageConfig, webpack_rules::WebpackLoaderBuiltinCondition,
50 },
51 util::relativize_glob,
52};
53
54pub const DIST_PROFILES_DIR_NAME: &str = ".next-profiles";
58
59#[turbo_tasks::value(transparent)]
60pub struct ModularizeImports(
61 #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<String, ModularizeImportPackageConfig>,
62);
63
64#[turbo_tasks::value(transparent)]
65#[derive(Clone, Debug)]
66pub struct CacheKinds(FxHashSet<RcStr>);
67
68impl CacheKinds {
69 pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
70 self.0.extend(iter);
71 }
72}
73
74impl Default for CacheKinds {
75 fn default() -> Self {
76 CacheKinds(
77 ["default", "remote", "private"]
78 .iter()
79 .map(|&s| s.into())
80 .collect(),
81 )
82 }
83}
84
85#[turbo_tasks::value(transparent)]
86pub struct CacheHandlersMap(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, RcStr>);
87
88#[turbo_tasks::value(eq = "manual")]
89#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
90#[serde(default, rename_all = "camelCase")]
91pub struct NextConfig {
92 config_file: Option<RcStr>,
95 config_file_name: RcStr,
96
97 cache_max_memory_size: Option<f64>,
101 cache_handler: Option<RcStr>,
103 #[bincode(with_serde)]
104 cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
105 #[bincode(with = "turbo_bincode::serde_self_describing")]
106 env: FxIndexMap<String, JsonValue>,
107 experimental: ExperimentalConfig,
108 images: ImageConfig,
109 page_extensions: Vec<RcStr>,
110 instrumentation_client_inject: Option<Vec<RcStr>>,
111 react_compiler: Option<ReactCompilerOptionsOrBoolean>,
112 react_production_profiling: Option<bool>,
113 react_strict_mode: Option<bool>,
114 transpile_packages: Option<Vec<RcStr>>,
115 #[bincode(with = "turbo_bincode::serde_self_describing")]
116 modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
117 dist_dir: RcStr,
118 dist_dir_root: RcStr,
119 deployment_id: Option<RcStr>,
120 #[bincode(with = "turbo_bincode::serde_self_describing")]
121 sass_options: Option<serde_json::Value>,
122 trailing_slash: Option<bool>,
123 asset_prefix: Option<RcStr>,
124 base_path: Option<RcStr>,
125 skip_proxy_url_normalize: Option<bool>,
126 skip_trailing_slash_redirect: Option<bool>,
127 i18n: Option<I18NConfig>,
128 cross_origin: CrossOrigin,
129 dev_indicators: Option<DevIndicatorsConfig>,
130 output: Option<OutputType>,
131 turbopack: Option<TurbopackConfig>,
132 production_browser_source_maps: bool,
133 #[bincode(with = "turbo_bincode::serde_self_describing")]
134 output_file_tracing_includes: Option<serde_json::Value>,
135 #[bincode(with = "turbo_bincode::serde_self_describing")]
136 output_file_tracing_excludes: Option<serde_json::Value>,
137 output_file_tracing_root: Option<RcStr>,
139
140 bundle_pages_router_dependencies: Option<bool>,
145
146 server_external_packages: Option<Vec<RcStr>>,
151
152 #[serde(rename = "_originalRedirects")]
153 original_redirects: Option<Vec<Redirect>>,
154
155 compiler: Option<CompilerConfig>,
157
158 optimize_fonts: Option<bool>,
159
160 clean_dist_dir: bool,
161 compress: bool,
162 eslint: EslintConfig,
163 exclude_default_moment_locales: bool,
164 generate_etags: bool,
165 http_agent_options: HttpAgentConfig,
166 on_demand_entries: OnDemandEntriesConfig,
167 powered_by_header: bool,
168 #[bincode(with = "turbo_bincode::serde_self_describing")]
169 public_runtime_config: FxIndexMap<String, serde_json::Value>,
170 #[bincode(with = "turbo_bincode::serde_self_describing")]
171 server_runtime_config: FxIndexMap<String, serde_json::Value>,
172 static_page_generation_timeout: f64,
173 target: Option<String>,
174 typescript: TypeScriptConfig,
175 use_file_system_public_routes: bool,
176 cache_components: Option<bool>,
177
178 adapter_path: Option<RcStr>,
179 }
186
187#[turbo_tasks::value_impl]
188impl NextConfig {
189 #[turbo_tasks::function]
190 pub fn with_analyze_config(&self) -> Vc<Self> {
191 let mut new = self.clone();
192 new.experimental.turbopack_source_maps = Some(true);
193 new.experimental.turbopack_input_source_maps = Some(false);
194 new.cell()
195 }
196}
197
198#[derive(
199 Clone,
200 Debug,
201 Default,
202 PartialEq,
203 Deserialize,
204 TraceRawVcs,
205 NonLocalValue,
206 OperationValue,
207 Encode,
208 Decode,
209)]
210#[serde(rename_all = "camelCase")]
211struct EslintConfig {
212 dirs: Option<Vec<String>>,
213 ignore_during_builds: Option<bool>,
214}
215
216#[derive(
217 Clone,
218 Debug,
219 Default,
220 PartialEq,
221 Deserialize,
222 TraceRawVcs,
223 NonLocalValue,
224 OperationValue,
225 Encode,
226 Decode,
227)]
228#[serde(rename_all = "kebab-case")]
229pub enum BuildActivityPositions {
230 #[default]
231 BottomRight,
232 BottomLeft,
233 TopRight,
234 TopLeft,
235}
236
237#[derive(
238 Clone,
239 Debug,
240 Default,
241 PartialEq,
242 Deserialize,
243 TraceRawVcs,
244 NonLocalValue,
245 OperationValue,
246 Encode,
247 Decode,
248)]
249#[serde(rename_all = "camelCase")]
250pub struct DevIndicatorsOptions {
251 pub build_activity_position: Option<BuildActivityPositions>,
252 pub position: Option<BuildActivityPositions>,
253}
254
255#[derive(
256 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
257)]
258#[serde(untagged)]
259pub enum DevIndicatorsConfig {
260 WithOptions(DevIndicatorsOptions),
261 Boolean(bool),
262}
263
264#[derive(
265 Clone,
266 Debug,
267 Default,
268 PartialEq,
269 Deserialize,
270 TraceRawVcs,
271 NonLocalValue,
272 OperationValue,
273 Encode,
274 Decode,
275)]
276#[serde(rename_all = "camelCase")]
277struct OnDemandEntriesConfig {
278 max_inactive_age: f64,
279 pages_buffer_length: f64,
280}
281
282#[derive(
283 Clone,
284 Debug,
285 Default,
286 PartialEq,
287 Deserialize,
288 TraceRawVcs,
289 NonLocalValue,
290 OperationValue,
291 Encode,
292 Decode,
293)]
294#[serde(rename_all = "camelCase")]
295struct HttpAgentConfig {
296 keep_alive: bool,
297}
298
299#[derive(
300 Clone,
301 Debug,
302 PartialEq,
303 Eq,
304 Deserialize,
305 TraceRawVcs,
306 NonLocalValue,
307 OperationValue,
308 Encode,
309 Decode,
310)]
311#[serde(rename_all = "camelCase")]
312pub struct DomainLocale {
313 pub default_locale: String,
314 pub domain: String,
315 pub http: Option<bool>,
316 pub locales: Option<Vec<String>>,
317}
318
319#[derive(
320 Clone,
321 Debug,
322 PartialEq,
323 Eq,
324 Deserialize,
325 TraceRawVcs,
326 NonLocalValue,
327 OperationValue,
328 Encode,
329 Decode,
330)]
331#[serde(rename_all = "camelCase")]
332pub struct I18NConfig {
333 pub default_locale: String,
334 pub domains: Option<Vec<DomainLocale>>,
335 pub locale_detection: Option<bool>,
336 pub locales: Vec<String>,
337}
338
339#[turbo_tasks::value(transparent)]
340pub struct OptionI18NConfig(Option<I18NConfig>);
341
342#[derive(
343 Clone,
344 Debug,
345 PartialEq,
346 Eq,
347 Deserialize,
348 TraceRawVcs,
349 NonLocalValue,
350 OperationValue,
351 Encode,
352 Decode,
353)]
354#[serde(rename_all = "kebab-case")]
355pub enum OutputType {
356 Standalone,
357 Export,
358}
359
360#[turbo_tasks::value(transparent)]
361pub struct OptionOutputType(Option<OutputType>);
362
363#[turbo_tasks::task_input]
364#[derive(
365 Debug,
366 Clone,
367 Hash,
368 Eq,
369 PartialEq,
370 Ord,
371 PartialOrd,
372 TraceRawVcs,
373 Serialize,
374 Deserialize,
375 OperationValue,
376 Encode,
377 Decode,
378)]
379#[serde(tag = "type", rename_all = "kebab-case")]
380pub enum RouteHas {
381 Header {
382 key: RcStr,
383 #[serde(skip_serializing_if = "Option::is_none")]
384 value: Option<RcStr>,
385 },
386 Cookie {
387 key: RcStr,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 value: Option<RcStr>,
390 },
391 Query {
392 key: RcStr,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 value: Option<RcStr>,
395 },
396 Host {
397 value: RcStr,
398 },
399}
400
401#[derive(Clone, Debug, Default, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
402#[serde(rename_all = "camelCase")]
403pub struct HeaderValue {
404 pub key: RcStr,
405 pub value: RcStr,
406}
407
408#[derive(Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
409#[serde(rename_all = "camelCase")]
410pub struct Header {
411 pub source: String,
412 pub base_path: Option<bool>,
413 pub locale: Option<bool>,
414 pub headers: Vec<HeaderValue>,
415 pub has: Option<Vec<RouteHas>>,
416 pub missing: Option<Vec<RouteHas>>,
417}
418
419#[derive(
420 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
421)]
422#[serde(rename_all = "camelCase")]
423pub enum RedirectStatus {
424 StatusCode(f64),
425 Permanent(bool),
426}
427
428#[derive(
429 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
430)]
431#[serde(rename_all = "camelCase")]
432pub struct Redirect {
433 pub source: String,
434 pub destination: String,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub base_path: Option<bool>,
437 #[serde(skip_serializing_if = "Option::is_none")]
438 pub locale: Option<bool>,
439 #[serde(skip_serializing_if = "Option::is_none")]
440 pub has: Option<Vec<RouteHas>>,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub missing: Option<Vec<RouteHas>>,
443
444 #[serde(flatten)]
445 pub status: RedirectStatus,
446}
447
448#[derive(Clone, Debug)]
449pub struct Rewrite {
450 pub source: String,
451 pub destination: String,
452 pub base_path: Option<bool>,
453 pub locale: Option<bool>,
454 pub has: Option<Vec<RouteHas>>,
455 pub missing: Option<Vec<RouteHas>>,
456}
457
458#[derive(Clone, Debug)]
459pub struct Rewrites {
460 pub before_files: Vec<Rewrite>,
461 pub after_files: Vec<Rewrite>,
462 pub fallback: Vec<Rewrite>,
463}
464
465#[derive(
466 Clone,
467 Debug,
468 Default,
469 PartialEq,
470 Deserialize,
471 TraceRawVcs,
472 NonLocalValue,
473 OperationValue,
474 Encode,
475 Decode,
476)]
477#[serde(rename_all = "camelCase")]
478pub struct TypeScriptConfig {
479 pub ignore_build_errors: Option<bool>,
480 pub tsconfig_path: Option<String>,
481}
482
483#[turbo_tasks::value(eq = "manual", operation)]
484#[derive(Clone, Debug, PartialEq, Deserialize)]
485#[serde(rename_all = "camelCase")]
486pub struct ImageConfig {
487 pub device_sizes: Vec<u16>,
488 pub image_sizes: Vec<u16>,
489 pub path: String,
490 pub loader: ImageLoader,
491 #[serde(deserialize_with = "empty_string_is_none")]
492 pub loader_file: Option<String>,
493 pub domains: Vec<String>,
494 pub disable_static_images: bool,
495 #[serde(rename = "minimumCacheTTL")]
496 pub minimum_cache_ttl: u64,
497 pub formats: Vec<ImageFormat>,
498 #[serde(rename = "dangerouslyAllowSVG")]
499 pub dangerously_allow_svg: bool,
500 pub content_security_policy: String,
501 pub remote_patterns: Vec<RemotePattern>,
502 pub unoptimized: bool,
503}
504
505fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
506where
507 D: Deserializer<'de>,
508{
509 let o = Option::<String>::deserialize(deserializer)?;
510 Ok(o.filter(|s| !s.is_empty()))
511}
512
513impl Default for ImageConfig {
514 fn default() -> Self {
515 Self {
517 device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
518 image_sizes: vec![32, 48, 64, 96, 128, 256, 384],
519 path: "/_next/image".to_string(),
520 loader: ImageLoader::Default,
521 loader_file: None,
522 domains: vec![],
523 disable_static_images: false,
524 minimum_cache_ttl: 60,
525 formats: vec![ImageFormat::Webp],
526 dangerously_allow_svg: false,
527 content_security_policy: "".to_string(),
528 remote_patterns: vec![],
529 unoptimized: false,
530 }
531 }
532}
533
534#[derive(
535 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
536)]
537#[serde(rename_all = "kebab-case")]
538pub enum ImageLoader {
539 Default,
540 Imgix,
541 Cloudinary,
542 Akamai,
543 Custom,
544}
545
546#[derive(
547 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
548)]
549pub enum ImageFormat {
550 #[serde(rename = "image/webp")]
551 Webp,
552 #[serde(rename = "image/avif")]
553 Avif,
554}
555
556#[derive(
557 Clone,
558 Debug,
559 Default,
560 PartialEq,
561 Deserialize,
562 TraceRawVcs,
563 NonLocalValue,
564 OperationValue,
565 Encode,
566 Decode,
567)]
568#[serde(rename_all = "camelCase")]
569pub struct RemotePattern {
570 pub hostname: String,
571 #[serde(skip_serializing_if = "Option::is_none")]
572 pub protocol: Option<RemotePatternProtocol>,
573 #[serde(skip_serializing_if = "Option::is_none")]
574 pub port: Option<String>,
575 #[serde(skip_serializing_if = "Option::is_none")]
576 pub pathname: Option<String>,
577}
578
579#[derive(
580 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
581)]
582#[serde(rename_all = "kebab-case")]
583pub enum RemotePatternProtocol {
584 Http,
585 Https,
586}
587
588#[derive(
589 Clone,
590 Debug,
591 Default,
592 PartialEq,
593 Deserialize,
594 TraceRawVcs,
595 NonLocalValue,
596 OperationValue,
597 Encode,
598 Decode,
599)]
600#[serde(rename_all = "camelCase")]
601pub struct TurbopackConfig {
602 #[serde(default)]
603 #[bincode(with = "turbo_bincode::indexmap")]
604 pub rules: FxIndexMap<RcStr, RuleConfigCollection>,
605 #[bincode(with = "turbo_bincode::serde_self_describing")]
606 pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
607 pub resolve_extensions: Option<Vec<RcStr>>,
608 pub debug_ids: Option<bool>,
609 pub chunk_loading_global: Option<RcStr>,
610 #[serde(default)]
612 pub ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
613}
614
615#[derive(
616 Deserialize,
617 Clone,
618 PartialEq,
619 Eq,
620 Debug,
621 TraceRawVcs,
622 NonLocalValue,
623 OperationValue,
624 Encode,
625 Decode,
626)]
627#[serde(deny_unknown_fields)]
628pub struct RegexComponents {
629 source: RcStr,
630 flags: RcStr,
631}
632
633#[derive(
638 Clone,
639 PartialEq,
640 Eq,
641 Debug,
642 Deserialize,
643 TraceRawVcs,
644 NonLocalValue,
645 OperationValue,
646 Encode,
647 Decode,
648)]
649#[serde(
650 tag = "type",
651 content = "value",
652 rename_all = "camelCase",
653 deny_unknown_fields
654)]
655pub enum ConfigConditionPath {
656 Glob(RcStr),
657 Regex(RegexComponents),
658}
659
660impl TryFrom<ConfigConditionPath> for ConditionPath {
661 type Error = anyhow::Error;
662
663 fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
664 Ok(match config {
665 ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
666 ConfigConditionPath::Regex(path) => {
667 ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
668 }
669 })
670 }
671}
672
673impl TryFrom<RegexComponents> for EsRegex {
674 type Error = anyhow::Error;
675
676 fn try_from(components: RegexComponents) -> Result<EsRegex> {
677 EsRegex::new(&components.source, &components.flags)
678 }
679}
680
681#[derive(
682 Clone,
683 PartialEq,
684 Eq,
685 Debug,
686 Deserialize,
687 TraceRawVcs,
688 NonLocalValue,
689 OperationValue,
690 Encode,
691 Decode,
692)]
693#[serde(
694 tag = "type",
695 content = "value",
696 rename_all = "camelCase",
697 deny_unknown_fields
698)]
699pub enum ConfigConditionQuery {
700 Constant(RcStr),
701 Regex(RegexComponents),
702}
703
704impl TryFrom<ConfigConditionQuery> for ConditionQuery {
705 type Error = anyhow::Error;
706
707 fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
708 Ok(match config {
709 ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
710 ConfigConditionQuery::Regex(regex) => {
711 ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
712 }
713 })
714 }
715}
716
717#[derive(
718 Clone,
719 PartialEq,
720 Eq,
721 Debug,
722 Deserialize,
723 TraceRawVcs,
724 NonLocalValue,
725 OperationValue,
726 Encode,
727 Decode,
728)]
729#[serde(
730 tag = "type",
731 content = "value",
732 rename_all = "camelCase",
733 deny_unknown_fields
734)]
735pub enum ConfigConditionContentType {
736 Glob(RcStr),
737 Regex(RegexComponents),
738}
739
740impl TryFrom<ConfigConditionContentType> for ConditionContentType {
741 type Error = anyhow::Error;
742
743 fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
744 Ok(match config {
745 ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
746 ConfigConditionContentType::Regex(regex) => {
747 ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
748 }
749 })
750 }
751}
752
753#[derive(
754 Deserialize,
755 Clone,
756 PartialEq,
757 Eq,
758 Debug,
759 TraceRawVcs,
760 NonLocalValue,
761 OperationValue,
762 Encode,
763 Decode,
764)]
765#[serde(deny_unknown_fields)]
768pub enum ConfigConditionItem {
769 #[serde(rename = "all")]
770 All(Box<[ConfigConditionItem]>),
771 #[serde(rename = "any")]
772 Any(Box<[ConfigConditionItem]>),
773 #[serde(rename = "not")]
774 Not(Box<ConfigConditionItem>),
775 #[serde(untagged)]
776 Builtin(WebpackLoaderBuiltinCondition),
777 #[serde(untagged)]
778 Base {
779 #[serde(default)]
780 path: Option<ConfigConditionPath>,
781 #[serde(default)]
782 content: Option<RegexComponents>,
783 #[serde(default)]
784 query: Option<ConfigConditionQuery>,
785 #[serde(default, rename = "contentType")]
786 content_type: Option<ConfigConditionContentType>,
787 },
788}
789
790impl TryFrom<ConfigConditionItem> for ConditionItem {
791 type Error = anyhow::Error;
792
793 fn try_from(config: ConfigConditionItem) -> Result<Self> {
794 let try_from_vec = |conds: Box<[_]>| {
795 conds
796 .into_iter()
797 .map(ConditionItem::try_from)
798 .collect::<Result<_>>()
799 };
800 Ok(match config {
801 ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
802 ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
803 ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
804 ConfigConditionItem::Builtin(cond) => {
805 ConditionItem::Builtin(RcStr::from(cond.as_str()))
806 }
807 ConfigConditionItem::Base {
808 path,
809 content,
810 query,
811 content_type,
812 } => ConditionItem::Base {
813 path: path.map(ConditionPath::try_from).transpose()?,
814 content: content
815 .map(EsRegex::try_from)
816 .transpose()?
817 .map(EsRegex::resolved_cell),
818 query: query.map(ConditionQuery::try_from).transpose()?,
819 content_type: content_type
820 .map(ConditionContentType::try_from)
821 .transpose()?,
822 },
823 })
824 }
825}
826
827#[derive(
828 Clone,
829 Debug,
830 PartialEq,
831 Eq,
832 Deserialize,
833 TraceRawVcs,
834 NonLocalValue,
835 OperationValue,
836 Encode,
837 Decode,
838)]
839#[serde(rename_all = "camelCase")]
840pub struct RuleConfigItem {
841 #[serde(default)]
842 pub loaders: Vec<LoaderItem>,
843 #[serde(default, alias = "as")]
844 pub rename_as: Option<RcStr>,
845 #[serde(default)]
846 pub condition: Option<ConfigConditionItem>,
847 #[serde(default, alias = "type")]
848 pub module_type: Option<RcStr>,
849}
850
851#[derive(
852 Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
853)]
854pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
855
856impl<'de> Deserialize<'de> for RuleConfigCollection {
857 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
858 where
859 D: Deserializer<'de>,
860 {
861 match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
862 deserializer,
863 )? {
864 Either::Left(collection) => Ok(RuleConfigCollection(collection)),
865 Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
866 item,
867 )])),
868 }
869 }
870}
871
872#[derive(
873 Clone,
874 Debug,
875 PartialEq,
876 Eq,
877 Deserialize,
878 TraceRawVcs,
879 NonLocalValue,
880 OperationValue,
881 Encode,
882 Decode,
883)]
884#[serde(untagged)]
885pub enum RuleConfigCollectionItem {
886 Shorthand(LoaderItem),
887 Full(RuleConfigItem),
888}
889
890#[derive(
891 Clone,
892 Debug,
893 PartialEq,
894 Eq,
895 Deserialize,
896 TraceRawVcs,
897 NonLocalValue,
898 OperationValue,
899 Encode,
900 Decode,
901)]
902#[serde(untagged)]
903pub enum LoaderItem {
904 LoaderName(RcStr),
905 LoaderOptions(WebpackLoaderItem),
906}
907
908#[turbo_tasks::value(operation)]
909#[derive(Copy, Clone, Debug, Deserialize)]
910#[serde(rename_all = "camelCase")]
911pub enum ModuleIds {
912 Named,
913 Deterministic,
914}
915
916#[turbo_tasks::value(operation)]
917#[derive(Copy, Clone, Debug, Deserialize)]
918#[serde(rename_all = "camelCase")]
919pub enum TurbopackPluginRuntimeStrategy {
920 #[cfg(feature = "worker_pool")]
921 WorkerThreads,
922 #[cfg(feature = "process_pool")]
923 ChildProcesses,
924}
925
926#[derive(
927 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
928)]
929#[serde(untagged)]
930pub enum MdxRsOptions {
931 Boolean(bool),
932 Option(MdxTransformOptions),
933}
934
935#[turbo_tasks::value(shared, operation)]
936#[derive(Clone, Debug, Default, Serialize, Deserialize)]
937#[serde(rename_all = "snake_case")]
938pub enum ReactCompilerPanicThreshold {
939 #[default]
940 None,
941 CriticalErrors,
942 AllErrors,
943}
944
945#[turbo_tasks::value(shared, operation)]
948#[derive(Clone, Debug, Default, Serialize, Deserialize)]
949#[serde(rename_all = "camelCase")]
950pub struct ReactCompilerOptions {
951 #[serde(default)]
952 pub compilation_mode: ReactCompilerCompilationMode,
953 #[serde(default)]
954 pub panic_threshold: ReactCompilerPanicThreshold,
955 #[serde(default, skip_deserializing, skip_serializing_if = "Option::is_none")]
956 pub target: Option<ReactCompilerTarget>,
957}
958
959#[derive(
960 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
961)]
962#[serde(untagged)]
963pub enum ReactCompilerOptionsOrBoolean {
964 Boolean(bool),
965 Option(ReactCompilerOptions),
966}
967
968#[turbo_tasks::value(transparent)]
969pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
970
971#[derive(
975 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
976)]
977#[serde(tag = "type")]
978pub enum TurbopackIgnoreIssuePathPattern {
979 #[serde(rename = "glob")]
980 Glob { value: RcStr },
981 #[serde(rename = "regex")]
982 Regex { source: RcStr, flags: RcStr },
983}
984
985impl TurbopackIgnoreIssuePathPattern {
986 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
987 match self {
988 TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
989 Glob::parse(value.clone(), GlobOptions::default())?,
990 )),
991 TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
992 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
993 }
994 }
995 }
996}
997
998#[derive(
1003 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1004)]
1005#[serde(tag = "type")]
1006pub enum TurbopackIgnoreIssueTextPattern {
1007 #[serde(rename = "string")]
1008 String { value: RcStr },
1009 #[serde(rename = "regex")]
1010 Regex { source: RcStr, flags: RcStr },
1011}
1012
1013impl TurbopackIgnoreIssueTextPattern {
1014 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
1015 match self {
1016 TurbopackIgnoreIssueTextPattern::String { value } => {
1017 Ok(IgnoreIssuePattern::ExactString(value.clone()))
1018 }
1019 TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
1020 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
1021 }
1022 }
1023 }
1024}
1025
1026#[derive(
1028 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1029)]
1030pub struct TurbopackIgnoreIssueRule {
1031 pub path: TurbopackIgnoreIssuePathPattern,
1032 #[serde(default)]
1033 pub title: Option<TurbopackIgnoreIssueTextPattern>,
1034 #[serde(default)]
1035 pub description: Option<TurbopackIgnoreIssueTextPattern>,
1036}
1037
1038#[derive(
1047 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1048)]
1049#[serde(untagged)]
1050pub enum CssChunkingConfig {
1051 Bool(bool),
1052 String(CssChunkingMode),
1053 Object(CssChunkingObject),
1054}
1055
1056#[derive(
1058 Clone,
1059 Copy,
1060 Debug,
1061 PartialEq,
1062 Eq,
1063 Deserialize,
1064 TraceRawVcs,
1065 NonLocalValue,
1066 OperationValue,
1067 Encode,
1068 Decode,
1069)]
1070#[serde(rename_all = "lowercase")]
1071pub enum CssChunkingMode {
1072 Strict,
1073 Loose,
1074 Graph,
1075}
1076
1077#[derive(
1082 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1083)]
1084#[serde(tag = "type", rename_all = "lowercase")]
1085pub enum CssChunkingObject {
1086 #[serde(skip)]
1087 None,
1088 Strict,
1089 Loose,
1090 Graph(CssChunkingGraphOptions),
1091}
1092
1093#[derive(
1095 Clone,
1096 Debug,
1097 Default,
1098 PartialEq,
1099 Deserialize,
1100 TraceRawVcs,
1101 NonLocalValue,
1102 OperationValue,
1103 Encode,
1104 Decode,
1105)]
1106#[serde(rename_all = "camelCase")]
1107pub struct CssChunkingGraphOptions {
1108 pub request_cost: Option<f32>,
1109 pub module_factor_cost: Option<f32>,
1110}
1111
1112impl CssChunkingConfig {
1113 pub fn normalize(&self) -> CssChunkingObject {
1116 match self {
1117 CssChunkingConfig::Bool(false) => CssChunkingObject::None,
1118 CssChunkingConfig::Bool(true) => CssChunkingObject::Loose,
1119 CssChunkingConfig::String(CssChunkingMode::Strict) => CssChunkingObject::Strict,
1120 CssChunkingConfig::String(CssChunkingMode::Loose) => CssChunkingObject::Loose,
1121 CssChunkingConfig::String(CssChunkingMode::Graph) => {
1122 CssChunkingObject::Graph(CssChunkingGraphOptions::default())
1123 }
1124 CssChunkingConfig::Object(obj) => obj.clone(),
1125 }
1126 }
1127}
1128
1129const DEFAULT_REQUEST_COST: f32 = 20_000.0;
1131const DEFAULT_MODULE_FACTOR_COST: f32 = 1.0;
1133
1134fn resolve_css_chunking_algorithm(
1140 config: Option<&CssChunkingConfig>,
1141) -> Result<StyleGroupsAlgorithm> {
1142 let Some(config) = config else {
1143 return Ok(StyleGroupsAlgorithm::Default);
1144 };
1145 Ok(match config.normalize() {
1146 CssChunkingObject::None => {
1147 anyhow::bail!(
1148 "`experimental.cssChunking: false` is not supported by Turbopack; this should \
1149 have been rejected at config validation time"
1150 )
1151 }
1152 CssChunkingObject::Strict => {
1153 anyhow::bail!(
1154 "`experimental.cssChunking: \"strict\"` is not supported by Turbopack; this \
1155 should have been rejected at config validation time"
1156 )
1157 }
1158 CssChunkingObject::Loose => StyleGroupsAlgorithm::Default,
1159 CssChunkingObject::Graph(opts) => StyleGroupsAlgorithm::graph(
1160 opts.request_cost.unwrap_or(DEFAULT_REQUEST_COST),
1161 opts.module_factor_cost
1162 .unwrap_or(DEFAULT_MODULE_FACTOR_COST),
1163 ),
1164 })
1165}
1166
1167#[derive(
1168 Clone,
1169 Debug,
1170 Default,
1171 PartialEq,
1172 Deserialize,
1173 TraceRawVcs,
1174 ValueDebugFormat,
1175 NonLocalValue,
1176 OperationValue,
1177 Encode,
1178 Decode,
1179)]
1180#[serde(rename_all = "camelCase")]
1181pub struct ExperimentalConfig {
1182 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1185 client_router_filter: Option<bool>,
1186 client_router_filter_allowed_rate: Option<f64>,
1189 client_router_filter_redirects: Option<bool>,
1190 fetch_cache_key_prefix: Option<RcStr>,
1191 isr_flush_to_disk: Option<bool>,
1192 mdx_rs: Option<MdxRsOptions>,
1195 strict_next_head: Option<bool>,
1196 #[bincode(with = "turbo_bincode::serde_self_describing")]
1197 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
1198 swc_env_options: Option<SwcEnvOptions>,
1199 external_middleware_rewrites_resolve: Option<bool>,
1200 scroll_restoration: Option<bool>,
1201 manual_client_base_path: Option<bool>,
1202 optimistic_client_cache: Option<bool>,
1203 middleware_prefetch: Option<MiddlewarePrefetchType>,
1204 #[bincode(with = "turbo_bincode::serde_self_describing")]
1207 optimize_css: Option<serde_json::Value>,
1208 next_script_workers: Option<bool>,
1209 web_vitals_attribution: Option<Vec<RcStr>>,
1210 server_actions: Option<ServerActionsOrLegacyBool>,
1211 sri: Option<SubResourceIntegrity>,
1212 cache_components: Option<bool>,
1215 use_cache: Option<bool>,
1216 runtime_server_deployment_id: Option<bool>,
1217 supports_immutable_assets: Option<bool>,
1218
1219 output_hash_salt: Option<RcStr>,
1222
1223 css_chunking: Option<CssChunkingConfig>,
1225
1226 adjust_font_fallbacks: Option<bool>,
1230 adjust_font_fallbacks_with_size_adjust: Option<bool>,
1231 after: Option<bool>,
1232 app_document_preloading: Option<bool>,
1233 app_new_scroll_handler: Option<bool>,
1234 case_sensitive_routes: Option<bool>,
1235 cpus: Option<f64>,
1236 cra_compat: Option<bool>,
1237 disable_optimized_loading: Option<bool>,
1238 disable_postcss_preset_env: Option<bool>,
1239 esm_externals: Option<EsmExternals>,
1240 #[bincode(with = "turbo_bincode::serde_self_describing")]
1241 extension_alias: Option<serde_json::Value>,
1242 external_dir: Option<bool>,
1243 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
1248 fully_specified: Option<bool>,
1249 gzip_size: Option<bool>,
1250
1251 inline_css: Option<bool>,
1252 instrumentation_hook: Option<bool>,
1253 client_trace_metadata: Option<Vec<String>>,
1254 large_page_data_bytes: Option<f64>,
1255 #[bincode(with = "turbo_bincode::serde_self_describing")]
1256 logging: Option<serde_json::Value>,
1257 memory_based_workers_count: Option<bool>,
1258 optimize_server_react: Option<bool>,
1260 optimize_package_imports: Option<Vec<RcStr>>,
1263 taint: Option<bool>,
1264 proxy_timeout: Option<f64>,
1265 server_minification: Option<bool>,
1267 server_source_maps: Option<bool>,
1269 swc_trace_profiling: Option<bool>,
1270 transition_indicator: Option<bool>,
1271 gesture_transition: Option<bool>,
1272 #[serde(rename = "blockingSSR")]
1275 blocking_ssr: Option<bool>,
1276 trust_host_header: Option<bool>,
1278
1279 #[bincode(with = "turbo_bincode::serde_self_describing")]
1280 url_imports: Option<serde_json::Value>,
1281 webpack_build_worker: Option<bool>,
1284 worker_threads: Option<bool>,
1285
1286 turbopack_minify: Option<bool>,
1287 turbopack_module_ids: Option<ModuleIds>,
1288 turbopack_plugin_runtime_strategy: Option<TurbopackPluginRuntimeStrategy>,
1289 turbopack_source_maps: Option<bool>,
1290 turbopack_input_source_maps: Option<bool>,
1291 turbopack_tree_shaking: Option<bool>,
1292 turbopack_scope_hoisting: Option<bool>,
1293 turbopack_worker_asset_prefix: Option<RcStr>,
1304 turbopack_client_side_nested_async_chunking: Option<bool>,
1305 turbopack_server_side_nested_async_chunking: Option<bool>,
1306 turbopack_import_type_bytes: Option<bool>,
1307 turbopack_import_type_text: Option<bool>,
1308 #[serde(default)]
1310 turbopack_use_builtin_sass: Option<bool>,
1311 #[serde(default)]
1314 turbopack_use_builtin_babel: Option<bool>,
1315 #[serde(default)]
1319 turbopack_local_postcss_config: Option<bool>,
1320 global_not_found: Option<bool>,
1322 turbopack_rust_react_compiler: Option<bool>,
1324 turbopack_remove_unused_imports: Option<bool>,
1326 turbopack_remove_unused_exports: Option<bool>,
1328 turbopack_infer_module_side_effects: Option<bool>,
1330 devtool_segment_explorer: Option<bool>,
1332 report_system_env_inlining: Option<String>,
1334 lightning_css_features: Option<LightningCssFeatures>,
1338}
1339
1340#[derive(
1341 Clone,
1342 Debug,
1343 PartialEq,
1344 Eq,
1345 Deserialize,
1346 TraceRawVcs,
1347 NonLocalValue,
1348 OperationValue,
1349 Encode,
1350 Decode,
1351)]
1352#[serde(rename_all = "camelCase")]
1353pub struct SubResourceIntegrity {
1354 pub algorithm: Option<RcStr>,
1355}
1356
1357#[derive(
1358 Clone,
1359 Debug,
1360 Default,
1361 PartialEq,
1362 Eq,
1363 Deserialize,
1364 TraceRawVcs,
1365 NonLocalValue,
1366 OperationValue,
1367 Encode,
1368 Decode,
1369)]
1370#[serde(rename_all = "camelCase")]
1371pub struct LightningCssFeatures {
1372 pub include: Option<Vec<RcStr>>,
1373 pub exclude: Option<Vec<RcStr>>,
1374}
1375
1376#[derive(
1377 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1378)]
1379#[serde(untagged)]
1380pub enum ServerActionsOrLegacyBool {
1381 ServerActionsConfig(ServerActions),
1383
1384 LegacyBool(bool),
1387}
1388
1389#[derive(
1390 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1391)]
1392#[serde(rename_all = "kebab-case")]
1393pub enum EsmExternalsValue {
1394 Loose,
1395}
1396
1397#[derive(
1398 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1399)]
1400#[serde(untagged)]
1401pub enum EsmExternals {
1402 Loose(EsmExternalsValue),
1403 Bool(bool),
1404}
1405
1406#[test]
1408fn test_esm_externals_deserialization() {
1409 let json = serde_json::json!({
1410 "esmExternals": true
1411 });
1412 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1413 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1414
1415 let json = serde_json::json!({
1416 "esmExternals": "loose"
1417 });
1418 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1419 assert_eq!(
1420 config.esm_externals,
1421 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1422 );
1423}
1424
1425#[derive(
1426 Clone,
1427 Debug,
1428 Default,
1429 PartialEq,
1430 Eq,
1431 Deserialize,
1432 TraceRawVcs,
1433 NonLocalValue,
1434 OperationValue,
1435 Encode,
1436 Decode,
1437)]
1438#[serde(rename_all = "camelCase")]
1439pub struct ServerActions {
1440 pub body_size_limit: Option<SizeLimit>,
1442}
1443
1444#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1445#[serde(untagged)]
1446pub enum SizeLimit {
1447 Number(f64),
1448 WithUnit(String),
1449}
1450
1451impl PartialEq for SizeLimit {
1454 fn eq(&self, other: &Self) -> bool {
1455 match (self, other) {
1456 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1457 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1458 _ => false,
1459 }
1460 }
1461}
1462
1463impl Eq for SizeLimit {}
1464
1465#[derive(
1466 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1467)]
1468#[serde(rename_all = "kebab-case")]
1469pub enum MiddlewarePrefetchType {
1470 Strict,
1471 Flexible,
1472}
1473
1474#[derive(
1475 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1476)]
1477#[serde(untagged)]
1478pub enum EmotionTransformOptionsOrBoolean {
1479 Boolean(bool),
1480 Options(EmotionTransformConfig),
1481}
1482
1483impl EmotionTransformOptionsOrBoolean {
1484 pub fn is_enabled(&self) -> bool {
1485 match self {
1486 Self::Boolean(enabled) => *enabled,
1487 _ => true,
1488 }
1489 }
1490}
1491
1492#[derive(
1493 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1494)]
1495#[serde(untagged)]
1496pub enum StyledComponentsTransformOptionsOrBoolean {
1497 Boolean(bool),
1498 Options(StyledComponentsTransformConfig),
1499}
1500
1501impl StyledComponentsTransformOptionsOrBoolean {
1502 pub fn is_enabled(&self) -> bool {
1503 match self {
1504 Self::Boolean(enabled) => *enabled,
1505 _ => true,
1506 }
1507 }
1508}
1509
1510#[turbo_tasks::value(eq = "manual")]
1511#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1512#[serde(rename_all = "camelCase")]
1513pub struct CompilerConfig {
1514 pub react_remove_properties: Option<ReactRemoveProperties>,
1515 pub relay: Option<RelayConfig>,
1516 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1517 pub remove_console: Option<RemoveConsoleConfig>,
1518 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1519}
1520
1521#[derive(
1522 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1523)]
1524#[serde(untagged, rename_all = "camelCase")]
1525pub enum ReactRemoveProperties {
1526 Boolean(bool),
1527 Config { properties: Option<Vec<String>> },
1528}
1529
1530impl ReactRemoveProperties {
1531 pub fn is_enabled(&self) -> bool {
1532 match self {
1533 Self::Boolean(enabled) => *enabled,
1534 _ => true,
1535 }
1536 }
1537}
1538
1539#[derive(
1540 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1541)]
1542#[serde(untagged)]
1543pub enum RemoveConsoleConfig {
1544 Boolean(bool),
1545 Config { exclude: Option<Vec<String>> },
1546}
1547
1548impl RemoveConsoleConfig {
1549 pub fn is_enabled(&self) -> bool {
1550 match self {
1551 Self::Boolean(enabled) => *enabled,
1552 _ => true,
1553 }
1554 }
1555}
1556
1557#[turbo_tasks::value(transparent)]
1558pub struct ResolveExtensions(Option<Vec<RcStr>>);
1559
1560#[turbo_tasks::value(transparent)]
1561pub struct SwcPlugins(
1562 #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1563);
1564
1565#[derive(
1567 Clone,
1568 Debug,
1569 Default,
1570 PartialEq,
1571 Eq,
1572 Serialize,
1573 Deserialize,
1574 TraceRawVcs,
1575 NonLocalValue,
1576 OperationValue,
1577 Encode,
1578 Decode,
1579)]
1580#[serde(rename_all = "camelCase")]
1581pub struct SwcEnvOptions {
1582 pub mode: Option<RcStr>,
1583 pub core_js: Option<RcStr>,
1584 pub skip: Option<Vec<RcStr>>,
1585 pub include: Option<Vec<RcStr>>,
1586 pub exclude: Option<Vec<RcStr>>,
1587 pub shipped_proposals: Option<bool>,
1588 pub force_all_transforms: Option<bool>,
1589 pub debug: Option<bool>,
1590 pub loose: Option<bool>,
1591}
1592
1593#[turbo_tasks::value(transparent)]
1594pub struct OptionSwcEnvOptions(Option<SwcEnvOptions>);
1595
1596#[turbo_tasks::value(transparent)]
1597pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1598
1599#[turbo_tasks::value(transparent)]
1600
1601pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1602
1603#[turbo_tasks::value(transparent)]
1604pub struct OptionFileSystemPath(Option<FileSystemPath>);
1605
1606#[turbo_tasks::value(transparent)]
1607pub struct IgnoreIssues(Box<[IgnoreIssue]>);
1608
1609#[turbo_tasks::value(transparent)]
1610pub struct OptionJsonValue(
1611 #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1612);
1613
1614fn turbopack_config_documentation_link() -> RcStr {
1615 rcstr!(
1616 "https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders"
1617 )
1618}
1619
1620#[turbo_tasks::value(shared)]
1621struct InvalidLoaderRuleRenameAsIssue {
1622 glob: RcStr,
1623 rename_as: RcStr,
1624 config_file_path: FileSystemPath,
1625}
1626
1627#[async_trait]
1628#[turbo_tasks::value_impl]
1629impl Issue for InvalidLoaderRuleRenameAsIssue {
1630 async fn file_path(&self) -> Result<FileSystemPath> {
1631 Ok(self.config_file_path.clone())
1632 }
1633
1634 fn stage(&self) -> IssueStage {
1635 IssueStage::Config
1636 }
1637
1638 async fn title(&self) -> Result<StyledString> {
1639 Ok(StyledString::Text(
1640 format!("Invalid loader rule for extension: {}", self.glob).into(),
1641 ))
1642 }
1643
1644 async fn description(&self) -> Result<Option<StyledString>> {
1645 Ok(Some(StyledString::Text(RcStr::from(format!(
1646 "The extension {} contains a wildcard, but the `as` option does not: {}",
1647 self.glob, self.rename_as,
1648 )))))
1649 }
1650
1651 fn documentation_link(&self) -> RcStr {
1652 turbopack_config_documentation_link()
1653 }
1654}
1655
1656#[turbo_tasks::value(shared)]
1657struct InvalidLoaderRuleConditionIssue {
1658 error_string: RcStr,
1659 condition: ConfigConditionItem,
1660 config_file_path: FileSystemPath,
1661}
1662
1663#[async_trait]
1664#[turbo_tasks::value_impl]
1665impl Issue for InvalidLoaderRuleConditionIssue {
1666 async fn file_path(&self) -> Result<FileSystemPath> {
1667 Ok(self.config_file_path.clone())
1668 }
1669
1670 fn stage(&self) -> IssueStage {
1671 IssueStage::Config
1672 }
1673
1674 async fn title(&self) -> Result<StyledString> {
1675 Ok(StyledString::Text(rcstr!(
1676 "Invalid condition for Turbopack loader rule"
1677 )))
1678 }
1679
1680 async fn description(&self) -> Result<Option<StyledString>> {
1681 Ok(Some(StyledString::Stack(vec![
1682 StyledString::Line(vec![
1683 StyledString::Text(rcstr!("Encountered the following error: ")),
1684 StyledString::Code(self.error_string.clone()),
1685 ]),
1686 StyledString::Text(rcstr!("While processing the condition:")),
1687 StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1688 ])))
1689 }
1690
1691 fn documentation_link(&self) -> RcStr {
1692 turbopack_config_documentation_link()
1693 }
1694}
1695
1696#[turbo_tasks::value(transparent)]
1697pub struct OutputFileTracingIncludesExcludes(
1698 #[bincode(with = "turbo_bincode::indexmap")]
1699 FxIndexMap<ResolvedVc<Glob>, Vec<(RcStr, FileSystemPath)>>,
1700);
1701
1702impl OutputFileTracingIncludesExcludes {
1703 pub async fn parse(
1704 project_path: FileSystemPath,
1705 value: &Option<serde_json::Value>,
1706 ) -> Result<OutputFileTracingIncludesExcludes> {
1707 if let Some(value) = value
1708 && let Some(map) = value.as_object()
1709 {
1710 Ok(OutputFileTracingIncludesExcludes(
1711 map.iter()
1712 .map(async |(route_pattern, file_patterns)| {
1713 let route_pattern = Glob::new(
1714 RcStr::from(route_pattern.clone()),
1715 GlobOptions { contains: true },
1716 )
1717 .to_resolved()
1718 .await?;
1719 let file_patterns = file_patterns
1720 .as_array()
1721 .iter()
1722 .flat_map(|pattern| pattern.iter())
1723 .filter_map(|pattern| pattern.as_str())
1724 .map(async |pattern_str| {
1725 let (glob, root) = relativize_glob(pattern_str, &project_path)?;
1726 Ok((RcStr::from(glob), root))
1727 })
1728 .try_join()
1729 .await?;
1730 Ok((route_pattern, file_patterns))
1731 })
1732 .try_join()
1733 .await?
1734 .into_iter()
1735 .collect(),
1736 ))
1737 } else {
1738 Ok(OutputFileTracingIncludesExcludes(FxIndexMap::default()))
1739 }
1740 }
1741}
1742
1743#[turbo_tasks::value_impl]
1744impl NextConfig {
1745 #[turbo_tasks::function]
1746 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1747 let string = string.await?;
1748 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1749 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1750 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1751 Ok(config.cell())
1752 }
1753
1754 #[turbo_tasks::function]
1755 pub async fn config_file_path(
1756 &self,
1757 project_path: FileSystemPath,
1758 ) -> Result<Vc<FileSystemPath>> {
1759 Ok(project_path.join(&self.config_file_name)?.cell())
1760 }
1761
1762 #[turbo_tasks::function]
1763 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1764 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1765 }
1766
1767 #[turbo_tasks::function]
1768 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1769 Vc::cell(self.react_production_profiling.unwrap_or_default())
1770 }
1771
1772 #[turbo_tasks::function]
1773 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1774 Vc::cell(
1775 self.server_external_packages
1776 .as_ref()
1777 .cloned()
1778 .unwrap_or_default(),
1779 )
1780 }
1781
1782 #[turbo_tasks::function]
1783 pub fn is_standalone(&self) -> Vc<bool> {
1784 Vc::cell(self.output == Some(OutputType::Standalone))
1785 }
1786
1787 #[turbo_tasks::function]
1788 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1789 Vc::cell(self.base_path.clone())
1790 }
1791
1792 #[turbo_tasks::function]
1793 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1794 if let Some(handler) = &self.cache_handler {
1795 Ok(Vc::cell(Some(project_path.join(handler)?)))
1796 } else {
1797 Ok(Vc::cell(None))
1798 }
1799 }
1800
1801 #[turbo_tasks::function]
1802 pub fn compiler(&self) -> Vc<CompilerConfig> {
1803 self.compiler.clone().unwrap_or_default().cell()
1804 }
1805
1806 #[turbo_tasks::function]
1807 pub fn env(&self) -> Vc<EnvMap> {
1808 let env = self
1812 .env
1813 .iter()
1814 .map(|(k, v)| {
1815 (
1816 k.as_str().into(),
1817 if let JsonValue::String(s) = v {
1818 s.as_str().into()
1820 } else {
1821 v.to_string().into()
1822 },
1823 )
1824 })
1825 .collect();
1826
1827 Vc::cell(env)
1828 }
1829
1830 #[turbo_tasks::function]
1831 pub fn image_config(&self) -> Vc<ImageConfig> {
1832 self.images.clone().cell()
1833 }
1834
1835 #[turbo_tasks::function]
1836 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1837 let mut extensions = self.page_extensions.clone();
1841 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1842 Vc::cell(extensions)
1843 }
1844
1845 #[turbo_tasks::function]
1846 pub fn instrumentation_client_inject(&self) -> Vc<Vec<RcStr>> {
1847 Vc::cell(
1848 self.instrumentation_client_inject
1849 .clone()
1850 .unwrap_or_default(),
1851 )
1852 }
1853
1854 #[turbo_tasks::function]
1855 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1856 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1857 }
1858
1859 #[turbo_tasks::function]
1860 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1861 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1862 }
1863
1864 #[turbo_tasks::function]
1865 pub async fn webpack_rules(
1866 self: Vc<Self>,
1867 project_path: FileSystemPath,
1868 ) -> Result<Vc<WebpackRules>> {
1869 let this = self.await?;
1870 let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1871 return Ok(Vc::cell(Vec::new()));
1872 };
1873 if turbo_rules.is_empty() {
1874 return Ok(Vc::cell(Vec::new()));
1875 }
1876 let mut rules = Vec::new();
1877 for (glob, rule_collection) in turbo_rules.iter() {
1878 fn transform_loaders(
1879 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1880 ) -> ResolvedVc<WebpackLoaderItems> {
1881 ResolvedVc::cell(
1882 loaders
1883 .map(|item| match item {
1884 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1885 loader: name.clone(),
1886 options: Default::default(),
1887 },
1888 LoaderItem::LoaderOptions(options) => options.clone(),
1889 })
1890 .collect(),
1891 )
1892 }
1893 for item in &rule_collection.0 {
1894 match item {
1895 RuleConfigCollectionItem::Shorthand(loaders) => {
1896 rules.push((
1897 glob.clone(),
1898 LoaderRuleItem {
1899 loaders: transform_loaders(&mut [loaders].into_iter()),
1900 rename_as: None,
1901 condition: None,
1902 module_type: None,
1903 },
1904 ));
1905 }
1906 RuleConfigCollectionItem::Full(RuleConfigItem {
1907 loaders,
1908 rename_as,
1909 condition,
1910 module_type,
1911 }) => {
1912 if glob.contains("*")
1916 && let Some(rename_as) = rename_as.as_ref()
1917 && !rename_as.contains("*")
1918 {
1919 InvalidLoaderRuleRenameAsIssue {
1920 glob: glob.clone(),
1921 config_file_path: self
1922 .config_file_path(project_path.clone())
1923 .owned()
1924 .await?,
1925 rename_as: rename_as.clone(),
1926 }
1927 .resolved_cell()
1928 .emit();
1929 }
1930
1931 let condition = if let Some(condition) = condition {
1934 match ConditionItem::try_from(condition.clone()) {
1935 Ok(cond) => Some(cond),
1936 Err(err) => {
1937 InvalidLoaderRuleConditionIssue {
1938 error_string: RcStr::from(err.to_string()),
1939 condition: condition.clone(),
1940 config_file_path: self
1941 .config_file_path(project_path.clone())
1942 .owned()
1943 .await?,
1944 }
1945 .resolved_cell()
1946 .emit();
1947 None
1948 }
1949 }
1950 } else {
1951 None
1952 };
1953 rules.push((
1954 glob.clone(),
1955 LoaderRuleItem {
1956 loaders: transform_loaders(&mut loaders.iter()),
1957 rename_as: rename_as.clone(),
1958 condition,
1959 module_type: module_type.clone(),
1960 },
1961 ));
1962 }
1963 }
1964 }
1965 }
1966 Ok(Vc::cell(rules))
1967 }
1968
1969 #[turbo_tasks::function]
1970 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1971 let Some(resolve_alias) = self
1972 .turbopack
1973 .as_ref()
1974 .and_then(|t| t.resolve_alias.as_ref())
1975 else {
1976 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1977 };
1978 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1979 Ok(alias_map.cell())
1980 }
1981
1982 #[turbo_tasks::function]
1983 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1984 let Some(resolve_extensions) = self
1985 .turbopack
1986 .as_ref()
1987 .and_then(|t| t.resolve_extensions.as_ref())
1988 else {
1989 return Vc::cell(None);
1990 };
1991 Vc::cell(Some(resolve_extensions.clone()))
1992 }
1993
1994 #[turbo_tasks::function]
1995 pub fn import_externals(&self) -> Result<Vc<bool>> {
1996 Ok(Vc::cell(match self.experimental.esm_externals {
1997 Some(EsmExternals::Bool(b)) => b,
1998 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1999 None => true,
2000 }))
2001 }
2002
2003 #[turbo_tasks::function]
2004 pub fn inline_css(&self) -> Vc<bool> {
2005 Vc::cell(self.experimental.inline_css.unwrap_or(false))
2006 }
2007
2008 #[turbo_tasks::function]
2011 pub fn css_chunking(&self) -> Result<Vc<StyleGroupsAlgorithm>> {
2012 Ok(resolve_css_chunking_algorithm(self.experimental.css_chunking.as_ref())?.cell())
2013 }
2014
2015 #[turbo_tasks::function]
2016 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
2017 let options = &self.experimental.mdx_rs;
2018
2019 let options = match options {
2020 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
2021 MdxTransformOptions {
2022 provider_import_source: Some(mdx_import_source_file()),
2023 ..Default::default()
2024 }
2025 .resolved_cell(),
2026 )),
2027 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
2028 MdxTransformOptions {
2029 provider_import_source: Some(
2030 options
2031 .provider_import_source
2032 .clone()
2033 .unwrap_or(mdx_import_source_file()),
2034 ),
2035 ..options.clone()
2036 }
2037 .resolved_cell(),
2038 )),
2039 _ => OptionalMdxTransformOptions(None),
2040 };
2041
2042 options.cell()
2043 }
2044
2045 #[turbo_tasks::function]
2046 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
2047 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
2048 }
2049
2050 #[turbo_tasks::function]
2051 pub fn dist_dir(&self) -> Vc<RcStr> {
2052 Vc::cell(self.dist_dir.clone())
2053 }
2054 #[turbo_tasks::function]
2055 pub fn dist_dir_root(&self) -> Vc<RcStr> {
2056 Vc::cell(self.dist_dir_root.clone())
2057 }
2058
2059 #[turbo_tasks::function]
2060 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
2061 if let Some(handlers) = &self.cache_handlers {
2062 Ok(Vc::cell(
2063 handlers
2064 .values()
2065 .map(|h| project_path.join(h))
2066 .collect::<Result<Vec<_>>>()?,
2067 ))
2068 } else {
2069 Ok(Vc::cell(vec![]))
2070 }
2071 }
2072
2073 #[turbo_tasks::function]
2074 pub fn cache_handlers_map(&self) -> Vc<CacheHandlersMap> {
2075 Vc::cell(self.cache_handlers.clone().unwrap_or_default())
2076 }
2077
2078 #[turbo_tasks::function]
2079 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
2080 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
2081 }
2082
2083 #[turbo_tasks::function]
2084 pub fn experimental_swc_env_options(&self) -> Vc<OptionSwcEnvOptions> {
2085 Vc::cell(self.experimental.swc_env_options.clone())
2086 }
2087
2088 #[turbo_tasks::function]
2089 pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
2090 Vc::cell(self.experimental.sri.clone())
2091 }
2092
2093 #[turbo_tasks::function]
2094 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
2095 Vc::cell(self.experimental.turbopack_use_builtin_babel)
2096 }
2097
2098 #[turbo_tasks::function]
2099 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
2100 Vc::cell(self.experimental.turbopack_use_builtin_sass)
2101 }
2102
2103 #[turbo_tasks::function]
2104 pub fn experimental_turbopack_local_postcss_config(&self) -> Vc<Option<bool>> {
2105 Vc::cell(self.experimental.turbopack_local_postcss_config)
2106 }
2107
2108 #[turbo_tasks::function]
2109 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
2110 let options = &self.react_compiler;
2111
2112 let options = match options {
2113 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
2114 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
2115 }
2116 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
2117 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
2118 ),
2119 _ => OptionalReactCompilerOptions(None),
2120 };
2121
2122 options.cell()
2123 }
2124
2125 #[turbo_tasks::function]
2128 pub fn rust_react_compiler(&self) -> Vc<OptionReactCompilerCompilationMode> {
2129 let use_rust = self
2130 .experimental
2131 .turbopack_rust_react_compiler
2132 .unwrap_or(false);
2133 let mode = match (use_rust, &self.react_compiler) {
2134 (true, Some(ReactCompilerOptionsOrBoolean::Boolean(true))) => {
2135 Some(ReactCompilerCompilationMode::Infer)
2136 }
2137 (true, Some(ReactCompilerOptionsOrBoolean::Option(opts))) => {
2138 Some(opts.compilation_mode)
2139 }
2140 _ => None,
2141 };
2142 Vc::cell(mode)
2143 }
2144
2145 #[turbo_tasks::function]
2146 pub fn sass_config(&self) -> Vc<JsonValue> {
2147 Vc::cell(self.sass_options.clone().unwrap_or_default())
2148 }
2149
2150 #[turbo_tasks::function]
2151 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
2152 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
2153 }
2154
2155 #[turbo_tasks::function]
2156 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
2157 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
2158 }
2159
2160 #[turbo_tasks::function]
2163 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
2164 let this = self.await?;
2165
2166 Ok(Vc::cell(
2167 format!(
2168 "{}/_next/",
2169 if let Some(asset_prefix) = &this.asset_prefix {
2170 asset_prefix
2171 } else {
2172 this.base_path.as_ref().map_or("", |b| b.as_str())
2173 }
2174 .trim_end_matches('/')
2175 )
2176 .into(),
2177 ))
2178 }
2179
2180 #[turbo_tasks::function]
2182 pub fn asset_suffix_path(&self) -> Vc<Option<RcStr>> {
2183 let needs_dpl_id = self
2184 .experimental
2185 .supports_immutable_assets
2186 .is_none_or(|f| !f);
2187
2188 Vc::cell(
2189 needs_dpl_id
2190 .then_some(self.deployment_id.as_ref())
2191 .flatten()
2192 .map(|id| format!("?dpl={id}").into()),
2193 )
2194 }
2195
2196 #[turbo_tasks::function]
2199 pub fn enable_immutable_assets(&self) -> Vc<bool> {
2200 Vc::cell(self.experimental.supports_immutable_assets == Some(true))
2201 }
2202
2203 #[turbo_tasks::function]
2204 pub fn client_static_folder_name(&self) -> Vc<RcStr> {
2205 Vc::cell(
2206 if self.experimental.supports_immutable_assets == Some(true) {
2207 rcstr!("static/immutable")
2209 } else {
2210 rcstr!("static")
2211 },
2212 )
2213 }
2214
2215 #[turbo_tasks::function]
2216 pub fn enable_taint(&self) -> Vc<bool> {
2217 Vc::cell(self.experimental.taint.unwrap_or(false))
2218 }
2219
2220 #[turbo_tasks::function]
2221 pub fn enable_transition_indicator(&self) -> Vc<bool> {
2222 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
2223 }
2224
2225 #[turbo_tasks::function]
2226 pub fn enable_gesture_transition(&self) -> Vc<bool> {
2227 Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
2228 }
2229
2230 #[turbo_tasks::function]
2231 pub fn enable_blocking_ssr(&self) -> Vc<bool> {
2232 Vc::cell(self.experimental.blocking_ssr.unwrap_or(false))
2233 }
2234
2235 #[turbo_tasks::function]
2236 pub fn enable_cache_components(&self) -> Vc<bool> {
2237 Vc::cell(self.cache_components.unwrap_or(false))
2238 }
2239
2240 #[turbo_tasks::function]
2241 pub fn enable_use_cache(&self) -> Vc<bool> {
2242 Vc::cell(
2243 self.experimental
2244 .use_cache
2245 .unwrap_or(self.cache_components.unwrap_or(false)),
2249 )
2250 }
2251
2252 #[turbo_tasks::function]
2253 pub fn is_using_adapter(&self) -> Vc<bool> {
2254 Vc::cell(self.adapter_path.is_some())
2255 }
2256
2257 #[turbo_tasks::function]
2258 pub fn should_append_server_deployment_id_at_runtime(&self) -> Vc<bool> {
2259 let needs_dpl_id = self
2260 .experimental
2261 .supports_immutable_assets
2262 .is_none_or(|f| !f);
2263
2264 Vc::cell(
2265 needs_dpl_id
2266 && self
2267 .experimental
2268 .runtime_server_deployment_id
2269 .unwrap_or(false),
2270 )
2271 }
2272
2273 #[turbo_tasks::function]
2274 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
2275 let mut cache_kinds = CacheKinds::default();
2276
2277 if let Some(handlers) = self.cache_handlers.as_ref() {
2278 cache_kinds.extend(handlers.keys().cloned());
2279 }
2280
2281 cache_kinds.cell()
2282 }
2283
2284 #[turbo_tasks::function]
2285 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
2286 Vc::cell(
2287 self.experimental
2288 .optimize_package_imports
2289 .clone()
2290 .unwrap_or_default(),
2291 )
2292 }
2293
2294 #[turbo_tasks::function]
2295 pub fn tree_shaking_mode_for_foreign_code(
2296 &self,
2297 _is_development: bool,
2298 ) -> Vc<OptionTreeShaking> {
2299 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2300 Some(false) => Some(TreeShakingMode::ReexportsOnly),
2301 Some(true) => Some(TreeShakingMode::ModuleFragments),
2302 None => Some(TreeShakingMode::ReexportsOnly),
2303 })
2304 .cell()
2305 }
2306
2307 #[turbo_tasks::function]
2308 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
2309 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2310 Some(false) => Some(TreeShakingMode::ReexportsOnly),
2311 Some(true) => Some(TreeShakingMode::ModuleFragments),
2312 None => Some(TreeShakingMode::ReexportsOnly),
2313 })
2314 .cell()
2315 }
2316
2317 #[turbo_tasks::function]
2318 pub async fn turbopack_remove_unused_imports(
2319 self: Vc<Self>,
2320 mode: Vc<NextMode>,
2321 ) -> Result<Vc<bool>> {
2322 let remove_unused_imports = self
2323 .await?
2324 .experimental
2325 .turbopack_remove_unused_imports
2326 .unwrap_or(matches!(*mode.await?, NextMode::Build));
2327
2328 if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2329 bail!(
2330 "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2331 enabling `experimental.turbopackRemoveUnusedExports`"
2332 );
2333 }
2334
2335 Ok(Vc::cell(remove_unused_imports))
2336 }
2337
2338 #[turbo_tasks::function]
2339 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2340 Ok(Vc::cell(
2341 self.experimental
2342 .turbopack_remove_unused_exports
2343 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2344 ))
2345 }
2346
2347 #[turbo_tasks::function]
2348 pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2349 Vc::cell(
2350 self.experimental
2351 .turbopack_infer_module_side_effects
2352 .unwrap_or(true),
2353 )
2354 }
2355
2356 #[turbo_tasks::function]
2357 pub fn turbopack_plugin_runtime_strategy(&self) -> Vc<TurbopackPluginRuntimeStrategy> {
2358 #[cfg(feature = "process_pool")]
2359 let default = TurbopackPluginRuntimeStrategy::ChildProcesses;
2360 #[cfg(all(feature = "worker_pool", not(feature = "process_pool")))]
2361 let default = TurbopackPluginRuntimeStrategy::WorkerThreads;
2362
2363 self.experimental
2364 .turbopack_plugin_runtime_strategy
2365 .unwrap_or(default)
2366 .cell()
2367 }
2368
2369 #[turbo_tasks::function]
2370 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2371 Ok(match *mode.await? {
2372 NextMode::Development => ModuleIds::Named.cell(),
2374 NextMode::Build => self
2375 .experimental
2376 .turbopack_module_ids
2377 .unwrap_or(ModuleIds::Deterministic)
2378 .cell(),
2379 })
2380 }
2381
2382 #[turbo_tasks::function]
2383 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2384 let minify = self.experimental.turbopack_minify;
2385 Ok(Vc::cell(
2386 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2387 ))
2388 }
2389
2390 #[turbo_tasks::function]
2391 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2392 Ok(Vc::cell(match *mode.await? {
2393 NextMode::Development => false,
2395 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2396 }))
2397 }
2398
2399 #[turbo_tasks::function]
2400 pub async fn turbo_nested_async_chunking(
2401 &self,
2402 mode: Vc<NextMode>,
2403 client_side: bool,
2404 ) -> Result<Vc<bool>> {
2405 let option = if client_side {
2406 self.experimental
2407 .turbopack_client_side_nested_async_chunking
2408 } else {
2409 self.experimental
2410 .turbopack_server_side_nested_async_chunking
2411 };
2412 Ok(Vc::cell(if let Some(value) = option {
2413 value
2414 } else {
2415 match *mode.await? {
2416 NextMode::Development => false,
2417 NextMode::Build => client_side,
2418 }
2419 }))
2420 }
2421
2422 #[turbo_tasks::function]
2423 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2424 Vc::cell(
2425 self.experimental
2426 .turbopack_import_type_bytes
2427 .unwrap_or(false),
2428 )
2429 }
2430
2431 #[turbo_tasks::function]
2432 pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2433 Vc::cell(
2434 self.experimental
2435 .turbopack_import_type_text
2436 .unwrap_or(false),
2437 )
2438 }
2439
2440 #[turbo_tasks::function]
2441 pub fn lightningcss_feature_flags(
2442 &self,
2443 ) -> Result<Vc<turbopack_css::LightningCssFeatureFlags>> {
2444 Ok(turbopack_css::LightningCssFeatureFlags {
2445 include: lightningcss_features_field_mask(
2446 &self.experimental.lightning_css_features,
2447 |f| f.include.as_ref(),
2448 )?,
2449 exclude: lightningcss_features_field_mask(
2450 &self.experimental.lightning_css_features,
2451 |f| f.exclude.as_ref(),
2452 )?,
2453 }
2454 .cell())
2455 }
2456
2457 #[turbo_tasks::function]
2458 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2459 let input_source_maps = self
2460 .experimental
2461 .turbopack_input_source_maps
2462 .unwrap_or(true);
2463 let source_maps = self
2464 .experimental
2465 .turbopack_source_maps
2466 .unwrap_or(match &*mode.await? {
2467 NextMode::Development => true,
2468 NextMode::Build => self.production_browser_source_maps,
2469 });
2470 Ok(match (source_maps, input_source_maps) {
2471 (true, true) => SourceMapsType::Full,
2472 (true, false) => SourceMapsType::Partial,
2473 (false, _) => SourceMapsType::None,
2474 }
2475 .cell())
2476 }
2477
2478 #[turbo_tasks::function]
2479 pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2480 let input_source_maps = self
2481 .experimental
2482 .turbopack_input_source_maps
2483 .unwrap_or(true);
2484 let source_maps = self
2485 .experimental
2486 .turbopack_source_maps
2487 .or(self.experimental.server_source_maps)
2488 .unwrap_or(true);
2489 Ok(match (source_maps, input_source_maps) {
2490 (true, true) => SourceMapsType::Full,
2491 (true, false) => SourceMapsType::Partial,
2492 (false, _) => SourceMapsType::None,
2493 }
2494 .cell())
2495 }
2496
2497 #[turbo_tasks::function]
2498 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2499 Vc::cell(
2500 self.turbopack
2501 .as_ref()
2502 .and_then(|turbopack| turbopack.debug_ids)
2503 .unwrap_or(false),
2504 )
2505 }
2506
2507 #[turbo_tasks::function]
2510 pub fn turbopack_worker_asset_prefix(&self) -> Vc<Option<RcStr>> {
2511 Vc::cell(
2512 self.experimental
2513 .turbopack_worker_asset_prefix
2514 .as_ref()
2515 .map(|prefix| format!("{}/_next/", prefix.trim_end_matches('/')).into()),
2516 )
2517 }
2518
2519 #[turbo_tasks::function]
2520 pub fn turbopack_chunk_loading_global(&self) -> Vc<Option<RcStr>> {
2521 Vc::cell(
2522 self.turbopack
2523 .as_ref()
2524 .and_then(|t| t.chunk_loading_global.clone()),
2525 )
2526 }
2527
2528 #[turbo_tasks::function]
2529 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2530 Ok(Vc::cell(
2531 self.typescript
2532 .tsconfig_path
2533 .as_ref()
2534 .map(|path| path.to_owned().into()),
2535 ))
2536 }
2537
2538 #[turbo_tasks::function]
2539 pub fn cross_origin(&self) -> Vc<CrossOrigin> {
2540 *self.cross_origin.resolved_cell()
2541 }
2542
2543 #[turbo_tasks::function]
2544 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2545 Vc::cell(self.i18n.clone())
2546 }
2547
2548 #[turbo_tasks::function]
2549 pub fn output(&self) -> Vc<OptionOutputType> {
2550 Vc::cell(self.output.clone())
2551 }
2552
2553 #[turbo_tasks::function]
2554 pub async fn output_file_tracing_includes(
2555 &self,
2556 project_path: FileSystemPath,
2557 ) -> Result<Vc<OutputFileTracingIncludesExcludes>> {
2558 Ok(OutputFileTracingIncludesExcludes::parse(
2559 project_path,
2560 &self.output_file_tracing_includes,
2561 )
2562 .await?
2563 .cell())
2564 }
2565
2566 #[turbo_tasks::function]
2567 pub async fn output_file_tracing_excludes(
2568 &self,
2569 project_path: FileSystemPath,
2570 ) -> Result<Vc<OutputFileTracingIncludesExcludes>> {
2571 Ok(OutputFileTracingIncludesExcludes::parse(
2572 project_path,
2573 &self.output_file_tracing_excludes,
2574 )
2575 .await?
2576 .cell())
2577 }
2578
2579 #[turbo_tasks::function]
2580 pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2581 FetchClientConfig::default().cell()
2582 }
2583
2584 #[turbo_tasks::function]
2585 pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2586 match self.experimental.report_system_env_inlining.as_deref() {
2587 None => Ok(IssueSeverity::Suggestion.cell()),
2588 Some("warn") => Ok(IssueSeverity::Warning.cell()),
2589 Some("error") => Ok(IssueSeverity::Error.cell()),
2590 _ => bail!(
2591 "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2592 ),
2593 }
2594 }
2595
2596 #[turbo_tasks::function]
2599 pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2600 let rules = self
2601 .turbopack
2602 .as_ref()
2603 .and_then(|tp| tp.ignore_issue.as_deref())
2604 .unwrap_or_default()
2605 .iter()
2606 .map(|rule| {
2607 Ok(IgnoreIssue {
2608 path: rule.path.to_ignore_pattern()?,
2609 title: rule
2610 .title
2611 .as_ref()
2612 .map(|t| t.to_ignore_pattern())
2613 .transpose()?,
2614 description: rule
2615 .description
2616 .as_ref()
2617 .map(|d| d.to_ignore_pattern())
2618 .transpose()?,
2619 })
2620 })
2621 .collect::<Result<_>>()?;
2622 Ok(Vc::cell(rules))
2623 }
2624
2625 #[turbo_tasks::function]
2626 pub fn output_hash_salt(&self) -> Vc<RcStr> {
2627 Vc::cell(
2628 self.experimental
2629 .output_hash_salt
2630 .clone()
2631 .unwrap_or_default(),
2632 )
2633 }
2634}
2635
2636#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2639#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2640#[serde(rename_all = "camelCase")]
2641pub struct JsConfig {
2642 #[bincode(with = "turbo_bincode::serde_self_describing")]
2643 compiler_options: Option<serde_json::Value>,
2644}
2645
2646#[turbo_tasks::value_impl]
2647impl JsConfig {
2648 #[turbo_tasks::function]
2649 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2650 let string = string.await?;
2651 let config: JsConfig = serde_json::from_str(&string)
2652 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2653
2654 Ok(config.cell())
2655 }
2656
2657 #[turbo_tasks::function]
2658 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2659 Vc::cell(self.compiler_options.clone().unwrap_or_default())
2660 }
2661}
2662
2663fn lightningcss_features_field_mask(
2666 features: &Option<LightningCssFeatures>,
2667 field: impl FnOnce(&LightningCssFeatures) -> Option<&Vec<RcStr>>,
2668) -> Result<u32> {
2669 features
2670 .as_ref()
2671 .and_then(field)
2672 .map(|names| lightningcss_feature_names_to_mask(names))
2673 .unwrap_or(Ok(0))
2674}
2675
2676pub fn lightningcss_feature_names_to_mask(
2685 names: &[impl std::ops::Deref<Target = str>],
2686) -> Result<u32> {
2687 use lightningcss::targets::Features;
2688 let mut mask = Features::empty();
2689 for name in names {
2690 mask |= match &**name {
2691 "nesting" => Features::Nesting,
2692 "not-selector-list" => Features::NotSelectorList,
2693 "dir-selector" => Features::DirSelector,
2694 "lang-selector-list" => Features::LangSelectorList,
2695 "is-selector" => Features::IsSelector,
2696 "text-decoration-thickness-percent" => Features::TextDecorationThicknessPercent,
2697 "media-interval-syntax" => Features::MediaIntervalSyntax,
2698 "media-range-syntax" => Features::MediaRangeSyntax,
2699 "custom-media-queries" => Features::CustomMediaQueries,
2700 "clamp-function" => Features::ClampFunction,
2701 "color-function" => Features::ColorFunction,
2702 "oklab-colors" => Features::OklabColors,
2703 "lab-colors" => Features::LabColors,
2704 "p3-colors" => Features::P3Colors,
2705 "hex-alpha-colors" => Features::HexAlphaColors,
2706 "space-separated-color-notation" => Features::SpaceSeparatedColorNotation,
2707 "font-family-system-ui" => Features::FontFamilySystemUi,
2708 "double-position-gradients" => Features::DoublePositionGradients,
2709 "vendor-prefixes" => Features::VendorPrefixes,
2710 "logical-properties" => Features::LogicalProperties,
2711 "light-dark" => Features::LightDark,
2712 "selectors" => Features::Selectors,
2714 "media-queries" => Features::MediaQueries,
2715 "colors" => Features::Colors,
2716 _ => bail!("Unknown lightningcss feature: {}", &**name),
2717 };
2718 }
2719 Ok(mask.bits())
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724 use super::*;
2725
2726 #[test]
2727 fn test_serde_rule_config_item_options() {
2728 let json_value = serde_json::json!({
2729 "loaders": [],
2730 "as": "*.js",
2731 "condition": {
2732 "all": [
2733 "production",
2734 {"not": "foreign"},
2735 {"any": [
2736 "browser",
2737 {
2738 "path": { "type": "glob", "value": "*.svg"},
2739 "query": {
2740 "type": "regex",
2741 "value": {
2742 "source": "@someQuery",
2743 "flags": ""
2744 }
2745 },
2746 "content": {
2747 "source": "@someTag",
2748 "flags": ""
2749 }
2750 }
2751 ]},
2752 ],
2753 }
2754 });
2755
2756 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2757
2758 assert_eq!(
2759 rule_config,
2760 RuleConfigItem {
2761 loaders: vec![],
2762 rename_as: Some(rcstr!("*.js")),
2763 module_type: None,
2764 condition: Some(ConfigConditionItem::All(
2765 [
2766 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2767 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2768 WebpackLoaderBuiltinCondition::Foreign
2769 ))),
2770 ConfigConditionItem::Any(
2771 vec![
2772 ConfigConditionItem::Builtin(
2773 WebpackLoaderBuiltinCondition::Browser
2774 ),
2775 ConfigConditionItem::Base {
2776 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2777 content: Some(RegexComponents {
2778 source: rcstr!("@someTag"),
2779 flags: rcstr!(""),
2780 }),
2781 query: Some(ConfigConditionQuery::Regex(RegexComponents {
2782 source: rcstr!("@someQuery"),
2783 flags: rcstr!(""),
2784 })),
2785 content_type: None,
2786 },
2787 ]
2788 .into(),
2789 ),
2790 ]
2791 .into(),
2792 )),
2793 }
2794 );
2795 }
2796}