Skip to main content

next_core/
next_config.rs

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