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
54pub 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 config_file: Option<RcStr>,
95 config_file_name: RcStr,
96
97 cache_max_memory_size: Option<f64>,
101 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 output_file_tracing_root: Option<RcStr>,
139
140 bundle_pages_router_dependencies: Option<bool>,
145
146 server_external_packages: Option<Vec<RcStr>>,
151
152 output_hash_salt: Option<RcStr>,
155
156 #[serde(rename = "_originalRedirects")]
157 original_redirects: Option<Vec<Redirect>>,
158
159 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 }
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 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 #[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 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
925const DEFAULT_REQUEST_COST: f32 = 20_000.0;
927const DEFAULT_WEIGHT_DISTRIBUTION: f32 = 0.1;
929
930#[derive(
932 Clone, Debug, Default, PartialEq, Deserialize, NonLocalValue, OperationValue, Encode, Decode,
933)]
934#[serde(rename_all = "camelCase")]
935pub struct TurbopackChunkingConfig {
936 clusters: Option<Vec<Vec<RegexComponents>>>,
939 first_page_load_priority: Option<f64>,
943 priority_routes: Option<Vec<RegexComponents>>,
947 priority_boost: Option<f64>,
950 request_cost: Option<u64>,
954 min_chunk_size: Option<usize>,
957 max_chunk_count_per_group: Option<usize>,
959 max_merge_chunk_size: Option<usize>,
962 generate_component_chunks: Option<bool>,
965 min_component_chunk_size: Option<usize>,
968}
969
970#[turbo_tasks::value]
971pub struct TurbopackChunking {
972 clusters: Vec<EsRegexSet>,
974 pub first_page_load_priority: Option<u32>,
976 priority_routes: EsRegexSet,
978 pub priority_boost_percent: Option<u32>,
981 pub request_cost: Option<u64>,
983 pub min_chunk_size: Option<usize>,
985 pub max_chunk_count_per_group: Option<usize>,
987 pub max_merge_chunk_size: Option<usize>,
989 pub min_component_chunk_size: Option<usize>,
991 pub generate_component_chunks: bool,
993}
994
995impl TurbopackChunking {
996 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
1014fn 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
1028fn 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 allowed_revalidate_header_keys: Option<Vec<RcStr>>,
1078 client_router_filter: Option<bool>,
1079 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 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 #[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 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: Option<CssChunkingConfig>,
1116
1117 turbopack_chunking: Option<TurbopackChunkingConfig>,
1118
1119 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 fallback_node_polyfills: Option<bool>, 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_server_react: Option<bool>,
1152 optimize_package_imports: Option<Vec<RcStr>>,
1155 taint: Option<bool>,
1156 proxy_timeout: Option<f64>,
1157 server_minification: Option<bool>,
1159 server_source_maps: Option<bool>,
1161 swc_trace_profiling: Option<bool>,
1162 transition_indicator: Option<bool>,
1163 gesture_transition: Option<bool>,
1164 concurrent_router_queue: Option<bool>,
1167 #[serde(rename = "blockingSSR")]
1170 blocking_ssr: Option<bool>,
1171 trust_host_header: Option<bool>,
1173
1174 #[bincode(with = "turbo_bincode::serde_self_describing")]
1175 url_imports: Option<serde_json::Value>,
1176 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 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 turbopack_lazy_dynamic_imports: Option<bool>,
1205 #[serde(rename = "turbopackLazyDynamicImportsSSR")]
1208 turbopack_lazy_dynamic_imports_ssr: Option<bool>,
1209 turbopack_import_type_bytes: Option<bool>,
1210 #[serde(default)]
1212 turbopack_use_builtin_sass: Option<bool>,
1213 #[serde(default)]
1216 turbopack_use_builtin_babel: Option<bool>,
1217 #[serde(default)]
1221 turbopack_local_postcss_config: Option<bool>,
1222 global_not_found: Option<bool>,
1224 explicit_parallel_route_children: Option<bool>,
1226 strict_route_matching: Option<bool>,
1229 turbopack_rust_react_compiler: Option<bool>,
1231 turbopack_remove_unused_imports: Option<bool>,
1233 turbopack_remove_unused_exports: Option<bool>,
1235 turbopack_infer_module_side_effects: Option<bool>,
1237 turbopack_cjs_tree_shaking: Option<bool>,
1239 turbopack_mangle_export_names: Option<bool>,
1242 turbopack_mangle_via_materialized_namespace_object: Option<bool>,
1244 turbopack_cjs_scope_hoisting: Option<bool>,
1246 turbopack_cross_module_constants: Option<bool>,
1248 devtool_segment_explorer: Option<bool>,
1250 report_system_env_inlining: Option<String>,
1252 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 ServerActionsConfig(ServerActions),
1287
1288 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]
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 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
1341impl 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#[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 fn client(&self) -> Option<bool> {
1437 match self {
1438 Self::Boolean(enabled) => Some(*enabled),
1439 Self::Config { client, .. } => *client,
1440 }
1441 }
1442
1443 fn server(&self) -> Option<bool> {
1445 match self {
1446 Self::Boolean(enabled) => Some(*enabled),
1447 Self::Config { server, .. } => *server,
1448 }
1449 }
1450
1451 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#[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 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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 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 .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 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 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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 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 #[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 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 #[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#[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
2791fn 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
2804pub 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 "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}