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