next_core/
next_config.rs

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