@@ -566,53 +566,92 @@ 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
+ // We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
611
+ // match nearly every object, since it has no required field.
612
+ #[ serde( deny_unknown_fields) ]
613
+ pub enum ConfigConditionItem {
614
+ #[ serde( rename = "all" ) ]
615
+ All ( Box < [ ConfigConditionItem ] > ) ,
616
+ #[ serde( rename = "any" ) ]
617
+ Any ( Box < [ ConfigConditionItem ] > ) ,
618
+ #[ serde( rename = "not" ) ]
619
+ Not ( Box < ConfigConditionItem > ) ,
620
+ #[ serde( untagged) ]
621
+ Builtin ( WebpackLoaderBuiltinCondition ) ,
622
+ #[ serde( untagged) ]
623
+ Base {
624
+ path : Option < ConfigConditionPath > ,
625
+ content : Option < RegexComponents > ,
626
+ } ,
605
627
}
606
628
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 ( ) ?,
629
+ impl TryFrom < ConfigConditionItem > for ConditionItem {
630
+ type Error = anyhow:: Error ;
631
+
632
+ fn try_from ( config : ConfigConditionItem ) -> Result < Self > {
633
+ let try_from_vec = |conds : Box < [ _ ] > | {
634
+ conds
635
+ . into_iter ( )
636
+ . map ( ConditionItem :: try_from)
637
+ . collect :: < Result < _ > > ( )
638
+ } ;
639
+ Ok ( match config {
640
+ ConfigConditionItem :: All ( conds) => ConditionItem :: All ( try_from_vec ( conds) ?) ,
641
+ ConfigConditionItem :: Any ( conds) => ConditionItem :: Any ( try_from_vec ( conds) ?) ,
642
+ ConfigConditionItem :: Not ( cond) => ConditionItem :: Not ( Box :: new ( ( * cond) . try_into ( ) ?) ) ,
643
+ ConfigConditionItem :: Builtin ( cond) => {
644
+ ConditionItem :: Builtin ( RcStr :: from ( cond. as_str ( ) ) )
645
+ }
646
+ ConfigConditionItem :: Base { path, content } => ConditionItem :: Base {
647
+ path : path. map ( ConditionPath :: try_from) . transpose ( ) ?,
648
+ content : content
649
+ . map ( EsRegex :: try_from)
650
+ . transpose ( ) ?
651
+ . map ( EsRegex :: resolved_cell) ,
652
+ } ,
612
653
} )
613
654
}
614
-
615
- type Error = anyhow:: Error ;
616
655
}
617
656
618
657
#[ derive(
@@ -623,6 +662,7 @@ pub struct RuleConfigItemOptions {
623
662
pub loaders : Vec < LoaderItem > ,
624
663
#[ serde( default , alias = "as" ) ]
625
664
pub rename_as : Option < RcStr > ,
665
+ pub condition : Option < ConfigConditionItem > ,
626
666
}
627
667
628
668
#[ derive(
@@ -640,8 +680,8 @@ pub enum RuleConfigItemOrShortcut {
640
680
#[ serde( rename_all = "camelCase" , untagged) ]
641
681
pub enum RuleConfigItem {
642
682
Options ( RuleConfigItemOptions ) ,
643
- Conditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
644
- Boolean ( bool ) ,
683
+ LegacyConditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
684
+ LegacyBoolean ( bool ) ,
645
685
}
646
686
647
687
#[ derive(
@@ -1320,13 +1360,16 @@ impl NextConfig {
1320
1360
NotFound ,
1321
1361
Break ,
1322
1362
}
1363
+ // This logic is needed for the `LegacyConditional`/`LegacyBoolean` configuration
1364
+ // syntax. This is technically public syntax, but was never documented and it is
1365
+ // unlikely that anyone is depending on it (outside of some Next.js internals).
1323
1366
fn find_rule < ' a > (
1324
1367
rule : & ' a RuleConfigItem ,
1325
1368
active_conditions : & BTreeSet < WebpackLoaderBuiltinCondition > ,
1326
1369
) -> FindRuleResult < ' a > {
1327
1370
match rule {
1328
1371
RuleConfigItem :: Options ( rule) => FindRuleResult :: Found ( rule) ,
1329
- RuleConfigItem :: Conditional ( map) => {
1372
+ RuleConfigItem :: LegacyConditional ( map) => {
1330
1373
for ( condition, rule) in map. iter ( ) {
1331
1374
let condition = WebpackLoaderBuiltinCondition :: from_str ( condition) ;
1332
1375
if let Ok ( condition) = condition
@@ -1346,7 +1389,7 @@ impl NextConfig {
1346
1389
}
1347
1390
FindRuleResult :: NotFound
1348
1391
}
1349
- RuleConfigItem :: Boolean ( _) => FindRuleResult :: Break ,
1392
+ RuleConfigItem :: LegacyBoolean ( _) => FindRuleResult :: Break ,
1350
1393
}
1351
1394
}
1352
1395
match rule {
@@ -1356,12 +1399,16 @@ impl NextConfig {
1356
1399
LoaderRuleItem {
1357
1400
loaders : transform_loaders ( loaders) ,
1358
1401
rename_as : None ,
1402
+ condition : None ,
1359
1403
} ,
1360
1404
) ;
1361
1405
}
1362
1406
RuleConfigItemOrShortcut :: Advanced ( rule) => {
1363
- if let FindRuleResult :: Found ( RuleConfigItemOptions { loaders, rename_as } ) =
1364
- find_rule ( rule, & active_conditions)
1407
+ if let FindRuleResult :: Found ( RuleConfigItemOptions {
1408
+ loaders,
1409
+ rename_as,
1410
+ condition,
1411
+ } ) = find_rule ( rule, & active_conditions)
1365
1412
{
1366
1413
// If the extension contains a wildcard, and the rename_as does not,
1367
1414
// emit an issue to prevent users from encountering duplicate module names.
@@ -1383,6 +1430,12 @@ impl NextConfig {
1383
1430
LoaderRuleItem {
1384
1431
loaders : transform_loaders ( loaders) ,
1385
1432
rename_as : rename_as. clone ( ) ,
1433
+ // TODO(bgw): Emit `InvalidLoaderRuleError` if this fails instead of
1434
+ // using try (?)
1435
+ condition : condition
1436
+ . clone ( )
1437
+ . map ( ConditionItem :: try_from)
1438
+ . transpose ( ) ?,
1386
1439
} ,
1387
1440
) ;
1388
1441
}
@@ -1820,3 +1873,66 @@ impl JsConfig {
1820
1873
Vc :: cell ( self . compiler_options . clone ( ) . unwrap_or_default ( ) )
1821
1874
}
1822
1875
}
1876
+
1877
+ #[ cfg( test) ]
1878
+ mod tests {
1879
+ use super :: * ;
1880
+
1881
+ #[ test]
1882
+ fn test_serde_rule_config_item_options ( ) {
1883
+ let json_value = serde_json:: json!( {
1884
+ "loaders" : [ ] ,
1885
+ "as" : "*.js" ,
1886
+ "condition" : {
1887
+ "all" : [
1888
+ "production" ,
1889
+ { "not" : "foreign" } ,
1890
+ { "any" : [
1891
+ "browser" ,
1892
+ {
1893
+ "path" : { "type" : "glob" , "value" : "*.svg" } ,
1894
+ "content" : {
1895
+ "source" : "@someTag" ,
1896
+ "flags" : ""
1897
+ }
1898
+ }
1899
+ ] } ,
1900
+ ] ,
1901
+ }
1902
+ } ) ;
1903
+
1904
+ let rule_config: RuleConfigItemOptions = serde_json:: from_value ( json_value) . unwrap ( ) ;
1905
+
1906
+ assert_eq ! (
1907
+ rule_config,
1908
+ RuleConfigItemOptions {
1909
+ loaders: vec![ ] ,
1910
+ rename_as: Some ( rcstr!( "*.js" ) ) ,
1911
+ condition: Some ( ConfigConditionItem :: All (
1912
+ [
1913
+ ConfigConditionItem :: Builtin ( WebpackLoaderBuiltinCondition :: Production ) ,
1914
+ ConfigConditionItem :: Not ( Box :: new( ConfigConditionItem :: Builtin (
1915
+ WebpackLoaderBuiltinCondition :: Foreign
1916
+ ) ) ) ,
1917
+ ConfigConditionItem :: Any (
1918
+ vec![
1919
+ ConfigConditionItem :: Builtin (
1920
+ WebpackLoaderBuiltinCondition :: Browser
1921
+ ) ,
1922
+ ConfigConditionItem :: Base {
1923
+ path: Some ( ConfigConditionPath :: Glob ( rcstr!( "*.svg" ) ) ) ,
1924
+ content: Some ( RegexComponents {
1925
+ source: rcstr!( "@someTag" ) ,
1926
+ flags: rcstr!( "" ) ,
1927
+ } ) ,
1928
+ } ,
1929
+ ]
1930
+ . into( ) ,
1931
+ ) ,
1932
+ ]
1933
+ . into( ) ,
1934
+ ) ) ,
1935
+ }
1936
+ ) ;
1937
+ }
1938
+ }
0 commit comments