Skip to main content

next_core/
next_config.rs

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