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