next_core/
next_config.rs

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