1use anyhow::{Context, Result, bail};
2use bincode::{Decode, Encode};
3use either::Either;
4use rustc_hash::FxHashSet;
5use serde::{Deserialize, Deserializer, Serialize};
6use serde_json::Value as JsonValue;
7use turbo_esregex::EsRegex;
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks::{
10 FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
11 trace::TraceRawVcs,
12};
13use turbo_tasks_env::EnvMap;
14use turbo_tasks_fetch::FetchClientConfig;
15use turbo_tasks_fs::{
16 FileSystemPath,
17 glob::{Glob, GlobOptions},
18};
19use turbopack::module_options::{
20 ConditionContentType, ConditionItem, ConditionPath, ConditionQuery, LoaderRuleItem,
21 WebpackRules, module_options_context::MdxTransformOptions,
22};
23use turbopack_core::{
24 chunk::SourceMapsType,
25 issue::{
26 IgnoreIssue, IgnoreIssuePattern, Issue, IssueExt, IssueSeverity, IssueStage,
27 OptionStyledString, StyledString,
28 },
29 resolve::ResolveAliasMap,
30};
31use turbopack_ecmascript::{OptionTreeShaking, TreeShakingMode};
32use turbopack_ecmascript_plugins::transform::{
33 emotion::EmotionTransformConfig, relay::RelayConfig,
34 styled_components::StyledComponentsTransformConfig,
35};
36use turbopack_node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItems};
37
38use crate::{
39 app_structure::FileSystemPathVec,
40 mode::NextMode,
41 next_import_map::mdx_import_source_file,
42 next_shared::{
43 transforms::ModularizeImportPackageConfig, webpack_rules::WebpackLoaderBuiltinCondition,
44 },
45};
46
47#[turbo_tasks::value(transparent)]
48pub struct ModularizeImports(
49 #[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<String, ModularizeImportPackageConfig>,
50);
51
52#[turbo_tasks::value(transparent)]
53#[derive(Clone, Debug)]
54pub struct CacheKinds(FxHashSet<RcStr>);
55
56impl CacheKinds {
57 pub fn extend<I: IntoIterator<Item = RcStr>>(&mut self, iter: I) {
58 self.0.extend(iter);
59 }
60}
61
62impl Default for CacheKinds {
63 fn default() -> Self {
64 CacheKinds(
65 ["default", "remote", "private"]
66 .iter()
67 .map(|&s| s.into())
68 .collect(),
69 )
70 }
71}
72
73#[turbo_tasks::value(eq = "manual")]
74#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
75#[serde(default, rename_all = "camelCase")]
76pub struct NextConfig {
77 config_file: Option<RcStr>,
80 config_file_name: RcStr,
81
82 cache_max_memory_size: Option<f64>,
86 cache_handler: Option<RcStr>,
88 #[bincode(with_serde)]
89 cache_handlers: Option<FxIndexMap<RcStr, RcStr>>,
90 #[bincode(with = "turbo_bincode::serde_self_describing")]
91 env: FxIndexMap<String, JsonValue>,
92 experimental: ExperimentalConfig,
93 images: ImageConfig,
94 page_extensions: Vec<RcStr>,
95 react_compiler: Option<ReactCompilerOptionsOrBoolean>,
96 react_production_profiling: Option<bool>,
97 react_strict_mode: Option<bool>,
98 transpile_packages: Option<Vec<RcStr>>,
99 #[bincode(with = "turbo_bincode::serde_self_describing")]
100 modularize_imports: Option<FxIndexMap<String, ModularizeImportPackageConfig>>,
101 dist_dir: RcStr,
102 dist_dir_root: RcStr,
103 deployment_id: Option<RcStr>,
104 #[bincode(with = "turbo_bincode::serde_self_describing")]
105 sass_options: Option<serde_json::Value>,
106 trailing_slash: Option<bool>,
107 asset_prefix: Option<RcStr>,
108 base_path: Option<RcStr>,
109 skip_proxy_url_normalize: Option<bool>,
110 skip_trailing_slash_redirect: Option<bool>,
111 i18n: Option<I18NConfig>,
112 cross_origin: Option<CrossOriginConfig>,
113 dev_indicators: Option<DevIndicatorsConfig>,
114 output: Option<OutputType>,
115 turbopack: Option<TurbopackConfig>,
116 production_browser_source_maps: bool,
117 #[bincode(with = "turbo_bincode::serde_self_describing")]
118 output_file_tracing_includes: Option<serde_json::Value>,
119 #[bincode(with = "turbo_bincode::serde_self_describing")]
120 output_file_tracing_excludes: Option<serde_json::Value>,
121 output_file_tracing_root: Option<RcStr>,
123
124 bundle_pages_router_dependencies: Option<bool>,
129
130 server_external_packages: Option<Vec<RcStr>>,
135
136 #[serde(rename = "_originalRedirects")]
137 original_redirects: Option<Vec<Redirect>>,
138
139 compiler: Option<CompilerConfig>,
141
142 optimize_fonts: Option<bool>,
143
144 clean_dist_dir: bool,
145 compress: bool,
146 eslint: EslintConfig,
147 exclude_default_moment_locales: bool,
148 generate_etags: bool,
149 http_agent_options: HttpAgentConfig,
150 on_demand_entries: OnDemandEntriesConfig,
151 powered_by_header: bool,
152 #[bincode(with = "turbo_bincode::serde_self_describing")]
153 public_runtime_config: FxIndexMap<String, serde_json::Value>,
154 #[bincode(with = "turbo_bincode::serde_self_describing")]
155 server_runtime_config: FxIndexMap<String, serde_json::Value>,
156 static_page_generation_timeout: f64,
157 target: Option<String>,
158 typescript: TypeScriptConfig,
159 use_file_system_public_routes: bool,
160 cache_components: Option<bool>,
161 }
168
169#[turbo_tasks::value_impl]
170impl NextConfig {
171 #[turbo_tasks::function]
172 pub fn with_analyze_config(&self) -> Vc<Self> {
173 let mut new = self.clone();
174 new.experimental.turbopack_source_maps = Some(true);
175 new.experimental.turbopack_input_source_maps = Some(false);
176 new.cell()
177 }
178}
179
180#[derive(
181 Clone,
182 Debug,
183 PartialEq,
184 Eq,
185 Serialize,
186 Deserialize,
187 TraceRawVcs,
188 NonLocalValue,
189 OperationValue,
190 Encode,
191 Decode,
192)]
193#[serde(rename_all = "kebab-case")]
194pub enum CrossOriginConfig {
195 Anonymous,
196 UseCredentials,
197}
198
199#[turbo_tasks::value(transparent)]
200pub struct OptionCrossOriginConfig(Option<CrossOriginConfig>);
201
202#[derive(
203 Clone,
204 Debug,
205 Default,
206 PartialEq,
207 Deserialize,
208 TraceRawVcs,
209 NonLocalValue,
210 OperationValue,
211 Encode,
212 Decode,
213)]
214#[serde(rename_all = "camelCase")]
215struct EslintConfig {
216 dirs: Option<Vec<String>>,
217 ignore_during_builds: Option<bool>,
218}
219
220#[derive(
221 Clone,
222 Debug,
223 Default,
224 PartialEq,
225 Deserialize,
226 TraceRawVcs,
227 NonLocalValue,
228 OperationValue,
229 Encode,
230 Decode,
231)]
232#[serde(rename_all = "kebab-case")]
233pub enum BuildActivityPositions {
234 #[default]
235 BottomRight,
236 BottomLeft,
237 TopRight,
238 TopLeft,
239}
240
241#[derive(
242 Clone,
243 Debug,
244 Default,
245 PartialEq,
246 Deserialize,
247 TraceRawVcs,
248 NonLocalValue,
249 OperationValue,
250 Encode,
251 Decode,
252)]
253#[serde(rename_all = "camelCase")]
254pub struct DevIndicatorsOptions {
255 pub build_activity_position: Option<BuildActivityPositions>,
256 pub position: Option<BuildActivityPositions>,
257}
258
259#[derive(
260 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
261)]
262#[serde(untagged)]
263pub enum DevIndicatorsConfig {
264 WithOptions(DevIndicatorsOptions),
265 Boolean(bool),
266}
267
268#[derive(
269 Clone,
270 Debug,
271 Default,
272 PartialEq,
273 Deserialize,
274 TraceRawVcs,
275 NonLocalValue,
276 OperationValue,
277 Encode,
278 Decode,
279)]
280#[serde(rename_all = "camelCase")]
281struct OnDemandEntriesConfig {
282 max_inactive_age: f64,
283 pages_buffer_length: f64,
284}
285
286#[derive(
287 Clone,
288 Debug,
289 Default,
290 PartialEq,
291 Deserialize,
292 TraceRawVcs,
293 NonLocalValue,
294 OperationValue,
295 Encode,
296 Decode,
297)]
298#[serde(rename_all = "camelCase")]
299struct HttpAgentConfig {
300 keep_alive: bool,
301}
302
303#[derive(
304 Clone,
305 Debug,
306 PartialEq,
307 Eq,
308 Deserialize,
309 TraceRawVcs,
310 NonLocalValue,
311 OperationValue,
312 Encode,
313 Decode,
314)]
315#[serde(rename_all = "camelCase")]
316pub struct DomainLocale {
317 pub default_locale: String,
318 pub domain: String,
319 pub http: Option<bool>,
320 pub locales: Option<Vec<String>>,
321}
322
323#[derive(
324 Clone,
325 Debug,
326 PartialEq,
327 Eq,
328 Deserialize,
329 TraceRawVcs,
330 NonLocalValue,
331 OperationValue,
332 Encode,
333 Decode,
334)]
335#[serde(rename_all = "camelCase")]
336pub struct I18NConfig {
337 pub default_locale: String,
338 pub domains: Option<Vec<DomainLocale>>,
339 pub locale_detection: Option<bool>,
340 pub locales: Vec<String>,
341}
342
343#[turbo_tasks::value(transparent)]
344pub struct OptionI18NConfig(Option<I18NConfig>);
345
346#[derive(
347 Clone,
348 Debug,
349 PartialEq,
350 Eq,
351 Deserialize,
352 TraceRawVcs,
353 NonLocalValue,
354 OperationValue,
355 Encode,
356 Decode,
357)]
358#[serde(rename_all = "kebab-case")]
359pub enum OutputType {
360 Standalone,
361 Export,
362}
363
364#[turbo_tasks::value(transparent)]
365pub struct OptionOutputType(Option<OutputType>);
366
367#[derive(
368 Debug,
369 Clone,
370 Hash,
371 Eq,
372 PartialEq,
373 Ord,
374 PartialOrd,
375 TaskInput,
376 TraceRawVcs,
377 Serialize,
378 Deserialize,
379 NonLocalValue,
380 OperationValue,
381 Encode,
382 Decode,
383)]
384#[serde(tag = "type", rename_all = "kebab-case")]
385pub enum RouteHas {
386 Header {
387 key: RcStr,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 value: Option<RcStr>,
390 },
391 Cookie {
392 key: RcStr,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 value: Option<RcStr>,
395 },
396 Query {
397 key: RcStr,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 value: Option<RcStr>,
400 },
401 Host {
402 value: RcStr,
403 },
404}
405
406#[derive(Clone, Debug, Default, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
407#[serde(rename_all = "camelCase")]
408pub struct HeaderValue {
409 pub key: RcStr,
410 pub value: RcStr,
411}
412
413#[derive(Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue)]
414#[serde(rename_all = "camelCase")]
415pub struct Header {
416 pub source: String,
417 pub base_path: Option<bool>,
418 pub locale: Option<bool>,
419 pub headers: Vec<HeaderValue>,
420 pub has: Option<Vec<RouteHas>>,
421 pub missing: Option<Vec<RouteHas>>,
422}
423
424#[derive(
425 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
426)]
427#[serde(rename_all = "camelCase")]
428pub enum RedirectStatus {
429 StatusCode(f64),
430 Permanent(bool),
431}
432
433#[derive(
434 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
435)]
436#[serde(rename_all = "camelCase")]
437pub struct Redirect {
438 pub source: String,
439 pub destination: String,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub base_path: Option<bool>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub locale: Option<bool>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub has: Option<Vec<RouteHas>>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub missing: Option<Vec<RouteHas>>,
448
449 #[serde(flatten)]
450 pub status: RedirectStatus,
451}
452
453#[derive(Clone, Debug)]
454pub struct Rewrite {
455 pub source: String,
456 pub destination: String,
457 pub base_path: Option<bool>,
458 pub locale: Option<bool>,
459 pub has: Option<Vec<RouteHas>>,
460 pub missing: Option<Vec<RouteHas>>,
461}
462
463#[derive(Clone, Debug)]
464pub struct Rewrites {
465 pub before_files: Vec<Rewrite>,
466 pub after_files: Vec<Rewrite>,
467 pub fallback: Vec<Rewrite>,
468}
469
470#[derive(
471 Clone,
472 Debug,
473 Default,
474 PartialEq,
475 Deserialize,
476 TraceRawVcs,
477 NonLocalValue,
478 OperationValue,
479 Encode,
480 Decode,
481)]
482#[serde(rename_all = "camelCase")]
483pub struct TypeScriptConfig {
484 pub ignore_build_errors: Option<bool>,
485 pub tsconfig_path: Option<String>,
486}
487
488#[turbo_tasks::value(eq = "manual", operation)]
489#[derive(Clone, Debug, PartialEq, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct ImageConfig {
492 pub device_sizes: Vec<u16>,
493 pub image_sizes: Vec<u16>,
494 pub path: String,
495 pub loader: ImageLoader,
496 #[serde(deserialize_with = "empty_string_is_none")]
497 pub loader_file: Option<String>,
498 pub domains: Vec<String>,
499 pub disable_static_images: bool,
500 #[serde(rename = "minimumCacheTTL")]
501 pub minimum_cache_ttl: u64,
502 pub formats: Vec<ImageFormat>,
503 #[serde(rename = "dangerouslyAllowSVG")]
504 pub dangerously_allow_svg: bool,
505 pub content_security_policy: String,
506 pub remote_patterns: Vec<RemotePattern>,
507 pub unoptimized: bool,
508}
509
510fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
511where
512 D: Deserializer<'de>,
513{
514 let o = Option::<String>::deserialize(deserializer)?;
515 Ok(o.filter(|s| !s.is_empty()))
516}
517
518impl Default for ImageConfig {
519 fn default() -> Self {
520 Self {
522 device_sizes: vec![640, 750, 828, 1080, 1200, 1920, 2048, 3840],
523 image_sizes: vec![32, 48, 64, 96, 128, 256, 384],
524 path: "/_next/image".to_string(),
525 loader: ImageLoader::Default,
526 loader_file: None,
527 domains: vec![],
528 disable_static_images: false,
529 minimum_cache_ttl: 60,
530 formats: vec![ImageFormat::Webp],
531 dangerously_allow_svg: false,
532 content_security_policy: "".to_string(),
533 remote_patterns: vec![],
534 unoptimized: false,
535 }
536 }
537}
538
539#[derive(
540 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
541)]
542#[serde(rename_all = "kebab-case")]
543pub enum ImageLoader {
544 Default,
545 Imgix,
546 Cloudinary,
547 Akamai,
548 Custom,
549}
550
551#[derive(
552 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
553)]
554pub enum ImageFormat {
555 #[serde(rename = "image/webp")]
556 Webp,
557 #[serde(rename = "image/avif")]
558 Avif,
559}
560
561#[derive(
562 Clone,
563 Debug,
564 Default,
565 PartialEq,
566 Deserialize,
567 TraceRawVcs,
568 NonLocalValue,
569 OperationValue,
570 Encode,
571 Decode,
572)]
573#[serde(rename_all = "camelCase")]
574pub struct RemotePattern {
575 pub hostname: String,
576 #[serde(skip_serializing_if = "Option::is_none")]
577 pub protocol: Option<RemotePatternProtocol>,
578 #[serde(skip_serializing_if = "Option::is_none")]
579 pub port: Option<String>,
580 #[serde(skip_serializing_if = "Option::is_none")]
581 pub pathname: Option<String>,
582}
583
584#[derive(
585 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
586)]
587#[serde(rename_all = "kebab-case")]
588pub enum RemotePatternProtocol {
589 Http,
590 Https,
591}
592
593#[derive(
594 Clone,
595 Debug,
596 Default,
597 PartialEq,
598 Deserialize,
599 TraceRawVcs,
600 NonLocalValue,
601 OperationValue,
602 Encode,
603 Decode,
604)]
605#[serde(rename_all = "camelCase")]
606pub struct TurbopackConfig {
607 #[serde(default)]
608 #[bincode(with = "turbo_bincode::indexmap")]
609 pub rules: FxIndexMap<RcStr, RuleConfigCollection>,
610 #[bincode(with = "turbo_bincode::serde_self_describing")]
611 pub resolve_alias: Option<FxIndexMap<RcStr, JsonValue>>,
612 pub resolve_extensions: Option<Vec<RcStr>>,
613 pub debug_ids: Option<bool>,
614 #[serde(default)]
616 pub ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
617}
618
619#[derive(
620 Deserialize,
621 Clone,
622 PartialEq,
623 Eq,
624 Debug,
625 TraceRawVcs,
626 NonLocalValue,
627 OperationValue,
628 Encode,
629 Decode,
630)]
631#[serde(deny_unknown_fields)]
632pub struct RegexComponents {
633 source: RcStr,
634 flags: RcStr,
635}
636
637#[derive(
642 Clone,
643 PartialEq,
644 Eq,
645 Debug,
646 Deserialize,
647 TraceRawVcs,
648 NonLocalValue,
649 OperationValue,
650 Encode,
651 Decode,
652)]
653#[serde(
654 tag = "type",
655 content = "value",
656 rename_all = "camelCase",
657 deny_unknown_fields
658)]
659pub enum ConfigConditionPath {
660 Glob(RcStr),
661 Regex(RegexComponents),
662}
663
664impl TryFrom<ConfigConditionPath> for ConditionPath {
665 type Error = anyhow::Error;
666
667 fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
668 Ok(match config {
669 ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
670 ConfigConditionPath::Regex(path) => {
671 ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
672 }
673 })
674 }
675}
676
677impl TryFrom<RegexComponents> for EsRegex {
678 type Error = anyhow::Error;
679
680 fn try_from(components: RegexComponents) -> Result<EsRegex> {
681 EsRegex::new(&components.source, &components.flags)
682 }
683}
684
685#[derive(
686 Clone,
687 PartialEq,
688 Eq,
689 Debug,
690 Deserialize,
691 TraceRawVcs,
692 NonLocalValue,
693 OperationValue,
694 Encode,
695 Decode,
696)]
697#[serde(
698 tag = "type",
699 content = "value",
700 rename_all = "camelCase",
701 deny_unknown_fields
702)]
703pub enum ConfigConditionQuery {
704 Constant(RcStr),
705 Regex(RegexComponents),
706}
707
708impl TryFrom<ConfigConditionQuery> for ConditionQuery {
709 type Error = anyhow::Error;
710
711 fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
712 Ok(match config {
713 ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
714 ConfigConditionQuery::Regex(regex) => {
715 ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
716 }
717 })
718 }
719}
720
721#[derive(
722 Clone,
723 PartialEq,
724 Eq,
725 Debug,
726 Deserialize,
727 TraceRawVcs,
728 NonLocalValue,
729 OperationValue,
730 Encode,
731 Decode,
732)]
733#[serde(
734 tag = "type",
735 content = "value",
736 rename_all = "camelCase",
737 deny_unknown_fields
738)]
739pub enum ConfigConditionContentType {
740 Glob(RcStr),
741 Regex(RegexComponents),
742}
743
744impl TryFrom<ConfigConditionContentType> for ConditionContentType {
745 type Error = anyhow::Error;
746
747 fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
748 Ok(match config {
749 ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
750 ConfigConditionContentType::Regex(regex) => {
751 ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
752 }
753 })
754 }
755}
756
757#[derive(
758 Deserialize,
759 Clone,
760 PartialEq,
761 Eq,
762 Debug,
763 TraceRawVcs,
764 NonLocalValue,
765 OperationValue,
766 Encode,
767 Decode,
768)]
769#[serde(deny_unknown_fields)]
772pub enum ConfigConditionItem {
773 #[serde(rename = "all")]
774 All(Box<[ConfigConditionItem]>),
775 #[serde(rename = "any")]
776 Any(Box<[ConfigConditionItem]>),
777 #[serde(rename = "not")]
778 Not(Box<ConfigConditionItem>),
779 #[serde(untagged)]
780 Builtin(WebpackLoaderBuiltinCondition),
781 #[serde(untagged)]
782 Base {
783 #[serde(default)]
784 path: Option<ConfigConditionPath>,
785 #[serde(default)]
786 content: Option<RegexComponents>,
787 #[serde(default)]
788 query: Option<ConfigConditionQuery>,
789 #[serde(default, rename = "contentType")]
790 content_type: Option<ConfigConditionContentType>,
791 },
792}
793
794impl TryFrom<ConfigConditionItem> for ConditionItem {
795 type Error = anyhow::Error;
796
797 fn try_from(config: ConfigConditionItem) -> Result<Self> {
798 let try_from_vec = |conds: Box<[_]>| {
799 conds
800 .into_iter()
801 .map(ConditionItem::try_from)
802 .collect::<Result<_>>()
803 };
804 Ok(match config {
805 ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
806 ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
807 ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
808 ConfigConditionItem::Builtin(cond) => {
809 ConditionItem::Builtin(RcStr::from(cond.as_str()))
810 }
811 ConfigConditionItem::Base {
812 path,
813 content,
814 query,
815 content_type,
816 } => ConditionItem::Base {
817 path: path.map(ConditionPath::try_from).transpose()?,
818 content: content
819 .map(EsRegex::try_from)
820 .transpose()?
821 .map(EsRegex::resolved_cell),
822 query: query.map(ConditionQuery::try_from).transpose()?,
823 content_type: content_type
824 .map(ConditionContentType::try_from)
825 .transpose()?,
826 },
827 })
828 }
829}
830
831#[derive(
832 Clone,
833 Debug,
834 PartialEq,
835 Eq,
836 Deserialize,
837 TraceRawVcs,
838 NonLocalValue,
839 OperationValue,
840 Encode,
841 Decode,
842)]
843#[serde(rename_all = "camelCase")]
844pub struct RuleConfigItem {
845 #[serde(default)]
846 pub loaders: Vec<LoaderItem>,
847 #[serde(default, alias = "as")]
848 pub rename_as: Option<RcStr>,
849 #[serde(default)]
850 pub condition: Option<ConfigConditionItem>,
851 #[serde(default, alias = "type")]
852 pub module_type: Option<RcStr>,
853}
854
855#[derive(
856 Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
857)]
858pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
859
860impl<'de> Deserialize<'de> for RuleConfigCollection {
861 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
862 where
863 D: Deserializer<'de>,
864 {
865 match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
866 deserializer,
867 )? {
868 Either::Left(collection) => Ok(RuleConfigCollection(collection)),
869 Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
870 item,
871 )])),
872 }
873 }
874}
875
876#[derive(
877 Clone,
878 Debug,
879 PartialEq,
880 Eq,
881 Deserialize,
882 TraceRawVcs,
883 NonLocalValue,
884 OperationValue,
885 Encode,
886 Decode,
887)]
888#[serde(untagged)]
889pub enum RuleConfigCollectionItem {
890 Shorthand(LoaderItem),
891 Full(RuleConfigItem),
892}
893
894#[derive(
895 Clone,
896 Debug,
897 PartialEq,
898 Eq,
899 Deserialize,
900 TraceRawVcs,
901 NonLocalValue,
902 OperationValue,
903 Encode,
904 Decode,
905)]
906#[serde(untagged)]
907pub enum LoaderItem {
908 LoaderName(RcStr),
909 LoaderOptions(WebpackLoaderItem),
910}
911
912#[turbo_tasks::value(operation)]
913#[derive(Copy, Clone, Debug, Deserialize)]
914#[serde(rename_all = "camelCase")]
915pub enum ModuleIds {
916 Named,
917 Deterministic,
918}
919
920#[turbo_tasks::value(transparent)]
921pub struct OptionModuleIds(pub Option<ModuleIds>);
922
923#[derive(
924 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
925)]
926#[serde(untagged)]
927pub enum MdxRsOptions {
928 Boolean(bool),
929 Option(MdxTransformOptions),
930}
931
932#[turbo_tasks::value(shared, operation)]
933#[derive(Clone, Debug, Default, Serialize, Deserialize)]
934#[serde(rename_all = "camelCase")]
935pub enum ReactCompilerCompilationMode {
936 #[default]
937 Infer,
938 Annotation,
939 All,
940}
941
942#[turbo_tasks::value(shared, operation)]
943#[derive(Clone, Debug, Default, Serialize, Deserialize)]
944#[serde(rename_all = "snake_case")]
945pub enum ReactCompilerPanicThreshold {
946 #[default]
947 None,
948 CriticalErrors,
949 AllErrors,
950}
951
952#[turbo_tasks::value(shared, operation)]
955#[derive(Clone, Debug, Default, Serialize, Deserialize)]
956#[serde(rename_all = "camelCase")]
957pub struct ReactCompilerOptions {
958 #[serde(default)]
959 pub compilation_mode: ReactCompilerCompilationMode,
960 #[serde(default)]
961 pub panic_threshold: ReactCompilerPanicThreshold,
962}
963
964#[derive(
965 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
966)]
967#[serde(untagged)]
968pub enum ReactCompilerOptionsOrBoolean {
969 Boolean(bool),
970 Option(ReactCompilerOptions),
971}
972
973#[turbo_tasks::value(transparent)]
974pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
975
976#[derive(
980 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
981)]
982#[serde(tag = "type")]
983pub enum TurbopackIgnoreIssuePathPattern {
984 #[serde(rename = "glob")]
985 Glob { value: RcStr },
986 #[serde(rename = "regex")]
987 Regex { source: RcStr, flags: RcStr },
988}
989
990impl TurbopackIgnoreIssuePathPattern {
991 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
992 match self {
993 TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
994 Glob::parse(value.clone(), GlobOptions::default())?,
995 )),
996 TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
997 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
998 }
999 }
1000 }
1001}
1002
1003#[derive(
1008 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1009)]
1010#[serde(tag = "type")]
1011pub enum TurbopackIgnoreIssueTextPattern {
1012 #[serde(rename = "string")]
1013 String { value: RcStr },
1014 #[serde(rename = "regex")]
1015 Regex { source: RcStr, flags: RcStr },
1016}
1017
1018impl TurbopackIgnoreIssueTextPattern {
1019 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
1020 match self {
1021 TurbopackIgnoreIssueTextPattern::String { value } => {
1022 Ok(IgnoreIssuePattern::ExactString(value.clone()))
1023 }
1024 TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
1025 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
1026 }
1027 }
1028 }
1029}
1030
1031#[derive(
1033 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1034)]
1035pub struct TurbopackIgnoreIssueRule {
1036 pub path: TurbopackIgnoreIssuePathPattern,
1037 #[serde(default)]
1038 pub title: Option<TurbopackIgnoreIssueTextPattern>,
1039 #[serde(default)]
1040 pub description: Option<TurbopackIgnoreIssueTextPattern>,
1041}
1042
1043#[derive(
1044 Clone,
1045 Debug,
1046 Default,
1047 PartialEq,
1048 Deserialize,
1049 TraceRawVcs,
1050 ValueDebugFormat,
1051 NonLocalValue,
1052 OperationValue,
1053 Encode,
1054 Decode,
1055)]
1056#[serde(rename_all = "camelCase")]
1057pub struct ExperimentalConfig {
1058 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1061 client_router_filter: Option<bool>,
1062 client_router_filter_allowed_rate: Option<f64>,
1065 client_router_filter_redirects: Option<bool>,
1066 fetch_cache_key_prefix: Option<RcStr>,
1067 isr_flush_to_disk: Option<bool>,
1068 mdx_rs: Option<MdxRsOptions>,
1071 strict_next_head: Option<bool>,
1072 #[bincode(with = "turbo_bincode::serde_self_describing")]
1073 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
1074 external_middleware_rewrites_resolve: Option<bool>,
1075 scroll_restoration: Option<bool>,
1076 manual_client_base_path: Option<bool>,
1077 optimistic_client_cache: Option<bool>,
1078 middleware_prefetch: Option<MiddlewarePrefetchType>,
1079 #[bincode(with = "turbo_bincode::serde_self_describing")]
1082 optimize_css: Option<serde_json::Value>,
1083 next_script_workers: Option<bool>,
1084 web_vitals_attribution: Option<Vec<RcStr>>,
1085 server_actions: Option<ServerActionsOrLegacyBool>,
1086 sri: Option<SubResourceIntegrity>,
1087 cache_components: Option<bool>,
1090 use_cache: Option<bool>,
1091 root_params: Option<bool>,
1092 runtime_server_deployment_id: Option<bool>,
1093 immutable_asset_token: Option<RcStr>,
1094
1095 adjust_font_fallbacks: Option<bool>,
1099 adjust_font_fallbacks_with_size_adjust: Option<bool>,
1100 after: Option<bool>,
1101 app_document_preloading: Option<bool>,
1102 app_new_scroll_handler: Option<bool>,
1103 case_sensitive_routes: Option<bool>,
1104 cpus: Option<f64>,
1105 cra_compat: Option<bool>,
1106 disable_optimized_loading: Option<bool>,
1107 disable_postcss_preset_env: Option<bool>,
1108 esm_externals: Option<EsmExternals>,
1109 #[bincode(with = "turbo_bincode::serde_self_describing")]
1110 extension_alias: Option<serde_json::Value>,
1111 external_dir: Option<bool>,
1112 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
1117 fully_specified: Option<bool>,
1118 gzip_size: Option<bool>,
1119
1120 inline_css: Option<bool>,
1121 instrumentation_hook: Option<bool>,
1122 client_trace_metadata: Option<Vec<String>>,
1123 large_page_data_bytes: Option<f64>,
1124 #[bincode(with = "turbo_bincode::serde_self_describing")]
1125 logging: Option<serde_json::Value>,
1126 memory_based_workers_count: Option<bool>,
1127 optimize_server_react: Option<bool>,
1129 optimize_package_imports: Option<Vec<RcStr>>,
1132 taint: Option<bool>,
1133 proxy_timeout: Option<f64>,
1134 server_minification: Option<bool>,
1136 server_source_maps: Option<bool>,
1138 swc_trace_profiling: Option<bool>,
1139 transition_indicator: Option<bool>,
1140 gesture_transition: Option<bool>,
1141 trust_host_header: Option<bool>,
1143
1144 #[bincode(with = "turbo_bincode::serde_self_describing")]
1145 url_imports: Option<serde_json::Value>,
1146 webpack_build_worker: Option<bool>,
1149 worker_threads: Option<bool>,
1150
1151 turbopack_minify: Option<bool>,
1152 turbopack_module_ids: Option<ModuleIds>,
1153 turbopack_source_maps: Option<bool>,
1154 turbopack_input_source_maps: Option<bool>,
1155 turbopack_tree_shaking: Option<bool>,
1156 turbopack_scope_hoisting: Option<bool>,
1157 turbopack_client_side_nested_async_chunking: Option<bool>,
1158 turbopack_server_side_nested_async_chunking: Option<bool>,
1159 turbopack_import_type_bytes: Option<bool>,
1160 turbopack_import_type_text: Option<bool>,
1161 #[serde(default)]
1163 turbopack_use_builtin_sass: Option<bool>,
1164 #[serde(default)]
1167 turbopack_use_builtin_babel: Option<bool>,
1168 global_not_found: Option<bool>,
1170 turbopack_remove_unused_imports: Option<bool>,
1172 turbopack_remove_unused_exports: Option<bool>,
1174 turbopack_infer_module_side_effects: Option<bool>,
1176 devtool_segment_explorer: Option<bool>,
1178 report_system_env_inlining: Option<String>,
1180 }
1184
1185#[derive(
1186 Clone,
1187 Debug,
1188 PartialEq,
1189 Eq,
1190 Deserialize,
1191 TraceRawVcs,
1192 NonLocalValue,
1193 OperationValue,
1194 Encode,
1195 Decode,
1196)]
1197#[serde(rename_all = "camelCase")]
1198pub struct SubResourceIntegrity {
1199 pub algorithm: Option<RcStr>,
1200}
1201
1202#[derive(
1203 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1204)]
1205#[serde(untagged)]
1206pub enum ServerActionsOrLegacyBool {
1207 ServerActionsConfig(ServerActions),
1209
1210 LegacyBool(bool),
1213}
1214
1215#[derive(
1216 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1217)]
1218#[serde(rename_all = "kebab-case")]
1219pub enum EsmExternalsValue {
1220 Loose,
1221}
1222
1223#[derive(
1224 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1225)]
1226#[serde(untagged)]
1227pub enum EsmExternals {
1228 Loose(EsmExternalsValue),
1229 Bool(bool),
1230}
1231
1232#[test]
1234fn test_esm_externals_deserialization() {
1235 let json = serde_json::json!({
1236 "esmExternals": true
1237 });
1238 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1239 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1240
1241 let json = serde_json::json!({
1242 "esmExternals": "loose"
1243 });
1244 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1245 assert_eq!(
1246 config.esm_externals,
1247 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1248 );
1249}
1250
1251#[derive(
1252 Clone,
1253 Debug,
1254 Default,
1255 PartialEq,
1256 Eq,
1257 Deserialize,
1258 TraceRawVcs,
1259 NonLocalValue,
1260 OperationValue,
1261 Encode,
1262 Decode,
1263)]
1264#[serde(rename_all = "camelCase")]
1265pub struct ServerActions {
1266 pub body_size_limit: Option<SizeLimit>,
1268}
1269
1270#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1271#[serde(untagged)]
1272pub enum SizeLimit {
1273 Number(f64),
1274 WithUnit(String),
1275}
1276
1277impl PartialEq for SizeLimit {
1280 fn eq(&self, other: &Self) -> bool {
1281 match (self, other) {
1282 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1283 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1284 _ => false,
1285 }
1286 }
1287}
1288
1289impl Eq for SizeLimit {}
1290
1291#[derive(
1292 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1293)]
1294#[serde(rename_all = "kebab-case")]
1295pub enum MiddlewarePrefetchType {
1296 Strict,
1297 Flexible,
1298}
1299
1300#[derive(
1301 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1302)]
1303#[serde(untagged)]
1304pub enum EmotionTransformOptionsOrBoolean {
1305 Boolean(bool),
1306 Options(EmotionTransformConfig),
1307}
1308
1309impl EmotionTransformOptionsOrBoolean {
1310 pub fn is_enabled(&self) -> bool {
1311 match self {
1312 Self::Boolean(enabled) => *enabled,
1313 _ => true,
1314 }
1315 }
1316}
1317
1318#[derive(
1319 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1320)]
1321#[serde(untagged)]
1322pub enum StyledComponentsTransformOptionsOrBoolean {
1323 Boolean(bool),
1324 Options(StyledComponentsTransformConfig),
1325}
1326
1327impl StyledComponentsTransformOptionsOrBoolean {
1328 pub fn is_enabled(&self) -> bool {
1329 match self {
1330 Self::Boolean(enabled) => *enabled,
1331 _ => true,
1332 }
1333 }
1334}
1335
1336#[turbo_tasks::value(eq = "manual")]
1337#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1338#[serde(rename_all = "camelCase")]
1339pub struct CompilerConfig {
1340 pub react_remove_properties: Option<ReactRemoveProperties>,
1341 pub relay: Option<RelayConfig>,
1342 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1343 pub remove_console: Option<RemoveConsoleConfig>,
1344 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1345}
1346
1347#[derive(
1348 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1349)]
1350#[serde(untagged, rename_all = "camelCase")]
1351pub enum ReactRemoveProperties {
1352 Boolean(bool),
1353 Config { properties: Option<Vec<String>> },
1354}
1355
1356impl ReactRemoveProperties {
1357 pub fn is_enabled(&self) -> bool {
1358 match self {
1359 Self::Boolean(enabled) => *enabled,
1360 _ => true,
1361 }
1362 }
1363}
1364
1365#[derive(
1366 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1367)]
1368#[serde(untagged)]
1369pub enum RemoveConsoleConfig {
1370 Boolean(bool),
1371 Config { exclude: Option<Vec<String>> },
1372}
1373
1374impl RemoveConsoleConfig {
1375 pub fn is_enabled(&self) -> bool {
1376 match self {
1377 Self::Boolean(enabled) => *enabled,
1378 _ => true,
1379 }
1380 }
1381}
1382
1383#[turbo_tasks::value(transparent)]
1384pub struct ResolveExtensions(Option<Vec<RcStr>>);
1385
1386#[turbo_tasks::value(transparent)]
1387pub struct SwcPlugins(
1388 #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1389);
1390
1391#[turbo_tasks::value(transparent)]
1392pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1393
1394#[turbo_tasks::value(transparent)]
1395
1396pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1397
1398#[turbo_tasks::value(transparent)]
1399pub struct OptionFileSystemPath(Option<FileSystemPath>);
1400
1401#[turbo_tasks::value(transparent)]
1402pub struct OptionServerActions(Option<ServerActions>);
1403
1404#[turbo_tasks::value(transparent)]
1405pub struct IgnoreIssues(Vec<IgnoreIssue>);
1406
1407#[turbo_tasks::value(transparent)]
1408pub struct OptionJsonValue(
1409 #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1410);
1411
1412fn turbopack_config_documentation_link() -> RcStr {
1413 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1414}
1415
1416#[turbo_tasks::value(shared)]
1417struct InvalidLoaderRuleRenameAsIssue {
1418 glob: RcStr,
1419 rename_as: RcStr,
1420 config_file_path: FileSystemPath,
1421}
1422
1423#[turbo_tasks::value_impl]
1424impl Issue for InvalidLoaderRuleRenameAsIssue {
1425 #[turbo_tasks::function]
1426 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1427 Ok(self.config_file_path.clone().cell())
1428 }
1429
1430 #[turbo_tasks::function]
1431 fn stage(&self) -> Vc<IssueStage> {
1432 IssueStage::Config.cell()
1433 }
1434
1435 #[turbo_tasks::function]
1436 async fn title(&self) -> Result<Vc<StyledString>> {
1437 Ok(
1438 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1439 .cell(),
1440 )
1441 }
1442
1443 #[turbo_tasks::function]
1444 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1445 Ok(Vc::cell(Some(
1446 StyledString::Text(RcStr::from(format!(
1447 "The extension {} contains a wildcard, but the `as` option does not: {}",
1448 self.glob, self.rename_as,
1449 )))
1450 .resolved_cell(),
1451 )))
1452 }
1453
1454 #[turbo_tasks::function]
1455 fn documentation_link(&self) -> Vc<RcStr> {
1456 Vc::cell(turbopack_config_documentation_link())
1457 }
1458}
1459
1460#[turbo_tasks::value(shared)]
1461struct InvalidLoaderRuleConditionIssue {
1462 error_string: RcStr,
1463 condition: ConfigConditionItem,
1464 config_file_path: FileSystemPath,
1465}
1466
1467#[turbo_tasks::value_impl]
1468impl Issue for InvalidLoaderRuleConditionIssue {
1469 #[turbo_tasks::function]
1470 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1471 Ok(self.await?.config_file_path.clone().cell())
1472 }
1473
1474 #[turbo_tasks::function]
1475 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1476 IssueStage::Config.cell()
1477 }
1478
1479 #[turbo_tasks::function]
1480 async fn title(&self) -> Result<Vc<StyledString>> {
1481 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1482 }
1483
1484 #[turbo_tasks::function]
1485 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1486 Ok(Vc::cell(Some(
1487 StyledString::Stack(vec![
1488 StyledString::Line(vec![
1489 StyledString::Text(rcstr!("Encountered the following error: ")),
1490 StyledString::Code(self.error_string.clone()),
1491 ]),
1492 StyledString::Text(rcstr!("While processing the condition:")),
1493 StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1494 ])
1495 .resolved_cell(),
1496 )))
1497 }
1498
1499 #[turbo_tasks::function]
1500 fn documentation_link(&self) -> Vc<RcStr> {
1501 Vc::cell(turbopack_config_documentation_link())
1502 }
1503}
1504
1505#[turbo_tasks::value_impl]
1506impl NextConfig {
1507 #[turbo_tasks::function]
1508 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1509 let string = string.await?;
1510 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1511 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1512 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1513 Ok(config.cell())
1514 }
1515
1516 #[turbo_tasks::function]
1517 pub async fn config_file_path(
1518 &self,
1519 project_path: FileSystemPath,
1520 ) -> Result<Vc<FileSystemPath>> {
1521 Ok(project_path.join(&self.config_file_name)?.cell())
1522 }
1523
1524 #[turbo_tasks::function]
1525 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1526 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1527 }
1528
1529 #[turbo_tasks::function]
1530 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1531 Vc::cell(self.react_production_profiling.unwrap_or_default())
1532 }
1533
1534 #[turbo_tasks::function]
1535 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1536 Vc::cell(
1537 self.server_external_packages
1538 .as_ref()
1539 .cloned()
1540 .unwrap_or_default(),
1541 )
1542 }
1543
1544 #[turbo_tasks::function]
1545 pub fn is_standalone(&self) -> Vc<bool> {
1546 Vc::cell(self.output == Some(OutputType::Standalone))
1547 }
1548
1549 #[turbo_tasks::function]
1550 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1551 Vc::cell(self.base_path.clone())
1552 }
1553
1554 #[turbo_tasks::function]
1555 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1556 if let Some(handler) = &self.cache_handler {
1557 Ok(Vc::cell(Some(project_path.join(handler)?)))
1558 } else {
1559 Ok(Vc::cell(None))
1560 }
1561 }
1562
1563 #[turbo_tasks::function]
1564 pub fn compiler(&self) -> Vc<CompilerConfig> {
1565 self.compiler.clone().unwrap_or_default().cell()
1566 }
1567
1568 #[turbo_tasks::function]
1569 pub fn env(&self) -> Vc<EnvMap> {
1570 let env = self
1574 .env
1575 .iter()
1576 .map(|(k, v)| {
1577 (
1578 k.as_str().into(),
1579 if let JsonValue::String(s) = v {
1580 s.as_str().into()
1582 } else {
1583 v.to_string().into()
1584 },
1585 )
1586 })
1587 .collect();
1588
1589 Vc::cell(env)
1590 }
1591
1592 #[turbo_tasks::function]
1593 pub fn image_config(&self) -> Vc<ImageConfig> {
1594 self.images.clone().cell()
1595 }
1596
1597 #[turbo_tasks::function]
1598 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1599 let mut extensions = self.page_extensions.clone();
1603 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1604 Vc::cell(extensions)
1605 }
1606
1607 #[turbo_tasks::function]
1608 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1609 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1610 }
1611
1612 #[turbo_tasks::function]
1613 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1614 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1615 }
1616
1617 #[turbo_tasks::function]
1618 pub async fn webpack_rules(
1619 self: Vc<Self>,
1620 project_path: FileSystemPath,
1621 ) -> Result<Vc<WebpackRules>> {
1622 let this = self.await?;
1623 let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1624 return Ok(Vc::cell(Vec::new()));
1625 };
1626 if turbo_rules.is_empty() {
1627 return Ok(Vc::cell(Vec::new()));
1628 }
1629 let mut rules = Vec::new();
1630 for (glob, rule_collection) in turbo_rules.iter() {
1631 fn transform_loaders(
1632 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1633 ) -> ResolvedVc<WebpackLoaderItems> {
1634 ResolvedVc::cell(
1635 loaders
1636 .map(|item| match item {
1637 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1638 loader: name.clone(),
1639 options: Default::default(),
1640 },
1641 LoaderItem::LoaderOptions(options) => options.clone(),
1642 })
1643 .collect(),
1644 )
1645 }
1646 for item in &rule_collection.0 {
1647 match item {
1648 RuleConfigCollectionItem::Shorthand(loaders) => {
1649 rules.push((
1650 glob.clone(),
1651 LoaderRuleItem {
1652 loaders: transform_loaders(&mut [loaders].into_iter()),
1653 rename_as: None,
1654 condition: None,
1655 module_type: None,
1656 },
1657 ));
1658 }
1659 RuleConfigCollectionItem::Full(RuleConfigItem {
1660 loaders,
1661 rename_as,
1662 condition,
1663 module_type,
1664 }) => {
1665 if glob.contains("*")
1669 && let Some(rename_as) = rename_as.as_ref()
1670 && !rename_as.contains("*")
1671 {
1672 InvalidLoaderRuleRenameAsIssue {
1673 glob: glob.clone(),
1674 config_file_path: self
1675 .config_file_path(project_path.clone())
1676 .owned()
1677 .await?,
1678 rename_as: rename_as.clone(),
1679 }
1680 .resolved_cell()
1681 .emit();
1682 }
1683
1684 let condition = if let Some(condition) = condition {
1687 match ConditionItem::try_from(condition.clone()) {
1688 Ok(cond) => Some(cond),
1689 Err(err) => {
1690 InvalidLoaderRuleConditionIssue {
1691 error_string: RcStr::from(err.to_string()),
1692 condition: condition.clone(),
1693 config_file_path: self
1694 .config_file_path(project_path.clone())
1695 .owned()
1696 .await?,
1697 }
1698 .resolved_cell()
1699 .emit();
1700 None
1701 }
1702 }
1703 } else {
1704 None
1705 };
1706 rules.push((
1707 glob.clone(),
1708 LoaderRuleItem {
1709 loaders: transform_loaders(&mut loaders.iter()),
1710 rename_as: rename_as.clone(),
1711 condition,
1712 module_type: module_type.clone(),
1713 },
1714 ));
1715 }
1716 }
1717 }
1718 }
1719 Ok(Vc::cell(rules))
1720 }
1721
1722 #[turbo_tasks::function]
1723 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1724 let Some(resolve_alias) = self
1725 .turbopack
1726 .as_ref()
1727 .and_then(|t| t.resolve_alias.as_ref())
1728 else {
1729 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1730 };
1731 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1732 Ok(alias_map.cell())
1733 }
1734
1735 #[turbo_tasks::function]
1736 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1737 let Some(resolve_extensions) = self
1738 .turbopack
1739 .as_ref()
1740 .and_then(|t| t.resolve_extensions.as_ref())
1741 else {
1742 return Vc::cell(None);
1743 };
1744 Vc::cell(Some(resolve_extensions.clone()))
1745 }
1746
1747 #[turbo_tasks::function]
1748 pub fn import_externals(&self) -> Result<Vc<bool>> {
1749 Ok(Vc::cell(match self.experimental.esm_externals {
1750 Some(EsmExternals::Bool(b)) => b,
1751 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1752 None => true,
1753 }))
1754 }
1755
1756 #[turbo_tasks::function]
1757 pub fn inline_css(&self) -> Vc<bool> {
1758 Vc::cell(self.experimental.inline_css.unwrap_or(false))
1759 }
1760
1761 #[turbo_tasks::function]
1762 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1763 let options = &self.experimental.mdx_rs;
1764
1765 let options = match options {
1766 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1767 MdxTransformOptions {
1768 provider_import_source: Some(mdx_import_source_file()),
1769 ..Default::default()
1770 }
1771 .resolved_cell(),
1772 )),
1773 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1774 MdxTransformOptions {
1775 provider_import_source: Some(
1776 options
1777 .provider_import_source
1778 .clone()
1779 .unwrap_or(mdx_import_source_file()),
1780 ),
1781 ..options.clone()
1782 }
1783 .resolved_cell(),
1784 )),
1785 _ => OptionalMdxTransformOptions(None),
1786 };
1787
1788 options.cell()
1789 }
1790
1791 #[turbo_tasks::function]
1792 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1793 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1794 }
1795
1796 #[turbo_tasks::function]
1797 pub fn dist_dir(&self) -> Vc<RcStr> {
1798 Vc::cell(self.dist_dir.clone())
1799 }
1800 #[turbo_tasks::function]
1801 pub fn dist_dir_root(&self) -> Vc<RcStr> {
1802 Vc::cell(self.dist_dir_root.clone())
1803 }
1804
1805 #[turbo_tasks::function]
1806 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1807 if let Some(handlers) = &self.cache_handlers {
1808 Ok(Vc::cell(
1809 handlers
1810 .values()
1811 .map(|h| project_path.join(h))
1812 .collect::<Result<Vec<_>>>()?,
1813 ))
1814 } else {
1815 Ok(Vc::cell(vec![]))
1816 }
1817 }
1818
1819 #[turbo_tasks::function]
1820 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1821 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1822 }
1823
1824 #[turbo_tasks::function]
1825 pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1826 Vc::cell(self.experimental.sri.clone())
1827 }
1828
1829 #[turbo_tasks::function]
1830 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1831 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1832 }
1833
1834 #[turbo_tasks::function]
1835 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1836 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1837 }
1838
1839 #[turbo_tasks::function]
1840 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1841 let options = &self.react_compiler;
1842
1843 let options = match options {
1844 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1845 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1846 }
1847 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1848 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1849 ),
1850 _ => OptionalReactCompilerOptions(None),
1851 };
1852
1853 options.cell()
1854 }
1855
1856 #[turbo_tasks::function]
1857 pub fn sass_config(&self) -> Vc<JsonValue> {
1858 Vc::cell(self.sass_options.clone().unwrap_or_default())
1859 }
1860
1861 #[turbo_tasks::function]
1862 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1863 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1864 }
1865
1866 #[turbo_tasks::function]
1867 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1868 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1869 }
1870
1871 #[turbo_tasks::function]
1874 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1875 let this = self.await?;
1876
1877 Ok(Vc::cell(
1878 format!(
1879 "{}/_next/",
1880 if let Some(asset_prefix) = &this.asset_prefix {
1881 asset_prefix
1882 } else {
1883 this.base_path.as_ref().map_or("", |b| b.as_str())
1884 }
1885 .trim_end_matches('/')
1886 )
1887 .into(),
1888 ))
1889 }
1890
1891 #[turbo_tasks::function]
1893 pub fn asset_suffix_path(&self) -> Vc<Option<RcStr>> {
1894 let id = self
1895 .experimental
1896 .immutable_asset_token
1897 .as_ref()
1898 .or(self.deployment_id.as_ref());
1899
1900 Vc::cell(id.as_ref().map(|id| format!("?dpl={id}").into()))
1901 }
1902
1903 #[turbo_tasks::function]
1906 pub fn enable_immutable_assets(&self) -> Vc<bool> {
1907 Vc::cell(self.experimental.immutable_asset_token.is_some())
1908 }
1909
1910 #[turbo_tasks::function]
1911 pub fn enable_taint(&self) -> Vc<bool> {
1912 Vc::cell(self.experimental.taint.unwrap_or(false))
1913 }
1914
1915 #[turbo_tasks::function]
1916 pub fn enable_transition_indicator(&self) -> Vc<bool> {
1917 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1918 }
1919
1920 #[turbo_tasks::function]
1921 pub fn enable_gesture_transition(&self) -> Vc<bool> {
1922 Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
1923 }
1924
1925 #[turbo_tasks::function]
1926 pub fn enable_app_new_scroll_handler(&self) -> Vc<bool> {
1927 Vc::cell(self.experimental.app_new_scroll_handler.unwrap_or(false))
1928 }
1929
1930 #[turbo_tasks::function]
1931 pub fn enable_cache_components(&self) -> Vc<bool> {
1932 Vc::cell(self.cache_components.unwrap_or(false))
1933 }
1934
1935 #[turbo_tasks::function]
1936 pub fn enable_use_cache(&self) -> Vc<bool> {
1937 Vc::cell(
1938 self.experimental
1939 .use_cache
1940 .unwrap_or(self.cache_components.unwrap_or(false)),
1944 )
1945 }
1946
1947 #[turbo_tasks::function]
1948 pub fn enable_root_params(&self) -> Vc<bool> {
1949 Vc::cell(
1950 self.experimental
1951 .root_params
1952 .unwrap_or(self.cache_components.unwrap_or(false)),
1954 )
1955 }
1956
1957 #[turbo_tasks::function]
1958 pub fn runtime_server_deployment_id_available(&self) -> Vc<bool> {
1959 Vc::cell(
1960 self.experimental
1961 .runtime_server_deployment_id
1962 .unwrap_or(false),
1963 )
1964 }
1965
1966 #[turbo_tasks::function]
1967 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1968 let mut cache_kinds = CacheKinds::default();
1969
1970 if let Some(handlers) = self.cache_handlers.as_ref() {
1971 cache_kinds.extend(handlers.keys().cloned());
1972 }
1973
1974 cache_kinds.cell()
1975 }
1976
1977 #[turbo_tasks::function]
1978 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1979 Vc::cell(
1980 self.experimental
1981 .optimize_package_imports
1982 .clone()
1983 .unwrap_or_default(),
1984 )
1985 }
1986
1987 #[turbo_tasks::function]
1988 pub fn tree_shaking_mode_for_foreign_code(
1989 &self,
1990 _is_development: bool,
1991 ) -> Vc<OptionTreeShaking> {
1992 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1993 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1994 Some(true) => Some(TreeShakingMode::ModuleFragments),
1995 None => Some(TreeShakingMode::ReexportsOnly),
1996 })
1997 .cell()
1998 }
1999
2000 #[turbo_tasks::function]
2001 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
2002 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
2003 Some(false) => Some(TreeShakingMode::ReexportsOnly),
2004 Some(true) => Some(TreeShakingMode::ModuleFragments),
2005 None => Some(TreeShakingMode::ReexportsOnly),
2006 })
2007 .cell()
2008 }
2009
2010 #[turbo_tasks::function]
2011 pub async fn turbopack_remove_unused_imports(
2012 self: Vc<Self>,
2013 mode: Vc<NextMode>,
2014 ) -> Result<Vc<bool>> {
2015 let remove_unused_imports = self
2016 .await?
2017 .experimental
2018 .turbopack_remove_unused_imports
2019 .unwrap_or(matches!(*mode.await?, NextMode::Build));
2020
2021 if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2022 bail!(
2023 "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2024 enabling `experimental.turbopackRemoveUnusedExports`"
2025 );
2026 }
2027
2028 Ok(Vc::cell(remove_unused_imports))
2029 }
2030
2031 #[turbo_tasks::function]
2032 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2033 Ok(Vc::cell(
2034 self.experimental
2035 .turbopack_remove_unused_exports
2036 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2037 ))
2038 }
2039
2040 #[turbo_tasks::function]
2041 pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2042 Vc::cell(
2043 self.experimental
2044 .turbopack_infer_module_side_effects
2045 .unwrap_or(true),
2046 )
2047 }
2048
2049 #[turbo_tasks::function]
2050 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2051 Ok(match *mode.await? {
2052 NextMode::Development => ModuleIds::Named.cell(),
2054 NextMode::Build => self
2055 .experimental
2056 .turbopack_module_ids
2057 .unwrap_or(ModuleIds::Deterministic)
2058 .cell(),
2059 })
2060 }
2061
2062 #[turbo_tasks::function]
2063 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2064 let minify = self.experimental.turbopack_minify;
2065 Ok(Vc::cell(
2066 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2067 ))
2068 }
2069
2070 #[turbo_tasks::function]
2071 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2072 Ok(Vc::cell(match *mode.await? {
2073 NextMode::Development => false,
2075 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2076 }))
2077 }
2078
2079 #[turbo_tasks::function]
2080 pub async fn turbo_nested_async_chunking(
2081 &self,
2082 mode: Vc<NextMode>,
2083 client_side: bool,
2084 ) -> Result<Vc<bool>> {
2085 let option = if client_side {
2086 self.experimental
2087 .turbopack_client_side_nested_async_chunking
2088 } else {
2089 self.experimental
2090 .turbopack_server_side_nested_async_chunking
2091 };
2092 Ok(Vc::cell(if let Some(value) = option {
2093 value
2094 } else {
2095 match *mode.await? {
2096 NextMode::Development => false,
2097 NextMode::Build => client_side,
2098 }
2099 }))
2100 }
2101
2102 #[turbo_tasks::function]
2103 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2104 Vc::cell(
2105 self.experimental
2106 .turbopack_import_type_bytes
2107 .unwrap_or(false),
2108 )
2109 }
2110
2111 #[turbo_tasks::function]
2112 pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2113 Vc::cell(
2114 self.experimental
2115 .turbopack_import_type_text
2116 .unwrap_or(false),
2117 )
2118 }
2119
2120 #[turbo_tasks::function]
2121 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2122 let input_source_maps = self
2123 .experimental
2124 .turbopack_input_source_maps
2125 .unwrap_or(true);
2126 let source_maps = self
2127 .experimental
2128 .turbopack_source_maps
2129 .unwrap_or(match &*mode.await? {
2130 NextMode::Development => true,
2131 NextMode::Build => self.production_browser_source_maps,
2132 });
2133 Ok(match (source_maps, input_source_maps) {
2134 (true, true) => SourceMapsType::Full,
2135 (true, false) => SourceMapsType::Partial,
2136 (false, _) => SourceMapsType::None,
2137 }
2138 .cell())
2139 }
2140
2141 #[turbo_tasks::function]
2142 pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2143 let input_source_maps = self
2144 .experimental
2145 .turbopack_input_source_maps
2146 .unwrap_or(true);
2147 let source_maps = self
2148 .experimental
2149 .turbopack_source_maps
2150 .or(self.experimental.server_source_maps)
2151 .unwrap_or(true);
2152 Ok(match (source_maps, input_source_maps) {
2153 (true, true) => SourceMapsType::Full,
2154 (true, false) => SourceMapsType::Partial,
2155 (false, _) => SourceMapsType::None,
2156 }
2157 .cell())
2158 }
2159
2160 #[turbo_tasks::function]
2161 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2162 Vc::cell(
2163 self.turbopack
2164 .as_ref()
2165 .and_then(|turbopack| turbopack.debug_ids)
2166 .unwrap_or(false),
2167 )
2168 }
2169
2170 #[turbo_tasks::function]
2171 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2172 Ok(Vc::cell(
2173 self.typescript
2174 .tsconfig_path
2175 .as_ref()
2176 .map(|path| path.to_owned().into()),
2177 ))
2178 }
2179
2180 #[turbo_tasks::function]
2181 pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2182 Vc::cell(self.cross_origin.clone())
2183 }
2184
2185 #[turbo_tasks::function]
2186 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2187 Vc::cell(self.i18n.clone())
2188 }
2189
2190 #[turbo_tasks::function]
2191 pub fn output(&self) -> Vc<OptionOutputType> {
2192 Vc::cell(self.output.clone())
2193 }
2194
2195 #[turbo_tasks::function]
2196 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2197 Vc::cell(self.output_file_tracing_includes.clone())
2198 }
2199
2200 #[turbo_tasks::function]
2201 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2202 Vc::cell(self.output_file_tracing_excludes.clone())
2203 }
2204
2205 #[turbo_tasks::function]
2206 pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2207 FetchClientConfig::default().cell()
2208 }
2209
2210 #[turbo_tasks::function]
2211 pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2212 match self.experimental.report_system_env_inlining.as_deref() {
2213 None => Ok(IssueSeverity::Suggestion.cell()),
2214 Some("warn") => Ok(IssueSeverity::Warning.cell()),
2215 Some("error") => Ok(IssueSeverity::Error.cell()),
2216 _ => bail!(
2217 "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2218 ),
2219 }
2220 }
2221
2222 #[turbo_tasks::function]
2225 pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2226 let rules = self
2227 .turbopack
2228 .as_ref()
2229 .and_then(|tp| tp.ignore_issue.as_deref())
2230 .unwrap_or_default()
2231 .iter()
2232 .map(|rule| {
2233 Ok(IgnoreIssue {
2234 path: rule.path.to_ignore_pattern()?,
2235 title: rule
2236 .title
2237 .as_ref()
2238 .map(|t| t.to_ignore_pattern())
2239 .transpose()?,
2240 description: rule
2241 .description
2242 .as_ref()
2243 .map(|d| d.to_ignore_pattern())
2244 .transpose()?,
2245 })
2246 })
2247 .collect::<Result<Vec<_>>>()?;
2248 Ok(Vc::cell(rules))
2249 }
2250}
2251
2252#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2255#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2256#[serde(rename_all = "camelCase")]
2257pub struct JsConfig {
2258 #[bincode(with = "turbo_bincode::serde_self_describing")]
2259 compiler_options: Option<serde_json::Value>,
2260}
2261
2262#[turbo_tasks::value_impl]
2263impl JsConfig {
2264 #[turbo_tasks::function]
2265 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2266 let string = string.await?;
2267 let config: JsConfig = serde_json::from_str(&string)
2268 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2269
2270 Ok(config.cell())
2271 }
2272
2273 #[turbo_tasks::function]
2274 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2275 Vc::cell(self.compiler_options.clone().unwrap_or_default())
2276 }
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281 use super::*;
2282
2283 #[test]
2284 fn test_serde_rule_config_item_options() {
2285 let json_value = serde_json::json!({
2286 "loaders": [],
2287 "as": "*.js",
2288 "condition": {
2289 "all": [
2290 "production",
2291 {"not": "foreign"},
2292 {"any": [
2293 "browser",
2294 {
2295 "path": { "type": "glob", "value": "*.svg"},
2296 "query": {
2297 "type": "regex",
2298 "value": {
2299 "source": "@someQuery",
2300 "flags": ""
2301 }
2302 },
2303 "content": {
2304 "source": "@someTag",
2305 "flags": ""
2306 }
2307 }
2308 ]},
2309 ],
2310 }
2311 });
2312
2313 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2314
2315 assert_eq!(
2316 rule_config,
2317 RuleConfigItem {
2318 loaders: vec![],
2319 rename_as: Some(rcstr!("*.js")),
2320 module_type: None,
2321 condition: Some(ConfigConditionItem::All(
2322 [
2323 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2324 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2325 WebpackLoaderBuiltinCondition::Foreign
2326 ))),
2327 ConfigConditionItem::Any(
2328 vec![
2329 ConfigConditionItem::Builtin(
2330 WebpackLoaderBuiltinCondition::Browser
2331 ),
2332 ConfigConditionItem::Base {
2333 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2334 content: Some(RegexComponents {
2335 source: rcstr!("@someTag"),
2336 flags: rcstr!(""),
2337 }),
2338 query: Some(ConfigConditionQuery::Regex(RegexComponents {
2339 source: rcstr!("@someQuery"),
2340 flags: rcstr!(""),
2341 })),
2342 content_type: None,
2343 },
2344 ]
2345 .into(),
2346 ),
2347 ]
2348 .into(),
2349 )),
2350 }
2351 );
2352 }
2353}