@@ -566,53 +566,90 @@ pub struct TurbopackConfig {
566
566
pub module_ids : Option < ModuleIds > ,
567
567
}
568
568
569
- #[ derive( Serialize , Deserialize , Clone , PartialEq , Eq , Debug ) ]
569
+ #[ derive(
570
+ Serialize , Deserialize , Clone , PartialEq , Eq , Debug , TraceRawVcs , NonLocalValue , OperationValue ,
571
+ ) ]
570
572
pub struct RegexComponents {
571
573
source : RcStr ,
572
574
flags : RcStr ,
573
575
}
574
576
575
- #[ derive( Clone , PartialEq , Eq , Debug , Serialize , Deserialize ) ]
577
+ #[ derive(
578
+ Clone , PartialEq , Eq , Debug , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
579
+ ) ]
576
580
#[ serde( tag = "type" , content = "value" , rename_all = "camelCase" ) ]
577
581
pub enum ConfigConditionPath {
578
582
Glob ( RcStr ) ,
579
583
Regex ( RegexComponents ) ,
580
584
}
581
585
582
- impl TryInto < ConditionPath > for ConfigConditionPath {
583
- fn try_into ( self ) -> Result < ConditionPath > {
584
- Ok ( match self {
586
+ impl TryFrom < ConfigConditionPath > for ConditionPath {
587
+ type Error = anyhow:: Error ;
588
+
589
+ fn try_from ( config : ConfigConditionPath ) -> Result < ConditionPath > {
590
+ Ok ( match config {
585
591
ConfigConditionPath :: Glob ( path) => ConditionPath :: Glob ( path) ,
586
- ConfigConditionPath :: Regex ( path) => ConditionPath :: Regex ( path. try_into ( ) ?) ,
592
+ ConfigConditionPath :: Regex ( path) => {
593
+ ConditionPath :: Regex ( EsRegex :: try_from ( path) ?. resolved_cell ( ) )
594
+ }
587
595
} )
588
596
}
597
+ }
589
598
599
+ impl TryFrom < RegexComponents > for EsRegex {
590
600
type Error = anyhow:: Error ;
591
- }
592
601
593
- impl TryInto < ResolvedVc < EsRegex > > for RegexComponents {
594
- fn try_into ( self ) -> Result < ResolvedVc < EsRegex > > {
595
- Ok ( EsRegex :: new ( & self . source , & self . flags ) ?. resolved_cell ( ) )
602
+ fn try_from ( components : RegexComponents ) -> Result < EsRegex > {
603
+ EsRegex :: new ( & components. source , & components. flags )
596
604
}
597
-
598
- type Error = anyhow:: Error ;
599
605
}
600
606
601
- #[ derive( Serialize , Deserialize , Clone , Debug , PartialEq ) ]
602
- pub struct ConfigConditionItem {
603
- pub path : Option < ConfigConditionPath > ,
604
- pub content : Option < RegexComponents > ,
607
+ #[ derive(
608
+ Serialize , Deserialize , Clone , PartialEq , Eq , Debug , TraceRawVcs , NonLocalValue , OperationValue ,
609
+ ) ]
610
+ #[ serde( deny_unknown_fields) ]
611
+ pub enum ConfigConditionItem {
612
+ #[ serde( rename = "all" , alias = "and" ) ]
613
+ All ( Box < [ ConfigConditionItem ] > ) ,
614
+ #[ serde( rename = "any" , alias = "or" ) ]
615
+ Any ( Box < [ ConfigConditionItem ] > ) ,
616
+ #[ serde( rename = "not" ) ]
617
+ Not ( Box < ConfigConditionItem > ) ,
618
+ #[ serde( untagged) ]
619
+ Builtin ( WebpackLoaderBuiltinCondition ) ,
620
+ #[ serde( untagged) ]
621
+ Base {
622
+ path : Option < ConfigConditionPath > ,
623
+ content : Option < RegexComponents > ,
624
+ } ,
605
625
}
606
626
607
- impl TryInto < ConditionItem > for ConfigConditionItem {
608
- fn try_into ( self ) -> Result < ConditionItem > {
609
- Ok ( ConditionItem {
610
- path : self . path . map ( |p| p. try_into ( ) ) . transpose ( ) ?,
611
- content : self . content . map ( |r| r. try_into ( ) ) . transpose ( ) ?,
627
+ impl TryFrom < ConfigConditionItem > for ConditionItem {
628
+ type Error = anyhow:: Error ;
629
+
630
+ fn try_from ( config : ConfigConditionItem ) -> Result < Self > {
631
+ let try_from_vec = |conds : Box < [ _ ] > | {
632
+ conds
633
+ . into_iter ( )
634
+ . map ( ConditionItem :: try_from)
635
+ . collect :: < Result < _ > > ( )
636
+ } ;
637
+ Ok ( match config {
638
+ ConfigConditionItem :: All ( conds) => ConditionItem :: All ( try_from_vec ( conds) ?) ,
639
+ ConfigConditionItem :: Any ( conds) => ConditionItem :: Any ( try_from_vec ( conds) ?) ,
640
+ ConfigConditionItem :: Not ( cond) => ConditionItem :: Not ( Box :: new ( ( * cond) . try_into ( ) ?) ) ,
641
+ ConfigConditionItem :: Builtin ( cond) => {
642
+ ConditionItem :: Builtin ( RcStr :: from ( cond. as_str ( ) ) )
643
+ }
644
+ ConfigConditionItem :: Base { path, content } => ConditionItem :: Base {
645
+ path : path. map ( ConditionPath :: try_from) . transpose ( ) ?,
646
+ content : content
647
+ . map ( EsRegex :: try_from)
648
+ . transpose ( ) ?
649
+ . map ( EsRegex :: resolved_cell) ,
650
+ } ,
612
651
} )
613
652
}
614
-
615
- type Error = anyhow:: Error ;
616
653
}
617
654
618
655
#[ derive(
@@ -623,6 +660,7 @@ pub struct RuleConfigItemOptions {
623
660
pub loaders : Vec < LoaderItem > ,
624
661
#[ serde( default , alias = "as" ) ]
625
662
pub rename_as : Option < RcStr > ,
663
+ pub condition : Option < ConfigConditionItem > ,
626
664
}
627
665
628
666
#[ derive(
@@ -640,8 +678,8 @@ pub enum RuleConfigItemOrShortcut {
640
678
#[ serde( rename_all = "camelCase" , untagged) ]
641
679
pub enum RuleConfigItem {
642
680
Options ( RuleConfigItemOptions ) ,
643
- Conditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
644
- Boolean ( bool ) ,
681
+ LegacyConditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
682
+ LegacyBoolean ( bool ) ,
645
683
}
646
684
647
685
#[ derive(
@@ -1320,13 +1358,16 @@ impl NextConfig {
1320
1358
NotFound ,
1321
1359
Break ,
1322
1360
}
1361
+ // This logic is needed for the `LegacyConditional`/`LegacyBoolean` configuration
1362
+ // syntax. This is technically public syntax, but was never documented and it is
1363
+ // unlikely that anyone is depending on it (outside of some Next.js internals).
1323
1364
fn find_rule < ' a > (
1324
1365
rule : & ' a RuleConfigItem ,
1325
1366
active_conditions : & BTreeSet < WebpackLoaderBuiltinCondition > ,
1326
1367
) -> FindRuleResult < ' a > {
1327
1368
match rule {
1328
1369
RuleConfigItem :: Options ( rule) => FindRuleResult :: Found ( rule) ,
1329
- RuleConfigItem :: Conditional ( map) => {
1370
+ RuleConfigItem :: LegacyConditional ( map) => {
1330
1371
for ( condition, rule) in map. iter ( ) {
1331
1372
let condition = WebpackLoaderBuiltinCondition :: from_str ( condition) ;
1332
1373
if let Ok ( condition) = condition
@@ -1346,7 +1387,7 @@ impl NextConfig {
1346
1387
}
1347
1388
FindRuleResult :: NotFound
1348
1389
}
1349
- RuleConfigItem :: Boolean ( _) => FindRuleResult :: Break ,
1390
+ RuleConfigItem :: LegacyBoolean ( _) => FindRuleResult :: Break ,
1350
1391
}
1351
1392
}
1352
1393
match rule {
@@ -1356,12 +1397,16 @@ impl NextConfig {
1356
1397
LoaderRuleItem {
1357
1398
loaders : transform_loaders ( loaders) ,
1358
1399
rename_as : None ,
1400
+ condition : None ,
1359
1401
} ,
1360
1402
) ;
1361
1403
}
1362
1404
RuleConfigItemOrShortcut :: Advanced ( rule) => {
1363
- if let FindRuleResult :: Found ( RuleConfigItemOptions { loaders, rename_as } ) =
1364
- find_rule ( rule, & active_conditions)
1405
+ if let FindRuleResult :: Found ( RuleConfigItemOptions {
1406
+ loaders,
1407
+ rename_as,
1408
+ condition,
1409
+ } ) = find_rule ( rule, & active_conditions)
1365
1410
{
1366
1411
// If the extension contains a wildcard, and the rename_as does not,
1367
1412
// emit an issue to prevent users from encountering duplicate module names.
@@ -1383,6 +1428,12 @@ impl NextConfig {
1383
1428
LoaderRuleItem {
1384
1429
loaders : transform_loaders ( loaders) ,
1385
1430
rename_as : rename_as. clone ( ) ,
1431
+ // TODO(bgw): Emit `InvalidLoaderRuleError` if this fails instead of
1432
+ // using try (?)
1433
+ condition : condition
1434
+ . clone ( )
1435
+ . map ( ConditionItem :: try_from)
1436
+ . transpose ( ) ?,
1386
1437
} ,
1387
1438
) ;
1388
1439
}
@@ -1820,3 +1871,66 @@ impl JsConfig {
1820
1871
Vc :: cell ( self . compiler_options . clone ( ) . unwrap_or_default ( ) )
1821
1872
}
1822
1873
}
1874
+
1875
+ #[ cfg( test) ]
1876
+ mod tests {
1877
+ use super :: * ;
1878
+
1879
+ #[ test]
1880
+ fn test_serde_rule_config_item_options ( ) {
1881
+ let json_value = serde_json:: json!( {
1882
+ "loaders" : [ ] ,
1883
+ "as" : "*.js" ,
1884
+ "condition" : {
1885
+ "all" : [
1886
+ "production" ,
1887
+ { "not" : "foreign" } ,
1888
+ { "any" : [
1889
+ "browser" ,
1890
+ {
1891
+ "path" : { "type" : "glob" , "value" : "*.svg" } ,
1892
+ "content" : {
1893
+ "source" : "@someTag" ,
1894
+ "flags" : ""
1895
+ }
1896
+ }
1897
+ ] } ,
1898
+ ] ,
1899
+ }
1900
+ } ) ;
1901
+
1902
+ let rule_config: RuleConfigItemOptions = serde_json:: from_value ( json_value) . unwrap ( ) ;
1903
+
1904
+ assert_eq ! (
1905
+ rule_config,
1906
+ RuleConfigItemOptions {
1907
+ loaders: vec![ ] ,
1908
+ rename_as: Some ( rcstr!( "*.js" ) ) ,
1909
+ condition: Some ( ConfigConditionItem :: All (
1910
+ [
1911
+ ConfigConditionItem :: Builtin ( WebpackLoaderBuiltinCondition :: Production ) ,
1912
+ ConfigConditionItem :: Not ( Box :: new( ConfigConditionItem :: Builtin (
1913
+ WebpackLoaderBuiltinCondition :: Foreign
1914
+ ) ) ) ,
1915
+ ConfigConditionItem :: Any (
1916
+ vec![
1917
+ ConfigConditionItem :: Builtin (
1918
+ WebpackLoaderBuiltinCondition :: Browser
1919
+ ) ,
1920
+ ConfigConditionItem :: Base {
1921
+ path: Some ( ConfigConditionPath :: Glob ( rcstr!( "*.svg" ) ) ) ,
1922
+ content: Some ( RegexComponents {
1923
+ source: rcstr!( "@someTag" ) ,
1924
+ flags: rcstr!( "" ) ,
1925
+ } ) ,
1926
+ } ,
1927
+ ]
1928
+ . into( ) ,
1929
+ ) ,
1930
+ ]
1931
+ . into( ) ,
1932
+ ) ) ,
1933
+ }
1934
+ ) ;
1935
+ }
1936
+ }
0 commit comments