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;
14use turbo_tasks_fetch::FetchClientConfig;
15use turbo_tasks_fs::FileSystemPath;
16use turbopack::module_options::{
17    ConditionItem, ConditionPath, ConditionQuery, 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    Clone,
678    PartialEq,
679    Eq,
680    Debug,
681    Deserialize,
682    TraceRawVcs,
683    NonLocalValue,
684    OperationValue,
685    Encode,
686    Decode,
687)]
688#[serde(
689    tag = "type",
690    content = "value",
691    rename_all = "camelCase",
692    deny_unknown_fields
693)]
694pub enum ConfigConditionQuery {
695    Constant(RcStr),
696    Regex(RegexComponents),
697}
698
699impl TryFrom<ConfigConditionQuery> for ConditionQuery {
700    type Error = anyhow::Error;
701
702    fn try_from(config: ConfigConditionQuery) -> Result<ConditionQuery> {
703        Ok(match config {
704            ConfigConditionQuery::Constant(value) => ConditionQuery::Constant(value),
705            ConfigConditionQuery::Regex(regex) => {
706                ConditionQuery::Regex(EsRegex::try_from(regex)?.resolved_cell())
707            }
708        })
709    }
710}
711
712#[derive(
713    Deserialize,
714    Clone,
715    PartialEq,
716    Eq,
717    Debug,
718    TraceRawVcs,
719    NonLocalValue,
720    OperationValue,
721    Encode,
722    Decode,
723)]
724// We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
725// match nearly every object, since it has no required field.
726#[serde(deny_unknown_fields)]
727pub enum ConfigConditionItem {
728    #[serde(rename = "all")]
729    All(Box<[ConfigConditionItem]>),
730    #[serde(rename = "any")]
731    Any(Box<[ConfigConditionItem]>),
732    #[serde(rename = "not")]
733    Not(Box<ConfigConditionItem>),
734    #[serde(untagged)]
735    Builtin(WebpackLoaderBuiltinCondition),
736    #[serde(untagged)]
737    Base {
738        #[serde(default)]
739        path: Option<ConfigConditionPath>,
740        #[serde(default)]
741        content: Option<RegexComponents>,
742        #[serde(default)]
743        query: Option<ConfigConditionQuery>,
744    },
745}
746
747impl TryFrom<ConfigConditionItem> for ConditionItem {
748    type Error = anyhow::Error;
749
750    fn try_from(config: ConfigConditionItem) -> Result<Self> {
751        let try_from_vec = |conds: Box<[_]>| {
752            conds
753                .into_iter()
754                .map(ConditionItem::try_from)
755                .collect::<Result<_>>()
756        };
757        Ok(match config {
758            ConfigConditionItem::All(conds) => ConditionItem::All(try_from_vec(conds)?),
759            ConfigConditionItem::Any(conds) => ConditionItem::Any(try_from_vec(conds)?),
760            ConfigConditionItem::Not(cond) => ConditionItem::Not(Box::new((*cond).try_into()?)),
761            ConfigConditionItem::Builtin(cond) => {
762                ConditionItem::Builtin(RcStr::from(cond.as_str()))
763            }
764            ConfigConditionItem::Base {
765                path,
766                content,
767                query,
768            } => ConditionItem::Base {
769                path: path.map(ConditionPath::try_from).transpose()?,
770                content: content
771                    .map(EsRegex::try_from)
772                    .transpose()?
773                    .map(EsRegex::resolved_cell),
774                query: query.map(ConditionQuery::try_from).transpose()?,
775            },
776        })
777    }
778}
779
780#[derive(
781    Clone,
782    Debug,
783    PartialEq,
784    Eq,
785    Deserialize,
786    TraceRawVcs,
787    NonLocalValue,
788    OperationValue,
789    Encode,
790    Decode,
791)]
792#[serde(rename_all = "camelCase")]
793pub struct RuleConfigItem {
794    pub loaders: Vec<LoaderItem>,
795    #[serde(default, alias = "as")]
796    pub rename_as: Option<RcStr>,
797    #[serde(default)]
798    pub condition: Option<ConfigConditionItem>,
799}
800
801#[derive(
802    Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
803)]
804pub struct RuleConfigCollection(Vec<RuleConfigCollectionItem>);
805
806impl<'de> Deserialize<'de> for RuleConfigCollection {
807    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
808    where
809        D: Deserializer<'de>,
810    {
811        match either::serde_untagged::deserialize::<Vec<RuleConfigCollectionItem>, RuleConfigItem, D>(
812            deserializer,
813        )? {
814            Either::Left(collection) => Ok(RuleConfigCollection(collection)),
815            Either::Right(item) => Ok(RuleConfigCollection(vec![RuleConfigCollectionItem::Full(
816                item,
817            )])),
818        }
819    }
820}
821
822#[derive(
823    Clone,
824    Debug,
825    PartialEq,
826    Eq,
827    Deserialize,
828    TraceRawVcs,
829    NonLocalValue,
830    OperationValue,
831    Encode,
832    Decode,
833)]
834#[serde(untagged)]
835pub enum RuleConfigCollectionItem {
836    Shorthand(LoaderItem),
837    Full(RuleConfigItem),
838}
839
840#[derive(
841    Clone,
842    Debug,
843    PartialEq,
844    Eq,
845    Deserialize,
846    TraceRawVcs,
847    NonLocalValue,
848    OperationValue,
849    Encode,
850    Decode,
851)]
852#[serde(untagged)]
853pub enum LoaderItem {
854    LoaderName(RcStr),
855    LoaderOptions(WebpackLoaderItem),
856}
857
858#[turbo_tasks::value(operation)]
859#[derive(Copy, Clone, Debug, Deserialize)]
860#[serde(rename_all = "camelCase")]
861pub enum ModuleIds {
862    Named,
863    Deterministic,
864}
865
866#[turbo_tasks::value(transparent)]
867pub struct OptionModuleIds(pub Option<ModuleIds>);
868
869#[derive(
870    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
871)]
872#[serde(untagged)]
873pub enum MdxRsOptions {
874    Boolean(bool),
875    Option(MdxTransformOptions),
876}
877
878#[turbo_tasks::value(shared, operation)]
879#[derive(Clone, Debug, Default, Serialize, Deserialize)]
880#[serde(rename_all = "camelCase")]
881pub enum ReactCompilerCompilationMode {
882    #[default]
883    Infer,
884    Annotation,
885    All,
886}
887
888#[turbo_tasks::value(shared, operation)]
889#[derive(Clone, Debug, Default, Serialize, Deserialize)]
890#[serde(rename_all = "snake_case")]
891pub enum ReactCompilerPanicThreshold {
892    #[default]
893    None,
894    CriticalErrors,
895    AllErrors,
896}
897
898/// Subset of react compiler options, we pass these options through to the webpack loader, so it
899/// must be serializable
900#[turbo_tasks::value(shared, operation)]
901#[derive(Clone, Debug, Default, Serialize, Deserialize)]
902#[serde(rename_all = "camelCase")]
903pub struct ReactCompilerOptions {
904    #[serde(default)]
905    pub compilation_mode: ReactCompilerCompilationMode,
906    #[serde(default)]
907    pub panic_threshold: ReactCompilerPanicThreshold,
908}
909
910#[derive(
911    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
912)]
913#[serde(untagged)]
914pub enum ReactCompilerOptionsOrBoolean {
915    Boolean(bool),
916    Option(ReactCompilerOptions),
917}
918
919#[turbo_tasks::value(transparent)]
920pub struct OptionalReactCompilerOptions(Option<ResolvedVc<ReactCompilerOptions>>);
921
922#[derive(
923    Clone,
924    Debug,
925    Default,
926    PartialEq,
927    Deserialize,
928    TraceRawVcs,
929    ValueDebugFormat,
930    NonLocalValue,
931    OperationValue,
932    Encode,
933    Decode,
934)]
935#[serde(rename_all = "camelCase")]
936pub struct ExperimentalConfig {
937    // all fields should be private and access should be wrapped within a turbo-tasks function
938    // Otherwise changing ExperimentalConfig will lead to invalidating all tasks accessing it.
939    allowed_revalidate_header_keys: Option<Vec<RcStr>>,
940    client_router_filter: Option<bool>,
941    /// decimal for percent for possible false positives e.g. 0.01 for 10%
942    /// potential false matches lower percent increases size of the filter
943    client_router_filter_allowed_rate: Option<f64>,
944    client_router_filter_redirects: Option<bool>,
945    fetch_cache_key_prefix: Option<RcStr>,
946    isr_flush_to_disk: Option<bool>,
947    /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler.
948    /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs)
949    mdx_rs: Option<MdxRsOptions>,
950    strict_next_head: Option<bool>,
951    #[bincode(with = "turbo_bincode::serde_self_describing")]
952    swc_plugins: Option<Vec<(RcStr, serde_json::Value)>>,
953    external_middleware_rewrites_resolve: Option<bool>,
954    scroll_restoration: Option<bool>,
955    manual_client_base_path: Option<bool>,
956    optimistic_client_cache: Option<bool>,
957    middleware_prefetch: Option<MiddlewarePrefetchType>,
958    /// optimizeCss can be boolean or critters' option object
959    /// 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))
960    #[bincode(with = "turbo_bincode::serde_self_describing")]
961    optimize_css: Option<serde_json::Value>,
962    next_script_workers: Option<bool>,
963    web_vitals_attribution: Option<Vec<RcStr>>,
964    server_actions: Option<ServerActionsOrLegacyBool>,
965    sri: Option<SubResourceIntegrity>,
966    /// @deprecated - use top-level cache_components instead.
967    /// This field is kept for backwards compatibility during migration.
968    cache_components: Option<bool>,
969    use_cache: Option<bool>,
970    root_params: Option<bool>,
971    // ---
972    // UNSUPPORTED
973    // ---
974    adjust_font_fallbacks: Option<bool>,
975    adjust_font_fallbacks_with_size_adjust: Option<bool>,
976    after: Option<bool>,
977    app_document_preloading: Option<bool>,
978    case_sensitive_routes: Option<bool>,
979    cpus: Option<f64>,
980    cra_compat: Option<bool>,
981    disable_optimized_loading: Option<bool>,
982    disable_postcss_preset_env: Option<bool>,
983    esm_externals: Option<EsmExternals>,
984    #[bincode(with = "turbo_bincode::serde_self_describing")]
985    extension_alias: Option<serde_json::Value>,
986    external_dir: Option<bool>,
987    /// If set to `false`, webpack won't fall back to polyfill Node.js modules
988    /// in the browser Full list of old polyfills is accessible here:
989    /// [webpack/webpack#Module_notound_error.js#L13-L42](https://github.com/webpack/webpack/blob/2a0536cf510768111a3a6dceeb14cb79b9f59273/lib/Module_not_found_error.js#L13-L42)
990    fallback_node_polyfills: Option<bool>, // false
991    force_swc_transforms: Option<bool>,
992    fully_specified: Option<bool>,
993    gzip_size: Option<bool>,
994
995    pub inline_css: Option<bool>,
996    instrumentation_hook: Option<bool>,
997    client_trace_metadata: Option<Vec<String>>,
998    large_page_data_bytes: Option<f64>,
999    #[bincode(with = "turbo_bincode::serde_self_describing")]
1000    logging: Option<serde_json::Value>,
1001    memory_based_workers_count: Option<bool>,
1002    /// Optimize React APIs for server builds.
1003    optimize_server_react: Option<bool>,
1004    /// Automatically apply the "modularize_imports" optimization to imports of
1005    /// the specified packages.
1006    optimize_package_imports: Option<Vec<RcStr>>,
1007    taint: Option<bool>,
1008    proxy_timeout: Option<f64>,
1009    /// enables the minification of server code.
1010    server_minification: Option<bool>,
1011    /// Enables source maps generation for the server production bundle.
1012    server_source_maps: Option<bool>,
1013    swc_trace_profiling: Option<bool>,
1014    transition_indicator: Option<bool>,
1015    /// @internal Used by the Next.js internals only.
1016    trust_host_header: Option<bool>,
1017
1018    #[bincode(with = "turbo_bincode::serde_self_describing")]
1019    url_imports: Option<serde_json::Value>,
1020    /// This option is to enable running the Webpack build in a worker thread
1021    /// (doesn't apply to Turbopack).
1022    webpack_build_worker: Option<bool>,
1023    worker_threads: Option<bool>,
1024
1025    turbopack_minify: Option<bool>,
1026    turbopack_module_ids: Option<ModuleIds>,
1027    turbopack_persistent_caching: Option<bool>,
1028    turbopack_source_maps: Option<bool>,
1029    turbopack_input_source_maps: Option<bool>,
1030    turbopack_tree_shaking: Option<bool>,
1031    turbopack_scope_hoisting: Option<bool>,
1032    turbopack_client_side_nested_async_chunking: Option<bool>,
1033    turbopack_server_side_nested_async_chunking: Option<bool>,
1034    turbopack_import_type_bytes: Option<bool>,
1035    /// Disable automatic configuration of the sass loader.
1036    #[serde(default)]
1037    turbopack_use_builtin_sass: Option<bool>,
1038    /// Disable automatic configuration of the babel loader when a babel configuration file is
1039    /// present.
1040    #[serde(default)]
1041    turbopack_use_builtin_babel: Option<bool>,
1042    // Whether to enable the global-not-found convention
1043    global_not_found: Option<bool>,
1044    /// Defaults to false in development mode, true in production mode.
1045    turbopack_remove_unused_imports: Option<bool>,
1046    /// Defaults to false in development mode, true in production mode.
1047    turbopack_remove_unused_exports: Option<bool>,
1048    /// Enable local analysis to infer side effect free modules. Defaults to true.
1049    turbopack_infer_module_side_effects: Option<bool>,
1050    /// Devtool option for the segment explorer.
1051    devtool_segment_explorer: Option<bool>,
1052}
1053
1054#[derive(
1055    Clone,
1056    Debug,
1057    PartialEq,
1058    Eq,
1059    Deserialize,
1060    TraceRawVcs,
1061    NonLocalValue,
1062    OperationValue,
1063    Encode,
1064    Decode,
1065)]
1066#[serde(rename_all = "camelCase")]
1067pub struct SubResourceIntegrity {
1068    pub algorithm: Option<RcStr>,
1069}
1070
1071#[derive(
1072    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1073)]
1074#[serde(untagged)]
1075pub enum ServerActionsOrLegacyBool {
1076    /// The current way to configure server actions sub behaviors.
1077    ServerActionsConfig(ServerActions),
1078
1079    /// The legacy way to disable server actions. This is no longer used, server
1080    /// actions is always enabled.
1081    LegacyBool(bool),
1082}
1083
1084#[derive(
1085    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1086)]
1087#[serde(rename_all = "kebab-case")]
1088pub enum EsmExternalsValue {
1089    Loose,
1090}
1091
1092#[derive(
1093    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1094)]
1095#[serde(untagged)]
1096pub enum EsmExternals {
1097    Loose(EsmExternalsValue),
1098    Bool(bool),
1099}
1100
1101// Test for esm externals deserialization.
1102#[test]
1103fn test_esm_externals_deserialization() {
1104    let json = serde_json::json!({
1105        "esmExternals": true
1106    });
1107    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1108    assert_eq!(config.esm_externals, Some(EsmExternals::Bool(true)));
1109
1110    let json = serde_json::json!({
1111        "esmExternals": "loose"
1112    });
1113    let config: ExperimentalConfig = serde_json::from_value(json).unwrap();
1114    assert_eq!(
1115        config.esm_externals,
1116        Some(EsmExternals::Loose(EsmExternalsValue::Loose))
1117    );
1118}
1119
1120#[derive(
1121    Clone,
1122    Debug,
1123    Default,
1124    PartialEq,
1125    Eq,
1126    Deserialize,
1127    TraceRawVcs,
1128    NonLocalValue,
1129    OperationValue,
1130    Encode,
1131    Decode,
1132)]
1133#[serde(rename_all = "camelCase")]
1134pub struct ServerActions {
1135    /// Allows adjusting body parser size limit for server actions.
1136    pub body_size_limit: Option<SizeLimit>,
1137}
1138
1139#[derive(Clone, Debug, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode)]
1140#[serde(untagged)]
1141pub enum SizeLimit {
1142    Number(f64),
1143    WithUnit(String),
1144}
1145
1146// Manual implementation of PartialEq and Eq for SizeLimit because f64 doesn't
1147// implement Eq.
1148impl PartialEq for SizeLimit {
1149    fn eq(&self, other: &Self) -> bool {
1150        match (self, other) {
1151            (SizeLimit::Number(a), SizeLimit::Number(b)) => a.to_bits() == b.to_bits(),
1152            (SizeLimit::WithUnit(a), SizeLimit::WithUnit(b)) => a == b,
1153            _ => false,
1154        }
1155    }
1156}
1157
1158impl Eq for SizeLimit {}
1159
1160#[derive(
1161    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1162)]
1163#[serde(rename_all = "kebab-case")]
1164pub enum MiddlewarePrefetchType {
1165    Strict,
1166    Flexible,
1167}
1168
1169#[derive(
1170    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1171)]
1172#[serde(untagged)]
1173pub enum EmotionTransformOptionsOrBoolean {
1174    Boolean(bool),
1175    Options(EmotionTransformConfig),
1176}
1177
1178impl EmotionTransformOptionsOrBoolean {
1179    pub fn is_enabled(&self) -> bool {
1180        match self {
1181            Self::Boolean(enabled) => *enabled,
1182            _ => true,
1183        }
1184    }
1185}
1186
1187#[derive(
1188    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1189)]
1190#[serde(untagged)]
1191pub enum StyledComponentsTransformOptionsOrBoolean {
1192    Boolean(bool),
1193    Options(StyledComponentsTransformConfig),
1194}
1195
1196impl StyledComponentsTransformOptionsOrBoolean {
1197    pub fn is_enabled(&self) -> bool {
1198        match self {
1199            Self::Boolean(enabled) => *enabled,
1200            _ => true,
1201        }
1202    }
1203}
1204
1205#[turbo_tasks::value(eq = "manual")]
1206#[derive(Clone, Debug, PartialEq, Default, OperationValue, Deserialize)]
1207#[serde(rename_all = "camelCase")]
1208pub struct CompilerConfig {
1209    pub react_remove_properties: Option<ReactRemoveProperties>,
1210    pub relay: Option<RelayConfig>,
1211    pub emotion: Option<EmotionTransformOptionsOrBoolean>,
1212    pub remove_console: Option<RemoveConsoleConfig>,
1213    pub styled_components: Option<StyledComponentsTransformOptionsOrBoolean>,
1214}
1215
1216#[derive(
1217    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1218)]
1219#[serde(untagged, rename_all = "camelCase")]
1220pub enum ReactRemoveProperties {
1221    Boolean(bool),
1222    Config { properties: Option<Vec<String>> },
1223}
1224
1225impl ReactRemoveProperties {
1226    pub fn is_enabled(&self) -> bool {
1227        match self {
1228            Self::Boolean(enabled) => *enabled,
1229            _ => true,
1230        }
1231    }
1232}
1233
1234#[derive(
1235    Clone, Debug, PartialEq, Deserialize, TraceRawVcs, NonLocalValue, OperationValue, Encode, Decode,
1236)]
1237#[serde(untagged)]
1238pub enum RemoveConsoleConfig {
1239    Boolean(bool),
1240    Config { exclude: Option<Vec<String>> },
1241}
1242
1243impl RemoveConsoleConfig {
1244    pub fn is_enabled(&self) -> bool {
1245        match self {
1246            Self::Boolean(enabled) => *enabled,
1247            _ => true,
1248        }
1249    }
1250}
1251
1252#[turbo_tasks::value(transparent)]
1253pub struct ResolveExtensions(Option<Vec<RcStr>>);
1254
1255#[turbo_tasks::value(transparent)]
1256pub struct SwcPlugins(
1257    #[bincode(with = "turbo_bincode::serde_self_describing")] Vec<(RcStr, serde_json::Value)>,
1258);
1259
1260#[turbo_tasks::value(transparent)]
1261pub struct OptionalMdxTransformOptions(Option<ResolvedVc<MdxTransformOptions>>);
1262
1263#[turbo_tasks::value(transparent)]
1264
1265pub struct OptionSubResourceIntegrity(Option<SubResourceIntegrity>);
1266
1267#[turbo_tasks::value(transparent)]
1268pub struct OptionFileSystemPath(Option<FileSystemPath>);
1269
1270#[turbo_tasks::value(transparent)]
1271pub struct OptionServerActions(Option<ServerActions>);
1272
1273#[turbo_tasks::value(transparent)]
1274pub struct OptionJsonValue(
1275    #[bincode(with = "turbo_bincode::serde_self_describing")] pub Option<serde_json::Value>,
1276);
1277
1278fn turbopack_config_documentation_link() -> RcStr {
1279    rcstr!("https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders")
1280}
1281
1282#[turbo_tasks::value(shared)]
1283struct InvalidLoaderRuleRenameAsIssue {
1284    glob: RcStr,
1285    rename_as: RcStr,
1286    config_file_path: FileSystemPath,
1287}
1288
1289#[turbo_tasks::value_impl]
1290impl Issue for InvalidLoaderRuleRenameAsIssue {
1291    #[turbo_tasks::function]
1292    async fn file_path(&self) -> Result<Vc<FileSystemPath>> {
1293        Ok(self.config_file_path.clone().cell())
1294    }
1295
1296    #[turbo_tasks::function]
1297    fn stage(&self) -> Vc<IssueStage> {
1298        IssueStage::Config.cell()
1299    }
1300
1301    #[turbo_tasks::function]
1302    async fn title(&self) -> Result<Vc<StyledString>> {
1303        Ok(
1304            StyledString::Text(format!("Invalid loader rule for extension: {}", self.glob).into())
1305                .cell(),
1306        )
1307    }
1308
1309    #[turbo_tasks::function]
1310    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1311        Ok(Vc::cell(Some(
1312            StyledString::Text(RcStr::from(format!(
1313                "The extension {} contains a wildcard, but the `as` option does not: {}",
1314                self.glob, self.rename_as,
1315            )))
1316            .resolved_cell(),
1317        )))
1318    }
1319
1320    #[turbo_tasks::function]
1321    fn documentation_link(&self) -> Vc<RcStr> {
1322        Vc::cell(turbopack_config_documentation_link())
1323    }
1324}
1325
1326#[turbo_tasks::value(shared)]
1327struct InvalidLoaderRuleConditionIssue {
1328    error_string: RcStr,
1329    condition: ConfigConditionItem,
1330    config_file_path: FileSystemPath,
1331}
1332
1333#[turbo_tasks::value_impl]
1334impl Issue for InvalidLoaderRuleConditionIssue {
1335    #[turbo_tasks::function]
1336    async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1337        Ok(self.await?.config_file_path.clone().cell())
1338    }
1339
1340    #[turbo_tasks::function]
1341    fn stage(self: Vc<Self>) -> Vc<IssueStage> {
1342        IssueStage::Config.cell()
1343    }
1344
1345    #[turbo_tasks::function]
1346    async fn title(&self) -> Result<Vc<StyledString>> {
1347        Ok(StyledString::Text(rcstr!("Invalid condition for Turbopack loader rule")).cell())
1348    }
1349
1350    #[turbo_tasks::function]
1351    async fn description(&self) -> Result<Vc<OptionStyledString>> {
1352        Ok(Vc::cell(Some(
1353            StyledString::Stack(vec![
1354                StyledString::Line(vec![
1355                    StyledString::Text(rcstr!("Encountered the following error: ")),
1356                    StyledString::Code(self.error_string.clone()),
1357                ]),
1358                StyledString::Text(rcstr!("While processing the condition:")),
1359                StyledString::Code(RcStr::from(format!("{:#?}", self.condition))),
1360            ])
1361            .resolved_cell(),
1362        )))
1363    }
1364
1365    #[turbo_tasks::function]
1366    fn documentation_link(&self) -> Vc<RcStr> {
1367        Vc::cell(turbopack_config_documentation_link())
1368    }
1369}
1370
1371#[turbo_tasks::value_impl]
1372impl NextConfig {
1373    #[turbo_tasks::function]
1374    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
1375        let string = string.await?;
1376        let mut jdeserializer = serde_json::Deserializer::from_str(&string);
1377        let config: NextConfig = serde_path_to_error::deserialize(&mut jdeserializer)
1378            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
1379        Ok(config.cell())
1380    }
1381
1382    #[turbo_tasks::function]
1383    pub async fn config_file_path(
1384        &self,
1385        project_path: FileSystemPath,
1386    ) -> Result<Vc<FileSystemPath>> {
1387        Ok(project_path.join(&self.config_file_name)?.cell())
1388    }
1389
1390    #[turbo_tasks::function]
1391    pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
1392        Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
1393    }
1394
1395    #[turbo_tasks::function]
1396    pub fn enable_react_production_profiling(&self) -> Vc<bool> {
1397        Vc::cell(self.react_production_profiling.unwrap_or_default())
1398    }
1399
1400    #[turbo_tasks::function]
1401    pub fn server_external_packages(&self) -> Vc<Vec<RcStr>> {
1402        Vc::cell(
1403            self.server_external_packages
1404                .as_ref()
1405                .cloned()
1406                .unwrap_or_default(),
1407        )
1408    }
1409
1410    #[turbo_tasks::function]
1411    pub fn is_standalone(&self) -> Vc<bool> {
1412        Vc::cell(self.output == Some(OutputType::Standalone))
1413    }
1414
1415    #[turbo_tasks::function]
1416    pub fn base_path(&self) -> Vc<Option<RcStr>> {
1417        Vc::cell(self.base_path.clone())
1418    }
1419
1420    #[turbo_tasks::function]
1421    pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
1422        if let Some(handler) = &self.cache_handler {
1423            Ok(Vc::cell(Some(project_path.join(handler)?)))
1424        } else {
1425            Ok(Vc::cell(None))
1426        }
1427    }
1428
1429    #[turbo_tasks::function]
1430    pub fn compiler(&self) -> Vc<CompilerConfig> {
1431        self.compiler.clone().unwrap_or_default().cell()
1432    }
1433
1434    #[turbo_tasks::function]
1435    pub fn env(&self) -> Vc<EnvMap> {
1436        // The value expected for env is Record<String, String>, but config itself
1437        // allows arbitrary object (https://github.com/vercel/next.js/blob/25ba8a74b7544dfb6b30d1b67c47b9cb5360cb4e/packages/next/src/server/config-schema.ts#L203)
1438        // then stringifies it. We do the interop here as well.
1439        let env = self
1440            .env
1441            .iter()
1442            .map(|(k, v)| {
1443                (
1444                    k.as_str().into(),
1445                    if let JsonValue::String(s) = v {
1446                        // A string value is kept, calling `to_string` would wrap in to quotes.
1447                        s.as_str().into()
1448                    } else {
1449                        v.to_string().into()
1450                    },
1451                )
1452            })
1453            .collect();
1454
1455        Vc::cell(env)
1456    }
1457
1458    #[turbo_tasks::function]
1459    pub fn image_config(&self) -> Vc<ImageConfig> {
1460        self.images.clone().cell()
1461    }
1462
1463    #[turbo_tasks::function]
1464    pub fn page_extensions(&self) -> Vc<Vec<RcStr>> {
1465        // Sort page extensions by length descending. This mirrors the Webpack behavior in Next.js,
1466        // which just builds a regex alternative, which greedily matches the longest
1467        // extension: https://github.com/vercel/next.js/blob/32476071fe331948d89a35c391eb578aed8de979/packages/next/src/build/entries.ts#L409
1468        let mut extensions = self.page_extensions.clone();
1469        extensions.sort_by_key(|ext| std::cmp::Reverse(ext.len()));
1470        Vc::cell(extensions)
1471    }
1472
1473    #[turbo_tasks::function]
1474    pub fn is_global_not_found_enabled(&self) -> Vc<bool> {
1475        Vc::cell(self.experimental.global_not_found.unwrap_or_default())
1476    }
1477
1478    #[turbo_tasks::function]
1479    pub fn transpile_packages(&self) -> Vc<Vec<RcStr>> {
1480        Vc::cell(self.transpile_packages.clone().unwrap_or_default())
1481    }
1482
1483    #[turbo_tasks::function]
1484    pub async fn webpack_rules(
1485        self: Vc<Self>,
1486        project_path: FileSystemPath,
1487    ) -> Result<Vc<WebpackRules>> {
1488        let this = self.await?;
1489        let Some(turbo_rules) = this.turbopack.as_ref().map(|t| &t.rules) else {
1490            return Ok(Vc::cell(Vec::new()));
1491        };
1492        if turbo_rules.is_empty() {
1493            return Ok(Vc::cell(Vec::new()));
1494        }
1495        let mut rules = Vec::new();
1496        for (glob, rule_collection) in turbo_rules.iter() {
1497            fn transform_loaders(
1498                loaders: &mut dyn Iterator<Item = &LoaderItem>,
1499            ) -> ResolvedVc<WebpackLoaderItems> {
1500                ResolvedVc::cell(
1501                    loaders
1502                        .map(|item| match item {
1503                            LoaderItem::LoaderName(name) => WebpackLoaderItem {
1504                                loader: name.clone(),
1505                                options: Default::default(),
1506                            },
1507                            LoaderItem::LoaderOptions(options) => options.clone(),
1508                        })
1509                        .collect(),
1510                )
1511            }
1512            for item in &rule_collection.0 {
1513                match item {
1514                    RuleConfigCollectionItem::Shorthand(loaders) => {
1515                        rules.push((
1516                            glob.clone(),
1517                            LoaderRuleItem {
1518                                loaders: transform_loaders(&mut [loaders].into_iter()),
1519                                rename_as: None,
1520                                condition: None,
1521                            },
1522                        ));
1523                    }
1524                    RuleConfigCollectionItem::Full(RuleConfigItem {
1525                        loaders,
1526                        rename_as,
1527                        condition,
1528                    }) => {
1529                        // If the extension contains a wildcard, and the rename_as does not,
1530                        // emit an issue to prevent users from encountering duplicate module
1531                        // names.
1532                        if glob.contains("*")
1533                            && let Some(rename_as) = rename_as.as_ref()
1534                            && !rename_as.contains("*")
1535                        {
1536                            InvalidLoaderRuleRenameAsIssue {
1537                                glob: glob.clone(),
1538                                config_file_path: self
1539                                    .config_file_path(project_path.clone())
1540                                    .owned()
1541                                    .await?,
1542                                rename_as: rename_as.clone(),
1543                            }
1544                            .resolved_cell()
1545                            .emit();
1546                        }
1547
1548                        // convert from Next.js-specific condition type to internal Turbopack
1549                        // condition type
1550                        let condition = if let Some(condition) = condition {
1551                            match ConditionItem::try_from(condition.clone()) {
1552                                Ok(cond) => Some(cond),
1553                                Err(err) => {
1554                                    InvalidLoaderRuleConditionIssue {
1555                                        error_string: RcStr::from(err.to_string()),
1556                                        condition: condition.clone(),
1557                                        config_file_path: self
1558                                            .config_file_path(project_path.clone())
1559                                            .owned()
1560                                            .await?,
1561                                    }
1562                                    .resolved_cell()
1563                                    .emit();
1564                                    None
1565                                }
1566                            }
1567                        } else {
1568                            None
1569                        };
1570                        rules.push((
1571                            glob.clone(),
1572                            LoaderRuleItem {
1573                                loaders: transform_loaders(&mut loaders.iter()),
1574                                rename_as: rename_as.clone(),
1575                                condition,
1576                            },
1577                        ));
1578                    }
1579                }
1580            }
1581        }
1582        Ok(Vc::cell(rules))
1583    }
1584
1585    #[turbo_tasks::function]
1586    pub fn persistent_caching_enabled(&self) -> Result<Vc<bool>> {
1587        Ok(Vc::cell(
1588            self.experimental
1589                .turbopack_persistent_caching
1590                .unwrap_or_default(),
1591        ))
1592    }
1593
1594    #[turbo_tasks::function]
1595    pub fn resolve_alias_options(&self) -> Result<Vc<ResolveAliasMap>> {
1596        let Some(resolve_alias) = self
1597            .turbopack
1598            .as_ref()
1599            .and_then(|t| t.resolve_alias.as_ref())
1600        else {
1601            return Ok(ResolveAliasMap::cell(ResolveAliasMap::default()));
1602        };
1603        let alias_map: ResolveAliasMap = resolve_alias.try_into()?;
1604        Ok(alias_map.cell())
1605    }
1606
1607    #[turbo_tasks::function]
1608    pub fn resolve_extension(&self) -> Vc<ResolveExtensions> {
1609        let Some(resolve_extensions) = self
1610            .turbopack
1611            .as_ref()
1612            .and_then(|t| t.resolve_extensions.as_ref())
1613        else {
1614            return Vc::cell(None);
1615        };
1616        Vc::cell(Some(resolve_extensions.clone()))
1617    }
1618
1619    #[turbo_tasks::function]
1620    pub fn import_externals(&self) -> Result<Vc<bool>> {
1621        Ok(Vc::cell(match self.experimental.esm_externals {
1622            Some(EsmExternals::Bool(b)) => b,
1623            Some(EsmExternals::Loose(_)) => bail!("esmExternals = \"loose\" is not supported"),
1624            None => true,
1625        }))
1626    }
1627
1628    #[turbo_tasks::function]
1629    pub fn inline_css(&self) -> Vc<bool> {
1630        Vc::cell(self.experimental.inline_css.unwrap_or(false))
1631    }
1632
1633    #[turbo_tasks::function]
1634    pub fn mdx_rs(&self) -> Vc<OptionalMdxTransformOptions> {
1635        let options = &self.experimental.mdx_rs;
1636
1637        let options = match options {
1638            Some(MdxRsOptions::Boolean(true)) => OptionalMdxTransformOptions(Some(
1639                MdxTransformOptions {
1640                    provider_import_source: Some(mdx_import_source_file()),
1641                    ..Default::default()
1642                }
1643                .resolved_cell(),
1644            )),
1645            Some(MdxRsOptions::Option(options)) => OptionalMdxTransformOptions(Some(
1646                MdxTransformOptions {
1647                    provider_import_source: Some(
1648                        options
1649                            .provider_import_source
1650                            .clone()
1651                            .unwrap_or(mdx_import_source_file()),
1652                    ),
1653                    ..options.clone()
1654                }
1655                .resolved_cell(),
1656            )),
1657            _ => OptionalMdxTransformOptions(None),
1658        };
1659
1660        options.cell()
1661    }
1662
1663    #[turbo_tasks::function]
1664    pub fn modularize_imports(&self) -> Vc<ModularizeImports> {
1665        Vc::cell(self.modularize_imports.clone().unwrap_or_default())
1666    }
1667
1668    #[turbo_tasks::function]
1669    pub fn dist_dir(&self) -> Vc<RcStr> {
1670        Vc::cell(self.dist_dir.clone())
1671    }
1672    #[turbo_tasks::function]
1673    pub fn dist_dir_root(&self) -> Vc<RcStr> {
1674        Vc::cell(self.dist_dir_root.clone())
1675    }
1676
1677    #[turbo_tasks::function]
1678    pub fn cache_handlers(&self, project_path: FileSystemPath) -> Result<Vc<FileSystemPathVec>> {
1679        if let Some(handlers) = &self.cache_handlers {
1680            Ok(Vc::cell(
1681                handlers
1682                    .values()
1683                    .map(|h| project_path.join(h))
1684                    .collect::<Result<Vec<_>>>()?,
1685            ))
1686        } else {
1687            Ok(Vc::cell(vec![]))
1688        }
1689    }
1690
1691    #[turbo_tasks::function]
1692    pub fn experimental_swc_plugins(&self) -> Vc<SwcPlugins> {
1693        Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
1694    }
1695
1696    // TODO not implemented yet
1697    // #[turbo_tasks::function]
1698    // pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
1699    //     Vc::cell(self.experimental.sri.clone())
1700    // }
1701
1702    #[turbo_tasks::function]
1703    pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
1704        Vc::cell(self.experimental.turbopack_use_builtin_babel)
1705    }
1706
1707    #[turbo_tasks::function]
1708    pub fn experimental_turbopack_use_builtin_sass(&self) -> Vc<Option<bool>> {
1709        Vc::cell(self.experimental.turbopack_use_builtin_sass)
1710    }
1711
1712    #[turbo_tasks::function]
1713    pub fn react_compiler_options(&self) -> Vc<OptionalReactCompilerOptions> {
1714        let options = &self.react_compiler;
1715
1716        let options = match options {
1717            Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
1718                OptionalReactCompilerOptions(Some(ReactCompilerOptions::default().resolved_cell()))
1719            }
1720            Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
1721                Some(ReactCompilerOptions { ..options.clone() }.resolved_cell()),
1722            ),
1723            _ => OptionalReactCompilerOptions(None),
1724        };
1725
1726        options.cell()
1727    }
1728
1729    #[turbo_tasks::function]
1730    pub fn sass_config(&self) -> Vc<JsonValue> {
1731        Vc::cell(self.sass_options.clone().unwrap_or_default())
1732    }
1733
1734    #[turbo_tasks::function]
1735    pub fn skip_proxy_url_normalize(&self) -> Vc<bool> {
1736        Vc::cell(self.skip_proxy_url_normalize.unwrap_or(false))
1737    }
1738
1739    #[turbo_tasks::function]
1740    pub fn skip_trailing_slash_redirect(&self) -> Vc<bool> {
1741        Vc::cell(self.skip_trailing_slash_redirect.unwrap_or(false))
1742    }
1743
1744    /// Returns the final asset prefix. If an assetPrefix is set, it's used.
1745    /// Otherwise, the basePath is used.
1746    #[turbo_tasks::function]
1747    pub async fn computed_asset_prefix(self: Vc<Self>) -> Result<Vc<RcStr>> {
1748        let this = self.await?;
1749
1750        Ok(Vc::cell(
1751            format!(
1752                "{}/_next/",
1753                if let Some(asset_prefix) = &this.asset_prefix {
1754                    asset_prefix
1755                } else {
1756                    this.base_path.as_ref().map_or("", |b| b.as_str())
1757                }
1758                .trim_end_matches('/')
1759            )
1760            .into(),
1761        ))
1762    }
1763
1764    /// Returns the suffix to use for chunk loading.
1765    #[turbo_tasks::function]
1766    pub async fn chunk_suffix_path(self: Vc<Self>) -> Result<Vc<Option<RcStr>>> {
1767        let this = self.await?;
1768
1769        match &this.deployment_id {
1770            Some(deployment_id) => Ok(Vc::cell(Some(format!("?dpl={deployment_id}").into()))),
1771            None => Ok(Vc::cell(None)),
1772        }
1773    }
1774
1775    #[turbo_tasks::function]
1776    pub fn enable_taint(&self) -> Vc<bool> {
1777        Vc::cell(self.experimental.taint.unwrap_or(false))
1778    }
1779
1780    #[turbo_tasks::function]
1781    pub fn enable_transition_indicator(&self) -> Vc<bool> {
1782        Vc::cell(self.experimental.transition_indicator.unwrap_or(false))
1783    }
1784
1785    #[turbo_tasks::function]
1786    pub fn enable_cache_components(&self) -> Vc<bool> {
1787        Vc::cell(self.cache_components.unwrap_or(false))
1788    }
1789
1790    #[turbo_tasks::function]
1791    pub fn enable_use_cache(&self) -> Vc<bool> {
1792        Vc::cell(
1793            self.experimental
1794                .use_cache
1795                // "use cache" was originally implicitly enabled with the
1796                // cacheComponents flag, so we transfer the value for cacheComponents to the
1797                // explicit useCache flag to ensure backwards compatibility.
1798                .unwrap_or(self.cache_components.unwrap_or(false)),
1799        )
1800    }
1801
1802    #[turbo_tasks::function]
1803    pub fn enable_root_params(&self) -> Vc<bool> {
1804        Vc::cell(
1805            self.experimental
1806                .root_params
1807                // rootParams should be enabled implicitly in cacheComponents.
1808                .unwrap_or(self.cache_components.unwrap_or(false)),
1809        )
1810    }
1811
1812    #[turbo_tasks::function]
1813    pub fn cache_kinds(&self) -> Vc<CacheKinds> {
1814        let mut cache_kinds = CacheKinds::default();
1815
1816        if let Some(handlers) = self.cache_handlers.as_ref() {
1817            cache_kinds.extend(handlers.keys().cloned());
1818        }
1819
1820        cache_kinds.cell()
1821    }
1822
1823    #[turbo_tasks::function]
1824    pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
1825        Vc::cell(
1826            self.experimental
1827                .optimize_package_imports
1828                .clone()
1829                .unwrap_or_default(),
1830        )
1831    }
1832
1833    #[turbo_tasks::function]
1834    pub fn tree_shaking_mode_for_foreign_code(
1835        &self,
1836        _is_development: bool,
1837    ) -> Vc<OptionTreeShaking> {
1838        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1839            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1840            Some(true) => Some(TreeShakingMode::ModuleFragments),
1841            None => Some(TreeShakingMode::ReexportsOnly),
1842        })
1843        .cell()
1844    }
1845
1846    #[turbo_tasks::function]
1847    pub fn tree_shaking_mode_for_user_code(&self, _is_development: bool) -> Vc<OptionTreeShaking> {
1848        OptionTreeShaking(match self.experimental.turbopack_tree_shaking {
1849            Some(false) => Some(TreeShakingMode::ReexportsOnly),
1850            Some(true) => Some(TreeShakingMode::ModuleFragments),
1851            None => Some(TreeShakingMode::ReexportsOnly),
1852        })
1853        .cell()
1854    }
1855
1856    #[turbo_tasks::function]
1857    pub async fn turbopack_remove_unused_imports(
1858        self: Vc<Self>,
1859        mode: Vc<NextMode>,
1860    ) -> Result<Vc<bool>> {
1861        let remove_unused_imports = self
1862            .await?
1863            .experimental
1864            .turbopack_remove_unused_imports
1865            .unwrap_or(matches!(*mode.await?, NextMode::Build));
1866
1867        if remove_unused_imports && !*self.turbopack_remove_unused_exports(mode).await? {
1868            bail!(
1869                "`experimental.turbopackRemoveUnusedImports` cannot be enabled without also \
1870                 enabling `experimental.turbopackRemoveUnusedExports`"
1871            );
1872        }
1873
1874        Ok(Vc::cell(remove_unused_imports))
1875    }
1876
1877    #[turbo_tasks::function]
1878    pub async fn turbopack_remove_unused_exports(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1879        Ok(Vc::cell(
1880            self.experimental
1881                .turbopack_remove_unused_exports
1882                .unwrap_or(matches!(*mode.await?, NextMode::Build)),
1883        ))
1884    }
1885
1886    #[turbo_tasks::function]
1887    pub fn turbopack_infer_module_side_effects(&self) -> Vc<bool> {
1888        Vc::cell(
1889            self.experimental
1890                .turbopack_infer_module_side_effects
1891                .unwrap_or(true),
1892        )
1893    }
1894
1895    #[turbo_tasks::function]
1896    pub async fn module_ids(&self, mode: Vc<NextMode>) -> Result<Vc<ModuleIds>> {
1897        Ok(match *mode.await? {
1898            // Ignore configuration in development mode, HMR only works with `named`
1899            NextMode::Development => ModuleIds::Named.cell(),
1900            NextMode::Build => self
1901                .experimental
1902                .turbopack_module_ids
1903                .unwrap_or(ModuleIds::Deterministic)
1904                .cell(),
1905        })
1906    }
1907
1908    #[turbo_tasks::function]
1909    pub async fn turbo_minify(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1910        let minify = self.experimental.turbopack_minify;
1911        Ok(Vc::cell(
1912            minify.unwrap_or(matches!(*mode.await?, NextMode::Build)),
1913        ))
1914    }
1915
1916    #[turbo_tasks::function]
1917    pub async fn turbo_scope_hoisting(&self, mode: Vc<NextMode>) -> Result<Vc<bool>> {
1918        Ok(Vc::cell(match *mode.await? {
1919            // Ignore configuration in development mode to not break HMR
1920            NextMode::Development => false,
1921            NextMode::Build => self.experimental.turbopack_scope_hoisting.unwrap_or(true),
1922        }))
1923    }
1924
1925    #[turbo_tasks::function]
1926    pub async fn turbo_nested_async_chunking(
1927        &self,
1928        mode: Vc<NextMode>,
1929        client_side: bool,
1930    ) -> Result<Vc<bool>> {
1931        let option = if client_side {
1932            self.experimental
1933                .turbopack_client_side_nested_async_chunking
1934        } else {
1935            self.experimental
1936                .turbopack_server_side_nested_async_chunking
1937        };
1938        Ok(Vc::cell(if let Some(value) = option {
1939            value
1940        } else {
1941            match *mode.await? {
1942                NextMode::Development => false,
1943                NextMode::Build => client_side,
1944            }
1945        }))
1946    }
1947
1948    #[turbo_tasks::function]
1949    pub async fn turbopack_import_type_bytes(&self) -> Vc<bool> {
1950        Vc::cell(
1951            self.experimental
1952                .turbopack_import_type_bytes
1953                .unwrap_or(false),
1954        )
1955    }
1956
1957    #[turbo_tasks::function]
1958    pub async fn client_source_maps(&self, mode: Vc<NextMode>) -> Result<Vc<SourceMapsType>> {
1959        let input_source_maps = self
1960            .experimental
1961            .turbopack_input_source_maps
1962            .unwrap_or(true);
1963        let source_maps = self
1964            .experimental
1965            .turbopack_source_maps
1966            .unwrap_or(match &*mode.await? {
1967                NextMode::Development => true,
1968                NextMode::Build => self.production_browser_source_maps,
1969            });
1970        Ok(match (source_maps, input_source_maps) {
1971            (true, true) => SourceMapsType::Full,
1972            (true, false) => SourceMapsType::Partial,
1973            (false, _) => SourceMapsType::None,
1974        }
1975        .cell())
1976    }
1977
1978    #[turbo_tasks::function]
1979    pub fn server_source_maps(&self) -> Result<Vc<SourceMapsType>> {
1980        let input_source_maps = self
1981            .experimental
1982            .turbopack_input_source_maps
1983            .unwrap_or(true);
1984        let source_maps = self
1985            .experimental
1986            .turbopack_source_maps
1987            .or(self.experimental.server_source_maps)
1988            .unwrap_or(true);
1989        Ok(match (source_maps, input_source_maps) {
1990            (true, true) => SourceMapsType::Full,
1991            (true, false) => SourceMapsType::Partial,
1992            (false, _) => SourceMapsType::None,
1993        }
1994        .cell())
1995    }
1996
1997    #[turbo_tasks::function]
1998    pub fn turbopack_debug_ids(&self) -> Vc<bool> {
1999        Vc::cell(
2000            self.turbopack
2001                .as_ref()
2002                .and_then(|turbopack| turbopack.debug_ids)
2003                .unwrap_or(false),
2004        )
2005    }
2006
2007    #[turbo_tasks::function]
2008    pub fn typescript_tsconfig_path(&self) -> Result<Vc<Option<RcStr>>> {
2009        Ok(Vc::cell(
2010            self.typescript
2011                .tsconfig_path
2012                .as_ref()
2013                .map(|path| path.to_owned().into()),
2014        ))
2015    }
2016
2017    #[turbo_tasks::function]
2018    pub fn cross_origin(&self) -> Vc<OptionCrossOriginConfig> {
2019        Vc::cell(self.cross_origin.clone())
2020    }
2021
2022    #[turbo_tasks::function]
2023    pub fn i18n(&self) -> Vc<OptionI18NConfig> {
2024        Vc::cell(self.i18n.clone())
2025    }
2026
2027    #[turbo_tasks::function]
2028    pub fn output(&self) -> Vc<OptionOutputType> {
2029        Vc::cell(self.output.clone())
2030    }
2031
2032    #[turbo_tasks::function]
2033    pub fn output_file_tracing_includes(&self) -> Vc<OptionJsonValue> {
2034        Vc::cell(self.output_file_tracing_includes.clone())
2035    }
2036
2037    #[turbo_tasks::function]
2038    pub fn output_file_tracing_excludes(&self) -> Vc<OptionJsonValue> {
2039        Vc::cell(self.output_file_tracing_excludes.clone())
2040    }
2041
2042    #[turbo_tasks::function]
2043    pub fn fetch_client(&self) -> Vc<FetchClientConfig> {
2044        FetchClientConfig::default().cell()
2045    }
2046}
2047
2048/// A subset of ts/jsconfig that next.js implicitly
2049/// interops with.
2050#[turbo_tasks::value(serialization = "custom", eq = "manual")]
2051#[derive(Clone, Debug, Default, PartialEq, Deserialize, Encode, Decode)]
2052#[serde(rename_all = "camelCase")]
2053pub struct JsConfig {
2054    #[bincode(with = "turbo_bincode::serde_self_describing")]
2055    compiler_options: Option<serde_json::Value>,
2056}
2057
2058#[turbo_tasks::value_impl]
2059impl JsConfig {
2060    #[turbo_tasks::function]
2061    pub async fn from_string(string: Vc<RcStr>) -> Result<Vc<Self>> {
2062        let string = string.await?;
2063        let config: JsConfig = serde_json::from_str(&string)
2064            .with_context(|| format!("failed to parse next.config.js: {string}"))?;
2065
2066        Ok(config.cell())
2067    }
2068
2069    #[turbo_tasks::function]
2070    pub fn compiler_options(&self) -> Vc<serde_json::Value> {
2071        Vc::cell(self.compiler_options.clone().unwrap_or_default())
2072    }
2073}
2074
2075#[cfg(test)]
2076mod tests {
2077    use super::*;
2078
2079    #[test]
2080    fn test_serde_rule_config_item_options() {
2081        let json_value = serde_json::json!({
2082            "loaders": [],
2083            "as": "*.js",
2084            "condition": {
2085                "all": [
2086                    "production",
2087                    {"not": "foreign"},
2088                    {"any": [
2089                        "browser",
2090                        {
2091                            "path": { "type": "glob", "value": "*.svg"},
2092                            "content": {
2093                                "source": "@someTag",
2094                                "flags": ""
2095                            }
2096                        }
2097                    ]},
2098                ],
2099            }
2100        });
2101
2102        let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();
2103
2104        assert_eq!(
2105            rule_config,
2106            RuleConfigItem {
2107                loaders: vec![],
2108                rename_as: Some(rcstr!("*.js")),
2109                condition: Some(ConfigConditionItem::All(
2110                    [
2111                        ConfigConditionItem::Builtin(WebpackLoaderBuiltinCondition::Production),
2112                        ConfigConditionItem::Not(Box::new(ConfigConditionItem::Builtin(
2113                            WebpackLoaderBuiltinCondition::Foreign
2114                        ))),
2115                        ConfigConditionItem::Any(
2116                            vec![
2117                                ConfigConditionItem::Builtin(
2118                                    WebpackLoaderBuiltinCondition::Browser
2119                                ),
2120                                ConfigConditionItem::Base {
2121                                    path: Some(ConfigConditionPath::Glob(rcstr!("*.svg"))),
2122                                    content: Some(RegexComponents {
2123                                        source: rcstr!("@someTag"),
2124                                        flags: rcstr!(""),
2125                                    }),
2126                                    query: None,
2127                                },
2128                            ]
2129                            .into(),
2130                        ),
2131                    ]
2132                    .into(),
2133                )),
2134            }
2135        );
2136    }
2137}