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}
615
616#[derive(
617 Deserialize,
618 Clone,
619 PartialEq,
620 Eq,
621 Debug,
622 TraceRawVcs,
623 NonLocalValue,
624 OperationValue,
625 Encode,
626 Decode,
627)]
628#[serde(deny_unknown_fields)]
629pub struct RegexComponents {
630 source: RcStr,
631 flags: RcStr,
632}
633
634#[derive(
639 Clone,
640 PartialEq,
641 Eq,
642 Debug,
643 Deserialize,
644 TraceRawVcs,
645 NonLocalValue,
646 OperationValue,
647 Encode,
648 Decode,
649)]
650#[serde(
651 tag = "type",
652 content = "value",
653 rename_all = "camelCase",
654 deny_unknown_fields
655)]
656pub enum ConfigConditionPath {
657 Glob(RcStr),
658 Regex(RegexComponents),
659}
660
661impl TryFrom<ConfigConditionPath> for ConditionPath {
662 type Error = anyhow::Error;
663
664 fn try_from(config: ConfigConditionPath) -> Result<ConditionPath> {
665 Ok(match config {
666 ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
667 ConfigConditionPath::Regex(path) => {
668 ConditionPath::Regex(EsRegex::try_from(path)?.resolved_cell())
669 }
670 })
671 }
672}
673
674impl TryFrom<RegexComponents> for EsRegex {
675 type Error = anyhow::Error;
676
677 fn try_from(components: RegexComponents) -> Result<EsRegex> {
678 EsRegex::new(&components.source, &components.flags)
679 }
680}
681
682#[derive(
683 Clone,
684 PartialEq,
685 Eq,
686 Debug,
687 Deserialize,
688 TraceRawVcs,
689 NonLocalValue,
690 OperationValue,
691 Encode,
692 Decode,
693)]
694#[serde(
695 tag = "type",
696 content = "value",
697 rename_all = "camelCase",
698 deny_unknown_fields
699)]
700pub enum ConfigConditionQuery {
701 Constant(RcStr),
702 Regex(RegexComponents),
703}
704
705impl TryFrom<ConfigConditionQuery> for ConditionQuery {
706 type Error = anyhow::Error;
707
708 fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
709 Ok(match config {
710 ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
711 ConfigConditionQuery::Regex(regex) => {
712 ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
713 }
714 })
715 }
716}
717
718#[derive(
719 Clone,
720 PartialEq,
721 Eq,
722 Debug,
723 Deserialize,
724 TraceRawVcs,
725 NonLocalValue,
726 OperationValue,
727 Encode,
728 Decode,
729)]
730#[serde(
731 tag = "type",
732 content = "value",
733 rename_all = "camelCase",
734 deny_unknown_fields
735)]
736pub enum ConfigConditionContentType {
737 Glob(RcStr),
738 Regex(RegexComponents),
739}
740
741impl TryFrom<ConfigConditionContentType> for ConditionContentType {
742 type Error = anyhow::Error;
743
744 fn try_from(config: ConfigConditionContentType) -> Result<ConditionContentType> {
745 Ok(match config {
746 ConfigConditionContentType::Glob(value) => ConditionContentType::Glob(value),
747 ConfigConditionContentType::Regex(regex) => {
748 ConditionContentType::Regex(EsRegex::try_from(regex)?.resolved_cell())
749 }
750 })
751 }
752}
753
754#[derive(
755 Deserialize,
756 Clone,
757 PartialEq,
758 Eq,
759 Debug,
760 TraceRawVcs,
761 NonLocalValue,
762 OperationValue,
763 Encode,
764 Decode,
765)]
766#[serde(deny_unknown_fields)]
769pub enum ConfigConditionItem {
770 #[serde(rename = "all")]
771 All(Box<[ConfigConditionItem]>),
772 #[serde(rename = "any")]
773 Any(Box<[ConfigConditionItem]>),
774 #[serde(rename = "not")]
775 Not(Box<ConfigConditionItem>),
776 #[serde(untagged)]
777 Builtin(WebpackLoaderBuiltinCondition),
778 #[serde(untagged)]
779 Base {
780 #[serde(default)]
781 path: Option<ConfigConditionPath>,
782 #[serde(default)]
783 content: Option<RegexComponents>,
784 #[serde(default)]
785 query: Option<ConfigConditionQuery>,
786 #[serde(default, rename = "contentType")]
787 content_type: Option<ConfigConditionContentType>,
788 },
789}
790
791impl TryFrom<ConfigConditionItem> for ConditionItem {
792 type Error = anyhow::Error;
793
794 fn try_from(config: ConfigConditionItem) -> Result<Self> {
795 let try_from_vec = |conds: Box<[_]>| {
796 conds
797 .into_iter()
798 .map(ConditionItem::try_from)
799 .collect::<Result<_>>()
800 };
801 Ok(match config {
802 ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
803 ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
804 ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
805 ConfigConditionItem::Builtin(cond) => {
806 ConditionItem::Builtin(RcStr::from(cond.as_str()))
807 }
808 ConfigConditionItem::Base {
809 path,
810 content,
811 query,
812 content_type,
813 } => ConditionItem::Base {
814 path: path.map(ConditionPath::try_from).transpose()?,
815 content: content
816 .map(EsRegex::try_from)
817 .transpose()?
818 .map(EsRegex::resolved_cell),
819 query: query.map(ConditionQuery::try_from).transpose()?,
820 content_type: content_type
821 .map(ConditionContentType::try_from)
822 .transpose()?,
823 },
824 })
825 }
826}
827
828#[derive(
829 Clone,
830 Debug,
831 PartialEq,
832 Eq,
833 Deserialize,
834 TraceRawVcs,
835 NonLocalValue,
836 OperationValue,
837 Encode,
838 Decode,
839)]
840#[serde(rename_all = "camelCase")]
841pub struct RuleConfigItem {
842 #[serde(default)]
843 pub loaders: Vec<LoaderItem>,
844 #[serde(default, alias = "as")]
845 pub rename_as: Option<RcStr>,
846 #[serde(default)]
847 pub condition: Option<ConfigConditionItem>,
848 #[serde(default, alias = "type")]
849 pub module_type: Option<RcStr>,
850}
851
852#[derive(
853 Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
854)]
855pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
856
857impl<'de> Deserialize<'de> for RuleConfigCollection {
858 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
859 where
860 D: Deserializer<'de>,
861 {
862 match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
863 deserializer,
864 )? {
865 Either::Left(collection) => Ok(RuleConfigCollection(collection)),
866 Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
867 item,
868 )])),
869 }
870 }
871}
872
873#[derive(
874 Clone,
875 Debug,
876 PartialEq,
877 Eq,
878 Deserialize,
879 TraceRawVcs,
880 NonLocalValue,
881 OperationValue,
882 Encode,
883 Decode,
884)]
885#[serde(untagged)]
886pub enum RuleConfigCollectionItem {
887 Shorthand(LoaderItem),
888 Full(RuleConfigItem),
889}
890
891#[derive(
892 Clone,
893 Debug,
894 PartialEq,
895 Eq,
896 Deserialize,
897 TraceRawVcs,
898 NonLocalValue,
899 OperationValue,
900 Encode,
901 Decode,
902)]
903#[serde(untagged)]
904pub enum LoaderItem {
905 LoaderName(RcStr),
906 LoaderOptions(WebpackLoaderItem),
907}
908
909#[turbo_tasks::value(operation)]
910#[derive(Copy, Clone, Debug, Deserialize)]
911#[serde(rename_all = "camelCase")]
912pub enum ModuleIds {
913 Named,
914 Deterministic,
915}
916
917#[turbo_tasks::value(transparent)]
918pub struct OptionModuleIds(pub Option<ModuleIds>);
919
920#[derive(
921 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
922)]
923#[serde(untagged)]
924pub enum MdxRsOptions {
925 Boolean(bool),
926 Option(MdxTransformOptions),
927}
928
929#[turbo_tasks::value(shared, operation)]
930#[derive(Clone, Debug, Default, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase")]
932pub enum ReactCompilerCompilationMode {
933 #[default]
934 Infer,
935 Annotation,
936 All,
937}
938
939#[turbo_tasks::value(shared, operation)]
940#[derive(Clone, Debug, Default, Serialize, Deserialize)]
941#[serde(rename_all = "snake_case")]
942pub enum ReactCompilerPanicThreshold {
943 #[default]
944 None,
945 CriticalErrors,
946 AllErrors,
947}
948
949#[turbo_tasks::value(shared, operation)]
952#[derive(Clone, Debug, Default, Serialize, Deserialize)]
953#[serde(rename_all = "camelCase")]
954pub struct ReactCompilerOptions {
955 #[serde(default)]
956 pub compilation_mode: ReactCompilerCompilationMode,
957 #[serde(default)]
958 pub panic_threshold: ReactCompilerPanicThreshold,
959}
960
961#[derive(
962 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
963)]
964#[serde(untagged)]
965pub enum ReactCompilerOptionsOrBoolean {
966 Boolean(bool),
967 Option(ReactCompilerOptions),
968}
969
970#[turbo_tasks::value(transparent)]
971pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
972
973#[derive(
977 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
978)]
979#[serde(tag = "type")]
980pub enum TurbopackIgnoreIssuePathPattern {
981 #[serde(rename = "glob")]
982 Glob { value: RcStr },
983 #[serde(rename = "regex")]
984 Regex { source: RcStr, flags: RcStr },
985}
986
987impl TurbopackIgnoreIssuePathPattern {
988 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
989 match self {
990 TurbopackIgnoreIssuePathPattern::Glob { value } => Ok(IgnoreIssuePattern::Glob(
991 Glob::parse(value.clone(), GlobOptions::default())?,
992 )),
993 TurbopackIgnoreIssuePathPattern::Regex { source, flags } => {
994 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
995 }
996 }
997 }
998}
999
1000#[derive(
1005 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1006)]
1007#[serde(tag = "type")]
1008pub enum TurbopackIgnoreIssueTextPattern {
1009 #[serde(rename = "string")]
1010 String { value: RcStr },
1011 #[serde(rename = "regex")]
1012 Regex { source: RcStr, flags: RcStr },
1013}
1014
1015impl TurbopackIgnoreIssueTextPattern {
1016 fn to_ignore_pattern(&self) -> Result<IgnoreIssuePattern> {
1017 match self {
1018 TurbopackIgnoreIssueTextPattern::String { value } => {
1019 Ok(IgnoreIssuePattern::ExactString(value.clone()))
1020 }
1021 TurbopackIgnoreIssueTextPattern::Regex { source, flags } => {
1022 Ok(IgnoreIssuePattern::Regex(EsRegex::new(source, flags)?))
1023 }
1024 }
1025 }
1026}
1027
1028#[derive(
1030 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1031)]
1032pub struct TurbopackIgnoreIssueRule {
1033 pub path: TurbopackIgnoreIssuePathPattern,
1034 #[serde(default)]
1035 pub title: Option<TurbopackIgnoreIssueTextPattern>,
1036 #[serde(default)]
1037 pub description: Option<TurbopackIgnoreIssueTextPattern>,
1038}
1039
1040#[derive(
1041 Clone,
1042 Debug,
1043 Default,
1044 PartialEq,
1045 Deserialize,
1046 TraceRawVcs,
1047 ValueDebugFormat,
1048 NonLocalValue,
1049 OperationValue,
1050 Encode,
1051 Decode,
1052)]
1053#[serde(rename_all = "camelCase")]
1054pub struct ExperimentalConfig {
1055 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1058 client_router_filter: Option<bool>,
1059 client_router_filter_allowed_rate: Option<f64>,
1062 client_router_filter_redirects: Option<bool>,
1063 fetch_cache_key_prefix: Option<RcStr>,
1064 isr_flush_to_disk: Option<bool>,
1065 mdx_rs: Option<MdxRsOptions>,
1068 strict_next_head: Option<bool>,
1069 #[bincode(with = "turbo_bincode::serde_self_describing")]
1070 swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
1071 external_middleware_rewrites_resolve: Option<bool>,
1072 scroll_restoration: Option<bool>,
1073 manual_client_base_path: Option<bool>,
1074 optimistic_client_cache: Option<bool>,
1075 middleware_prefetch: Option<MiddlewarePrefetchType>,
1076 #[bincode(with = "turbo_bincode::serde_self_describing")]
1079 optimize_css: Option<serde_json::Value>,
1080 next_script_workers: Option<bool>,
1081 web_vitals_attribution: Option<Vec<RcStr>>,
1082 server_actions: Option<ServerActionsOrLegacyBool>,
1083 sri: Option<SubResourceIntegrity>,
1084 cache_components: Option<bool>,
1087 use_cache: Option<bool>,
1088 root_params: Option<bool>,
1089 runtime_server_deployment_id: Option<bool>,
1090
1091 adjust_font_fallbacks: Option<bool>,
1095 adjust_font_fallbacks_with_size_adjust: Option<bool>,
1096 after: Option<bool>,
1097 app_document_preloading: Option<bool>,
1098 case_sensitive_routes: Option<bool>,
1099 cpus: Option<f64>,
1100 cra_compat: Option<bool>,
1101 disable_optimized_loading: Option<bool>,
1102 disable_postcss_preset_env: Option<bool>,
1103 esm_externals: Option<EsmExternals>,
1104 #[bincode(with = "turbo_bincode::serde_self_describing")]
1105 extension_alias: Option<serde_json::Value>,
1106 external_dir: Option<bool>,
1107 fallback_node_polyfills: Option<bool>, force_swc_transforms: Option<bool>,
1112 fully_specified: Option<bool>,
1113 gzip_size: Option<bool>,
1114
1115 pub inline_css: Option<bool>,
1116 instrumentation_hook: Option<bool>,
1117 client_trace_metadata: Option<Vec<String>>,
1118 large_page_data_bytes: Option<f64>,
1119 #[bincode(with = "turbo_bincode::serde_self_describing")]
1120 logging: Option<serde_json::Value>,
1121 memory_based_workers_count: Option<bool>,
1122 optimize_server_react: Option<bool>,
1124 optimize_package_imports: Option<Vec<RcStr>>,
1127 taint: Option<bool>,
1128 proxy_timeout: Option<f64>,
1129 server_minification: Option<bool>,
1131 server_source_maps: Option<bool>,
1133 swc_trace_profiling: Option<bool>,
1134 transition_indicator: Option<bool>,
1135 gesture_transition: Option<bool>,
1136 trust_host_header: Option<bool>,
1138
1139 #[bincode(with = "turbo_bincode::serde_self_describing")]
1140 url_imports: Option<serde_json::Value>,
1141 webpack_build_worker: Option<bool>,
1144 worker_threads: Option<bool>,
1145
1146 turbopack_minify: Option<bool>,
1147 turbopack_module_ids: Option<ModuleIds>,
1148 turbopack_source_maps: Option<bool>,
1149 turbopack_input_source_maps: Option<bool>,
1150 turbopack_tree_shaking: Option<bool>,
1151 turbopack_scope_hoisting: Option<bool>,
1152 turbopack_client_side_nested_async_chunking: Option<bool>,
1153 turbopack_server_side_nested_async_chunking: Option<bool>,
1154 turbopack_import_type_bytes: Option<bool>,
1155 turbopack_import_type_text: Option<bool>,
1156 #[serde(default)]
1158 turbopack_use_builtin_sass: Option<bool>,
1159 #[serde(default)]
1162 turbopack_use_builtin_babel: Option<bool>,
1163 global_not_found: Option<bool>,
1165 turbopack_remove_unused_imports: Option<bool>,
1167 turbopack_remove_unused_exports: Option<bool>,
1169 turbopack_infer_module_side_effects: Option<bool>,
1171 #[serde(default)]
1173 turbopack_ignore_issue: Option<Vec<TurbopackIgnoreIssueRule>>,
1174 devtool_segment_explorer: Option<bool>,
1176 report_system_env_inlining: Option<String>,
1178 }
1182
1183#[derive(
1184 Clone,
1185 Debug,
1186 PartialEq,
1187 Eq,
1188 Deserialize,
1189 TraceRawVcs,
1190 NonLocalValue,
1191 OperationValue,
1192 Encode,
1193 Decode,
1194)]
1195#[serde(rename_all = "camelCase")]
1196pub struct SubResourceIntegrity {
1197 pub algorithm: Option<RcStr>,
1198}
1199
1200#[derive(
1201 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1202)]
1203#[serde(untagged)]
1204pub enum ServerActionsOrLegacyBool {
1205 ServerActionsConfig(ServerActions),
1207
1208 LegacyBool(bool),
1211}
1212
1213#[derive(
1214 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1215)]
1216#[serde(rename_all = "kebab-case")]
1217pub enum EsmExternalsValue {
1218 Loose,
1219}
1220
1221#[derive(
1222 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1223)]
1224#[serde(untagged)]
1225pub enum EsmExternals {
1226 Loose(EsmExternalsValue),
1227 Bool(bool),
1228}
1229
1230#[test]
1232fn test_esm_externals_deserialization() {
1233 let json = serde_json::json!({
1234 "esmExternals": true
1235 });
1236 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1237 assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1238
1239 let json = serde_json::json!({
1240 "esmExternals": "loose"
1241 });
1242 let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1243 assert_eq!(
1244 config.esm_externals,
1245 Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1246 );
1247}
1248
1249#[derive(
1250 Clone,
1251 Debug,
1252 Default,
1253 PartialEq,
1254 Eq,
1255 Deserialize,
1256 TraceRawVcs,
1257 NonLocalValue,
1258 OperationValue,
1259 Encode,
1260 Decode,
1261)]
1262#[serde(rename_all = "camelCase")]
1263pub struct ServerActions {
1264 pub body_size_limit: Option<SizeLimit>,
1266}
1267
1268#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1269#[serde(untagged)]
1270pub enum SizeLimit {
1271 Number(f64),
1272 WithUnit(String),
1273}
1274
1275impl PartialEq for SizeLimit {
1278 fn eq(&self, other: &Self) -> bool {
1279 match (self, other) {
1280 (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1281 (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1282 _ => false,
1283 }
1284 }
1285}
1286
1287impl Eq for SizeLimit {}
1288
1289#[derive(
1290 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1291)]
1292#[serde(rename_all = "kebab-case")]
1293pub enum MiddlewarePrefetchType {
1294 Strict,
1295 Flexible,
1296}
1297
1298#[derive(
1299 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1300)]
1301#[serde(untagged)]
1302pub enum EmotionTransformOptionsOrBoolean {
1303 Boolean(bool),
1304 Options(EmotionTransformConfig),
1305}
1306
1307impl EmotionTransformOptionsOrBoolean {
1308 pub fn is_enabled(&self) -> bool {
1309 match self {
1310 Self::Boolean(enabled) => *enabled,
1311 _ => true,
1312 }
1313 }
1314}
1315
1316#[derive(
1317 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1318)]
1319#[serde(untagged)]
1320pub enum StyledComponentsTransformOptionsOrBoolean {
1321 Boolean(bool),
1322 Options(StyledComponentsTransformConfig),
1323}
1324
1325impl StyledComponentsTransformOptionsOrBoolean {
1326 pub fn is_enabled(&self) -> bool {
1327 match self {
1328 Self::Boolean(enabled) => *enabled,
1329 _ => true,
1330 }
1331 }
1332}
1333
1334#[turbo_tasks::value(eq = "manual")]
1335#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1336#[serde(rename_all = "camelCase")]
1337pub struct CompilerConfig {
1338 pub react_remove_properties: Option<ReactRemoveProperties>,
1339 pub relay: Option<RelayConfig>,
1340 pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1341 pub remove_console: Option<RemoveConsoleConfig>,
1342 pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1343}
1344
1345#[derive(
1346 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1347)]
1348#[serde(untagged, rename_all = "camelCase")]
1349pub enum ReactRemoveProperties {
1350 Boolean(bool),
1351 Config { properties: Option<Vec<String>> },
1352}
1353
1354impl ReactRemoveProperties {
1355 pub fn is_enabled(&self) -> bool {
1356 match self {
1357 Self::Boolean(enabled) => *enabled,
1358 _ => true,
1359 }
1360 }
1361}
1362
1363#[derive(
1364 Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1365)]
1366#[serde(untagged)]
1367pub enum RemoveConsoleConfig {
1368 Boolean(bool),
1369 Config { exclude: Option<Vec<String>> },
1370}
1371
1372impl RemoveConsoleConfig {
1373 pub fn is_enabled(&self) -> bool {
1374 match self {
1375 Self::Boolean(enabled) => *enabled,
1376 _ => true,
1377 }
1378 }
1379}
1380
1381#[turbo_tasks::value(transparent)]
1382pub struct ResolveExtensions(Option<Vec<RcStr>>);
1383
1384#[turbo_tasks::value(transparent)]
1385pub struct SwcPlugins(
1386 #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1387);
1388
1389#[turbo_tasks::value(transparent)]
1390pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1391
1392#[turbo_tasks::value(transparent)]
1393
1394pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1395
1396#[turbo_tasks::value(transparent)]
1397pub struct OptionFileSystemPath(Option<FileSystemPath>);
1398
1399#[turbo_tasks::value(transparent)]
1400pub struct OptionServerActions(Option<ServerActions>);
1401
1402#[turbo_tasks::value(transparent)]
1403pub struct IgnoreIssues(Vec<IgnoreIssue>);
1404
1405#[turbo_tasks::value(transparent)]
1406pub struct OptionJsonValue(
1407 #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1408);
1409
1410fn turbopack_config_documentation_link() -> RcStr {
1411 rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1412}
1413
1414#[turbo_tasks::value(shared)]
1415struct InvalidLoaderRuleRenameAsIssue {
1416 glob: RcStr,
1417 rename_as: RcStr,
1418 config_file_path: FileSystemPath,
1419}
1420
1421#[turbo_tasks::value_impl]
1422impl Issue for InvalidLoaderRuleRenameAsIssue {
1423 #[turbo_tasks::function]
1424 async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1425 Ok(self.config_file_path.clone().cell())
1426 }
1427
1428 #[turbo_tasks::function]
1429 fn stage(&self) -> Vc<IssueStage> {
1430 IssueStage::Config.cell()
1431 }
1432
1433 #[turbo_tasks::function]
1434 async fn title(&self) -> Result<Vc<StyledString>> {
1435 Ok(
1436 StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1437 .cell(),
1438 )
1439 }
1440
1441 #[turbo_tasks::function]
1442 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1443 Ok(Vc::cell(Some(
1444 StyledString::Text(RcStr::from(format!(
1445 "The extension {} contains a wildcard, but the `as` option does not: {}",
1446 self.glob, self.rename_as,
1447 )))
1448 .resolved_cell(),
1449 )))
1450 }
1451
1452 #[turbo_tasks::function]
1453 fn documentation_link(&self) -> Vc<RcStr> {
1454 Vc::cell(turbopack_config_documentation_link())
1455 }
1456}
1457
1458#[turbo_tasks::value(shared)]
1459struct InvalidLoaderRuleConditionIssue {
1460 error_string: RcStr,
1461 condition: ConfigConditionItem,
1462 config_file_path: FileSystemPath,
1463}
1464
1465#[turbo_tasks::value_impl]
1466impl Issue for InvalidLoaderRuleConditionIssue {
1467 #[turbo_tasks::function]
1468 async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1469 Ok(self.await?.config_file_path.clone().cell())
1470 }
1471
1472 #[turbo_tasks::function]
1473 fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1474 IssueStage::Config.cell()
1475 }
1476
1477 #[turbo_tasks::function]
1478 async fn title(&self) -> Result<Vc<StyledString>> {
1479 Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1480 }
1481
1482 #[turbo_tasks::function]
1483 async fn description(&self) -> Result<Vc<OptionStyledString>> {
1484 Ok(Vc::cell(Some(
1485 StyledString::Stack(vec![
1486 StyledString::Line(vec![
1487 StyledString::Text(rcstr!("Encountered the following error: ")),
1488 StyledString::Code(self.error_string.clone()),
1489 ]),
1490 StyledString::Text(rcstr!("While processing the condition:")),
1491 StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1492 ])
1493 .resolved_cell(),
1494 )))
1495 }
1496
1497 #[turbo_tasks::function]
1498 fn documentation_link(&self) -> Vc<RcStr> {
1499 Vc::cell(turbopack_config_documentation_link())
1500 }
1501}
1502
1503#[turbo_tasks::value_impl]
1504impl NextConfig {
1505 #[turbo_tasks::function]
1506 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1507 let string = string.await?;
1508 let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1509 let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1510 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1511 Ok(config.cell())
1512 }
1513
1514 #[turbo_tasks::function]
1515 pub async fn config_file_path(
1516 &self,
1517 project_path: FileSystemPath,
1518 ) -> Result<Vc<FileSystemPath>> {
1519 Ok(project_path.join(&self.config_file_name)?.cell())
1520 }
1521
1522 #[turbo_tasks::function]
1523 pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1524 Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1525 }
1526
1527 #[turbo_tasks::function]
1528 pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1529 Vc::cell(self.react_production_profiling.unwrap_or_default())
1530 }
1531
1532 #[turbo_tasks::function]
1533 pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1534 Vc::cell(
1535 self.server_external_packages
1536 .as_ref()
1537 .cloned()
1538 .unwrap_or_default(),
1539 )
1540 }
1541
1542 #[turbo_tasks::function]
1543 pub fn is_standalone(&self) -> Vc<bool> {
1544 Vc::cell(self.output == Some(OutputType::Standalone))
1545 }
1546
1547 #[turbo_tasks::function]
1548 pub fn base_path(&self) -> Vc<Option<RcStr>> {
1549 Vc::cell(self.base_path.clone())
1550 }
1551
1552 #[turbo_tasks::function]
1553 pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1554 if let Some(handler) = &self.cache_handler {
1555 Ok(Vc::cell(Some(project_path.join(handler)?)))
1556 } else {
1557 Ok(Vc::cell(None))
1558 }
1559 }
1560
1561 #[turbo_tasks::function]
1562 pub fn compiler(&self) -> Vc<CompilerConfig> {
1563 self.compiler.clone().unwrap_or_default().cell()
1564 }
1565
1566 #[turbo_tasks::function]
1567 pub fn env(&self) -> Vc<EnvMap> {
1568 let env = self
1572 .env
1573 .iter()
1574 .map(|(k, v)| {
1575 (
1576 k.as_str().into(),
1577 if let JsonValue::String(s) = v {
1578 s.as_str().into()
1580 } else {
1581 v.to_string().into()
1582 },
1583 )
1584 })
1585 .collect();
1586
1587 Vc::cell(env)
1588 }
1589
1590 #[turbo_tasks::function]
1591 pub fn image_config(&self) -> Vc<ImageConfig> {
1592 self.images.clone().cell()
1593 }
1594
1595 #[turbo_tasks::function]
1596 pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1597 let mut extensions = self.page_extensions.clone();
1601 extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1602 Vc::cell(extensions)
1603 }
1604
1605 #[turbo_tasks::function]
1606 pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1607 Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1608 }
1609
1610 #[turbo_tasks::function]
1611 pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1612 Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1613 }
1614
1615 #[turbo_tasks::function]
1616 pub async fn webpack_rules(
1617 self: Vc<Self>,
1618 project_path: FileSystemPath,
1619 ) -> Result<Vc<WebpackRules>> {
1620 let this = self.await?;
1621 let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1622 return Ok(Vc::cell(Vec::new()));
1623 };
1624 if turbo_rules.is_empty() {
1625 return Ok(Vc::cell(Vec::new()));
1626 }
1627 let mut rules = Vec::new();
1628 for (glob, rule_collection) in turbo_rules.iter() {
1629 fn transform_loaders(
1630 loaders: &mut dyn Iterator<Item = &LoaderItem>,
1631 ) -> ResolvedVc<WebpackLoaderItems> {
1632 ResolvedVc::cell(
1633 loaders
1634 .map(|item| match item {
1635 LoaderItem::LoaderName(name) => WebpackLoaderItem {
1636 loader: name.clone(),
1637 options: Default::default(),
1638 },
1639 LoaderItem::LoaderOptions(options) => options.clone(),
1640 })
1641 .collect(),
1642 )
1643 }
1644 for item in &rule_collection.0 {
1645 match item {
1646 RuleConfigCollectionItem::Shorthand(loaders) => {
1647 rules.push((
1648 glob.clone(),
1649 LoaderRuleItem {
1650 loaders: transform_loaders(&mut [loaders].into_iter()),
1651 rename_as: None,
1652 condition: None,
1653 module_type: None,
1654 },
1655 ));
1656 }
1657 RuleConfigCollectionItem::Full(RuleConfigItem {
1658 loaders,
1659 rename_as,
1660 condition,
1661 module_type,
1662 }) => {
1663 if glob.contains("*")
1667 && let Some(rename_as) = rename_as.as_ref()
1668 && !rename_as.contains("*")
1669 {
1670 InvalidLoaderRuleRenameAsIssue {
1671 glob: glob.clone(),
1672 config_file_path: self
1673 .config_file_path(project_path.clone())
1674 .owned()
1675 .await?,
1676 rename_as: rename_as.clone(),
1677 }
1678 .resolved_cell()
1679 .emit();
1680 }
1681
1682 let condition = if let Some(condition) = condition {
1685 match ConditionItem::try_from(condition.clone()) {
1686 Ok(cond) => Some(cond),
1687 Err(err) => {
1688 InvalidLoaderRuleConditionIssue {
1689 error_string: RcStr::from(err.to_string()),
1690 condition: condition.clone(),
1691 config_file_path: self
1692 .config_file_path(project_path.clone())
1693 .owned()
1694 .await?,
1695 }
1696 .resolved_cell()
1697 .emit();
1698 None
1699 }
1700 }
1701 } else {
1702 None
1703 };
1704 rules.push((
1705 glob.clone(),
1706 LoaderRuleItem {
1707 loaders: transform_loaders(&mut loaders.iter()),
1708 rename_as: rename_as.clone(),
1709 condition,
1710 module_type: module_type.clone(),
1711 },
1712 ));
1713 }
1714 }
1715 }
1716 }
1717 Ok(Vc::cell(rules))
1718 }
1719
1720 #[turbo_tasks::function]
1721 pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1722 let Some(resolve_alias) = self
1723 .turbopack
1724 .as_ref()
1725 .and_then(|t| t.resolve_alias.as_ref())
1726 else {
1727 return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1728 };
1729 let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1730 Ok(alias_map.cell())
1731 }
1732
1733 #[turbo_tasks::function]
1734 pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1735 let Some(resolve_extensions) = self
1736 .turbopack
1737 .as_ref()
1738 .and_then(|t| t.resolve_extensions.as_ref())
1739 else {
1740 return Vc::cell(None);
1741 };
1742 Vc::cell(Some(resolve_extensions.clone()))
1743 }
1744
1745 #[turbo_tasks::function]
1746 pub fn import_externals(&self) -> Result<Vc<bool>> {
1747 Ok(Vc::cell(match self.experimental.esm_externals {
1748 Some(EsmExternals::Bool(b)) => b,
1749 Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1750 None => true,
1751 }))
1752 }
1753
1754 #[turbo_tasks::function]
1755 pub fn inline_css(&self) -> Vc<bool> {
1756 Vc::cell(self.experimental.inline_css.unwrap_or(false))
1757 }
1758
1759 #[turbo_tasks::function]
1760 pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1761 let options = &self.experimental.mdx_rs;
1762
1763 let options = match options {
1764 Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1765 MdxTransformOptions {
1766 provider_import_source: Some(mdx_import_source_file()),
1767 ..Default::default()
1768 }
1769 .resolved_cell(),
1770 )),
1771 Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1772 MdxTransformOptions {
1773 provider_import_source: Some(
1774 options
1775 .provider_import_source
1776 .clone()
1777 .unwrap_or(mdx_import_source_file()),
1778 ),
1779 ..options.clone()
1780 }
1781 .resolved_cell(),
1782 )),
1783 _ => OptionalMdxTransformOptions(None),
1784 };
1785
1786 options.cell()
1787 }
1788
1789 #[turbo_tasks::function]
1790 pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1791 Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1792 }
1793
1794 #[turbo_tasks::function]
1795 pub fn dist_dir(&self) -> Vc<RcStr> {
1796 Vc::cell(self.dist_dir.clone())
1797 }
1798 #[turbo_tasks::function]
1799 pub fn dist_dir_root(&self) -> Vc<RcStr> {
1800 Vc::cell(self.dist_dir_root.clone())
1801 }
1802
1803 #[turbo_tasks::function]
1804 pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1805 if let Some(handlers) = &self.cache_handlers {
1806 Ok(Vc::cell(
1807 handlers
1808 .values()
1809 .map(|h| project_path.join(h))
1810 .collect::<Result<Vec<_>>>()?,
1811 ))
1812 } else {
1813 Ok(Vc::cell(vec![]))
1814 }
1815 }
1816
1817 #[turbo_tasks::function]
1818 pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1819 Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1820 }
1821
1822 #[turbo_tasks::function]
1829 pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1830 Vc::cell(self.experimental.turbopack_use_builtin_babel)
1831 }
1832
1833 #[turbo_tasks::function]
1834 pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1835 Vc::cell(self.experimental.turbopack_use_builtin_sass)
1836 }
1837
1838 #[turbo_tasks::function]
1839 pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1840 let options = &self.react_compiler;
1841
1842 let options = match options {
1843 Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1844 OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1845 }
1846 Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1847 Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1848 ),
1849 _ => OptionalReactCompilerOptions(None),
1850 };
1851
1852 options.cell()
1853 }
1854
1855 #[turbo_tasks::function]
1856 pub fn sass_config(&self) -> Vc<JsonValue> {
1857 Vc::cell(self.sass_options.clone().unwrap_or_default())
1858 }
1859
1860 #[turbo_tasks::function]
1861 pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1862 Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1863 }
1864
1865 #[turbo_tasks::function]
1866 pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1867 Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1868 }
1869
1870 #[turbo_tasks::function]
1873 pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1874 let this = self.await?;
1875
1876 Ok(Vc::cell(
1877 format!(
1878 "{}/_next/",
1879 if let Some(asset_prefix) = &this.asset_prefix {
1880 asset_prefix
1881 } else {
1882 this.base_path.as_ref().map_or("", |b| b.as_str())
1883 }
1884 .trim_end_matches('/')
1885 )
1886 .into(),
1887 ))
1888 }
1889
1890 #[turbo_tasks::function]
1892 pub async fn asset_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1893 let this = self.await?;
1894
1895 match &this.deployment_id {
1896 Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1897 None => Ok(Vc::cell(None)),
1898 }
1899 }
1900
1901 #[turbo_tasks::function]
1902 pub fn enable_taint(&self) -> Vc<bool> {
1903 Vc::cell(self.experimental.taint.unwrap_or(false))
1904 }
1905
1906 #[turbo_tasks::function]
1907 pub fn enable_transition_indicator(&self) -> Vc<bool> {
1908 Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1909 }
1910
1911 #[turbo_tasks::function]
1912 pub fn enable_gesture_transition(&self) -> Vc<bool> {
1913 Vc::cell(self.experimental.gesture_transition.unwrap_or(false))
1914 }
1915
1916 #[turbo_tasks::function]
1917 pub fn enable_cache_components(&self) -> Vc<bool> {
1918 Vc::cell(self.cache_components.unwrap_or(false))
1919 }
1920
1921 #[turbo_tasks::function]
1922 pub fn enable_use_cache(&self) -> Vc<bool> {
1923 Vc::cell(
1924 self.experimental
1925 .use_cache
1926 .unwrap_or(self.cache_components.unwrap_or(false)),
1930 )
1931 }
1932
1933 #[turbo_tasks::function]
1934 pub fn enable_root_params(&self) -> Vc<bool> {
1935 Vc::cell(
1936 self.experimental
1937 .root_params
1938 .unwrap_or(self.cache_components.unwrap_or(false)),
1940 )
1941 }
1942
1943 #[turbo_tasks::function]
1944 pub fn runtime_server_deployment_id_available(&self) -> Vc<bool> {
1945 Vc::cell(
1946 self.experimental
1947 .runtime_server_deployment_id
1948 .unwrap_or(false),
1949 )
1950 }
1951
1952 #[turbo_tasks::function]
1953 pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1954 let mut cache_kinds = CacheKinds::default();
1955
1956 if let Some(handlers) = self.cache_handlers.as_ref() {
1957 cache_kinds.extend(handlers.keys().cloned());
1958 }
1959
1960 cache_kinds.cell()
1961 }
1962
1963 #[turbo_tasks::function]
1964 pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1965 Vc::cell(
1966 self.experimental
1967 .optimize_package_imports
1968 .clone()
1969 .unwrap_or_default(),
1970 )
1971 }
1972
1973 #[turbo_tasks::function]
1974 pub fn tree_shaking_mode_for_foreign_code(
1975 &self,
1976 _is_development: bool,
1977 ) -> Vc<OptionTreeShaking> {
1978 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1979 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1980 Some(true) => Some(TreeShakingMode::ModuleFragments),
1981 None => Some(TreeShakingMode::ReexportsOnly),
1982 })
1983 .cell()
1984 }
1985
1986 #[turbo_tasks::function]
1987 pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1988 OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1989 Some(false) => Some(TreeShakingMode::ReexportsOnly),
1990 Some(true) => Some(TreeShakingMode::ModuleFragments),
1991 None => Some(TreeShakingMode::ReexportsOnly),
1992 })
1993 .cell()
1994 }
1995
1996 #[turbo_tasks::function]
1997 pub async fn turbopack_remove_unused_imports(
1998 self: Vc<Self>,
1999 mode: Vc<NextMode>,
2000 ) -> Result<Vc<bool>> {
2001 let remove_unused_imports = self
2002 .await?
2003 .experimental
2004 .turbopack_remove_unused_imports
2005 .unwrap_or(matches!(*mode.await?, NextMode::Build));
2006
2007 if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
2008 bail!(
2009 "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
2010 enabling `experimental.turbopackRemoveUnusedExports`"
2011 );
2012 }
2013
2014 Ok(Vc::cell(remove_unused_imports))
2015 }
2016
2017 #[turbo_tasks::function]
2018 pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2019 Ok(Vc::cell(
2020 self.experimental
2021 .turbopack_remove_unused_exports
2022 .unwrap_or(matches!(*mode.await?, NextMode::Build)),
2023 ))
2024 }
2025
2026 #[turbo_tasks::function]
2027 pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
2028 Vc::cell(
2029 self.experimental
2030 .turbopack_infer_module_side_effects
2031 .unwrap_or(true),
2032 )
2033 }
2034
2035 #[turbo_tasks::function]
2036 pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
2037 Ok(match *mode.await? {
2038 NextMode::Development => ModuleIds::Named.cell(),
2040 NextMode::Build => self
2041 .experimental
2042 .turbopack_module_ids
2043 .unwrap_or(ModuleIds::Deterministic)
2044 .cell(),
2045 })
2046 }
2047
2048 #[turbo_tasks::function]
2049 pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2050 let minify = self.experimental.turbopack_minify;
2051 Ok(Vc::cell(
2052 minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
2053 ))
2054 }
2055
2056 #[turbo_tasks::function]
2057 pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
2058 Ok(Vc::cell(match *mode.await? {
2059 NextMode::Development => false,
2061 NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
2062 }))
2063 }
2064
2065 #[turbo_tasks::function]
2066 pub async fn turbo_nested_async_chunking(
2067 &self,
2068 mode: Vc<NextMode>,
2069 client_side: bool,
2070 ) -> Result<Vc<bool>> {
2071 let option = if client_side {
2072 self.experimental
2073 .turbopack_client_side_nested_async_chunking
2074 } else {
2075 self.experimental
2076 .turbopack_server_side_nested_async_chunking
2077 };
2078 Ok(Vc::cell(if let Some(value) = option {
2079 value
2080 } else {
2081 match *mode.await? {
2082 NextMode::Development => false,
2083 NextMode::Build => client_side,
2084 }
2085 }))
2086 }
2087
2088 #[turbo_tasks::function]
2089 pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
2090 Vc::cell(
2091 self.experimental
2092 .turbopack_import_type_bytes
2093 .unwrap_or(false),
2094 )
2095 }
2096
2097 #[turbo_tasks::function]
2098 pub async fn turbopack_import_type_text(&self) -> Vc<bool> {
2099 Vc::cell(
2100 self.experimental
2101 .turbopack_import_type_text
2102 .unwrap_or(false),
2103 )
2104 }
2105
2106 #[turbo_tasks::function]
2107 pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
2108 let input_source_maps = self
2109 .experimental
2110 .turbopack_input_source_maps
2111 .unwrap_or(true);
2112 let source_maps = self
2113 .experimental
2114 .turbopack_source_maps
2115 .unwrap_or(match &*mode.await? {
2116 NextMode::Development => true,
2117 NextMode::Build => self.production_browser_source_maps,
2118 });
2119 Ok(match (source_maps, input_source_maps) {
2120 (true, true) => SourceMapsType::Full,
2121 (true, false) => SourceMapsType::Partial,
2122 (false, _) => SourceMapsType::None,
2123 }
2124 .cell())
2125 }
2126
2127 #[turbo_tasks::function]
2128 pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
2129 let input_source_maps = self
2130 .experimental
2131 .turbopack_input_source_maps
2132 .unwrap_or(true);
2133 let source_maps = self
2134 .experimental
2135 .turbopack_source_maps
2136 .or(self.experimental.server_source_maps)
2137 .unwrap_or(true);
2138 Ok(match (source_maps, input_source_maps) {
2139 (true, true) => SourceMapsType::Full,
2140 (true, false) => SourceMapsType::Partial,
2141 (false, _) => SourceMapsType::None,
2142 }
2143 .cell())
2144 }
2145
2146 #[turbo_tasks::function]
2147 pub fn turbopack_debug_ids(&self) -> Vc<bool> {
2148 Vc::cell(
2149 self.turbopack
2150 .as_ref()
2151 .and_then(|turbopack| turbopack.debug_ids)
2152 .unwrap_or(false),
2153 )
2154 }
2155
2156 #[turbo_tasks::function]
2157 pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2158 Ok(Vc::cell(
2159 self.typescript
2160 .tsconfig_path
2161 .as_ref()
2162 .map(|path| path.to_owned().into()),
2163 ))
2164 }
2165
2166 #[turbo_tasks::function]
2167 pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2168 Vc::cell(self.cross_origin.clone())
2169 }
2170
2171 #[turbo_tasks::function]
2172 pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2173 Vc::cell(self.i18n.clone())
2174 }
2175
2176 #[turbo_tasks::function]
2177 pub fn output(&self) -> Vc<OptionOutputType> {
2178 Vc::cell(self.output.clone())
2179 }
2180
2181 #[turbo_tasks::function]
2182 pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2183 Vc::cell(self.output_file_tracing_includes.clone())
2184 }
2185
2186 #[turbo_tasks::function]
2187 pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2188 Vc::cell(self.output_file_tracing_excludes.clone())
2189 }
2190
2191 #[turbo_tasks::function]
2192 pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2193 FetchClientConfig::default().cell()
2194 }
2195
2196 #[turbo_tasks::function]
2197 pub async fn report_system_env_inlining(&self) -> Result<Vc<IssueSeverity>> {
2198 match self.experimental.report_system_env_inlining.as_deref() {
2199 None => Ok(IssueSeverity::Suggestion.cell()),
2200 Some("warn") => Ok(IssueSeverity::Warning.cell()),
2201 Some("error") => Ok(IssueSeverity::Error.cell()),
2202 _ => bail!(
2203 "`experimental.reportSystemEnvInlining` must be undefined, \"error\", or \"warn\""
2204 ),
2205 }
2206 }
2207
2208 #[turbo_tasks::function]
2211 pub fn turbopack_ignore_issue_rules(&self) -> Result<Vc<IgnoreIssues>> {
2212 let rules = self
2213 .experimental
2214 .turbopack_ignore_issue
2215 .as_deref()
2216 .unwrap_or_default()
2217 .iter()
2218 .map(|rule| {
2219 Ok(IgnoreIssue {
2220 path: rule.path.to_ignore_pattern()?,
2221 title: rule
2222 .title
2223 .as_ref()
2224 .map(|t| t.to_ignore_pattern())
2225 .transpose()?,
2226 description: rule
2227 .description
2228 .as_ref()
2229 .map(|d| d.to_ignore_pattern())
2230 .transpose()?,
2231 })
2232 })
2233 .collect::<Result<Vec<_>>>()?;
2234 Ok(Vc::cell(rules))
2235 }
2236}
2237
2238#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2241#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2242#[serde(rename_all = "camelCase")]
2243pub struct JsConfig {
2244 #[bincode(with = "turbo_bincode::serde_self_describing")]
2245 compiler_options: Option<serde_json::Value>,
2246}
2247
2248#[turbo_tasks::value_impl]
2249impl JsConfig {
2250 #[turbo_tasks::function]
2251 pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2252 let string = string.await?;
2253 let config: JsConfig = serde_json::from_str(&string)
2254 .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2255
2256 Ok(config.cell())
2257 }
2258
2259 #[turbo_tasks::function]
2260 pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2261 Vc::cell(self.compiler_options.clone().unwrap_or_default())
2262 }
2263}
2264
2265#[cfg(test)]
2266mod tests {
2267 use super::*;
2268
2269 #[test]
2270 fn test_serde_rule_config_item_options() {
2271 let json_value = serde_json::json!({
2272 "loaders": [],
2273 "as": "*.js",
2274 "condition": {
2275 "all": [
2276 "production",
2277 {"not": "foreign"},
2278 {"any": [
2279 "browser",
2280 {
2281 "path": { "type": "glob", "value": "*.svg"},
2282 "query": {
2283 "type": "regex",
2284 "value": {
2285 "source": "@someQuery",
2286 "flags": ""
2287 }
2288 },
2289 "content": {
2290 "source": "@someTag",
2291 "flags": ""
2292 }
2293 }
2294 ]},
2295 ],
2296 }
2297 });
2298
2299 let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2300
2301 assert_eq!(
2302 rule_config,
2303 RuleConfigItem {
2304 loaders: vec![],
2305 rename_as: Some(rcstr!("*.js")),
2306 module_type: None,
2307 condition: Some(ConfigConditionItem::All(
2308 [
2309 ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2310 ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2311 WebpackLoaderBuiltinCondition::Foreign
2312 ))),
2313 ConfigConditionItem::Any(
2314 vec![
2315 ConfigConditionItem::Builtin(
2316 WebpackLoaderBuiltinCondition::Browser
2317 ),
2318 ConfigConditionItem::Base {
2319 path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2320 content: Some(RegexComponents {
2321 source: rcstr!("@someTag"),
2322 flags: rcstr!(""),
2323 }),
2324 query: Some(ConfigConditionQuery::Regex(RegexComponents {
2325 source: rcstr!("@someQuery"),
2326 flags: rcstr!(""),
2327 })),
2328 content_type: None,
2329 },
2330 ]
2331 .into(),
2332 ),
2333 ]
2334 .into(),
2335 )),
2336 }
2337 );
2338 }
2339}