11use std:: { collections:: BTreeSet , str:: FromStr } ;
22
33use anyhow:: { Context , Result , bail} ;
4+ use either:: Either ;
45use rustc_hash:: FxHashSet ;
56use serde:: { Deserialize , Deserializer , Serialize } ;
67use serde_json:: Value as JsonValue ;
@@ -558,7 +559,7 @@ pub enum RemotePatternProtocol {
558559pub struct TurbopackConfig {
559560 /// This option has been replaced by `rules`.
560561 pub loaders : Option < JsonValue > ,
561- pub rules : Option < FxIndexMap < RcStr , RuleConfigItemOrShortcut > > ,
562+ pub rules : Option < FxIndexMap < RcStr , RuleConfigCollection > > ,
562563 #[ turbo_tasks( trace_ignore) ]
563564 pub conditions : Option < FxIndexMap < RcStr , ConfigConditionItem > > ,
564565 pub resolve_alias : Option < FxIndexMap < RcStr , JsonValue > > ,
@@ -666,6 +667,16 @@ impl TryFrom<ConfigConditionItem> for ConditionItem {
666667 }
667668}
668669
670+ #[ derive(
671+ Clone , Debug , PartialEq , Eq , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
672+ ) ]
673+ #[ serde( rename_all = "camelCase" , untagged) ]
674+ pub enum RuleConfigItem {
675+ Options ( RuleConfigItemOptions ) ,
676+ LegacyConditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
677+ LegacyBoolean ( bool ) ,
678+ }
679+
669680#[ derive(
670681 Clone , Debug , PartialEq , Eq , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
671682) ]
@@ -678,23 +689,33 @@ pub struct RuleConfigItemOptions {
678689 pub condition : Option < ConfigConditionItem > ,
679690}
680691
681- #[ derive(
682- Clone , Debug , PartialEq , Eq , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
683- ) ]
684- #[ serde( rename_all = "camelCase" , untagged) ]
685- pub enum RuleConfigItemOrShortcut {
686- Loaders ( Vec < LoaderItem > ) ,
687- Advanced ( RuleConfigItem ) ,
692+ #[ derive( Clone , Debug , PartialEq , Eq , Serialize , TraceRawVcs , NonLocalValue , OperationValue ) ]
693+ #[ serde( transparent) ]
694+ pub struct RuleConfigCollection ( Vec < RuleConfigCollectionItem > ) ;
695+
696+ impl < ' de > Deserialize < ' de > for RuleConfigCollection {
697+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
698+ where
699+ D : Deserializer < ' de > ,
700+ {
701+ match either:: serde_untagged:: deserialize :: < Vec < RuleConfigCollectionItem > , RuleConfigItem , D > (
702+ deserializer,
703+ ) ? {
704+ Either :: Left ( collection) => Ok ( RuleConfigCollection ( collection) ) ,
705+ Either :: Right ( item) => Ok ( RuleConfigCollection ( vec ! [ RuleConfigCollectionItem :: Full (
706+ item,
707+ ) ] ) ) ,
708+ }
709+ }
688710}
689711
690712#[ derive(
691713 Clone , Debug , PartialEq , Eq , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
692714) ]
693- #[ serde( rename_all = "camelCase" , untagged) ]
694- pub enum RuleConfigItem {
695- Options ( RuleConfigItemOptions ) ,
696- LegacyConditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
697- LegacyBoolean ( bool ) ,
715+ #[ serde( untagged) ]
716+ pub enum RuleConfigCollectionItem {
717+ Shorthand ( LoaderItem ) ,
718+ Full ( RuleConfigItem ) ,
698719}
699720
700721#[ derive(
@@ -1392,11 +1413,12 @@ impl NextConfig {
13921413 return Ok ( Vc :: cell ( Vec :: new ( ) ) ) ;
13931414 }
13941415 let mut rules = Vec :: new ( ) ;
1395- for ( glob, rule) in turbo_rules. iter ( ) {
1396- fn transform_loaders ( loaders : & [ LoaderItem ] ) -> ResolvedVc < WebpackLoaderItems > {
1416+ for ( glob, rule_collection) in turbo_rules. iter ( ) {
1417+ fn transform_loaders (
1418+ loaders : & mut dyn Iterator < Item = & LoaderItem > ,
1419+ ) -> ResolvedVc < WebpackLoaderItems > {
13971420 ResolvedVc :: cell (
13981421 loaders
1399- . iter ( )
14001422 . map ( |item| match item {
14011423 LoaderItem :: LoaderName ( name) => WebpackLoaderItem {
14021424 loader : name. clone ( ) ,
@@ -1445,65 +1467,67 @@ impl NextConfig {
14451467 }
14461468 }
14471469 let config_file_path = || project_path. join ( & self . config_file_name ) ;
1448- match rule {
1449- RuleConfigItemOrShortcut :: Loaders ( loaders) => {
1450- rules. push ( (
1451- glob. clone ( ) ,
1452- LoaderRuleItem {
1453- loaders : transform_loaders ( loaders) ,
1454- rename_as : None ,
1455- condition : None ,
1456- } ,
1457- ) ) ;
1458- }
1459- RuleConfigItemOrShortcut :: Advanced ( rule) => {
1460- if let FindRuleResult :: Found ( RuleConfigItemOptions {
1461- loaders,
1462- rename_as,
1463- condition,
1464- } ) = find_rule ( rule, & active_conditions)
1465- {
1466- // If the extension contains a wildcard, and the rename_as does not,
1467- // emit an issue to prevent users from encountering duplicate module names.
1468- if glob. contains ( "*" )
1469- && let Some ( rename_as) = rename_as. as_ref ( )
1470- && !rename_as. contains ( "*" )
1470+ for item in & rule_collection. 0 {
1471+ match item {
1472+ RuleConfigCollectionItem :: Shorthand ( loaders) => {
1473+ rules. push ( (
1474+ glob. clone ( ) ,
1475+ LoaderRuleItem {
1476+ loaders : transform_loaders ( & mut [ loaders] . into_iter ( ) ) ,
1477+ rename_as : None ,
1478+ condition : None ,
1479+ } ,
1480+ ) ) ;
1481+ }
1482+ RuleConfigCollectionItem :: Full ( rule_config_item) => {
1483+ if let FindRuleResult :: Found ( RuleConfigItemOptions {
1484+ loaders,
1485+ rename_as,
1486+ condition,
1487+ } ) = find_rule ( rule_config_item, & active_conditions)
14711488 {
1472- InvalidLoaderRuleRenameAsIssue {
1473- glob : glob. clone ( ) ,
1474- config_file_path : config_file_path ( ) ?,
1475- rename_as : rename_as. clone ( ) ,
1476- }
1477- . resolved_cell ( )
1478- . emit ( ) ;
1479- }
1480-
1481- // convert from Next.js-specific condition type to internal Turbopack
1482- // condition type
1483- let condition = if let Some ( condition) = condition {
1484- if let Ok ( cond) = ConditionItem :: try_from ( condition. clone ( ) ) {
1485- Some ( cond)
1486- } else {
1487- InvalidLoaderRuleConditionIssue {
1488- condition : condition. clone ( ) ,
1489+ // If the extension contains a wildcard, and the rename_as does not,
1490+ // emit an issue to prevent users from encountering duplicate module
1491+ // names.
1492+ if glob. contains ( "*" )
1493+ && let Some ( rename_as) = rename_as. as_ref ( )
1494+ && !rename_as. contains ( "*" )
1495+ {
1496+ InvalidLoaderRuleRenameAsIssue {
1497+ glob : glob. clone ( ) ,
14891498 config_file_path : config_file_path ( ) ?,
1499+ rename_as : rename_as. clone ( ) ,
14901500 }
14911501 . resolved_cell ( )
14921502 . emit ( ) ;
1493- None
14941503 }
1495- } else {
1496- None
1497- } ;
14981504
1499- rules. push ( (
1500- glob. clone ( ) ,
1501- LoaderRuleItem {
1502- loaders : transform_loaders ( loaders) ,
1503- rename_as : rename_as. clone ( ) ,
1504- condition,
1505- } ,
1506- ) ) ;
1505+ // convert from Next.js-specific condition type to internal Turbopack
1506+ // condition type
1507+ let condition = if let Some ( condition) = condition {
1508+ if let Ok ( cond) = ConditionItem :: try_from ( condition. clone ( ) ) {
1509+ Some ( cond)
1510+ } else {
1511+ InvalidLoaderRuleConditionIssue {
1512+ condition : condition. clone ( ) ,
1513+ config_file_path : config_file_path ( ) ?,
1514+ }
1515+ . resolved_cell ( )
1516+ . emit ( ) ;
1517+ None
1518+ }
1519+ } else {
1520+ None
1521+ } ;
1522+ rules. push ( (
1523+ glob. clone ( ) ,
1524+ LoaderRuleItem {
1525+ loaders : transform_loaders ( & mut loaders. iter ( ) ) ,
1526+ rename_as : rename_as. clone ( ) ,
1527+ condition,
1528+ } ,
1529+ ) ) ;
1530+ }
15071531 }
15081532 }
15091533 }
0 commit comments