@@ -566,53 +566,104 @@ 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
+ ) ]
572
+ #[ serde( deny_unknown_fields) ]
570
573
pub struct RegexComponents {
571
574
source : RcStr ,
572
575
flags : RcStr ,
573
576
}
574
577
575
- #[ derive( Clone , PartialEq , Eq , Debug , Serialize , Deserialize ) ]
576
- #[ serde( tag = "type" , content = "value" , rename_all = "camelCase" ) ]
578
+ /// This type should not be hand-written, but instead `packages/next/src/build/swc/index.ts` will
579
+ /// transform a JS `RegExp` to a `RegexComponents` or a string to a `Glob` before passing it to us.
580
+ ///
581
+ /// This is needed because `RegExp` objects are not otherwise serializable.
582
+ #[ derive(
583
+ Clone , PartialEq , Eq , Debug , Serialize , Deserialize , TraceRawVcs , NonLocalValue , OperationValue ,
584
+ ) ]
585
+ #[ serde(
586
+ tag = "type" ,
587
+ content = "value" ,
588
+ rename_all = "camelCase" ,
589
+ deny_unknown_fields
590
+ ) ]
577
591
pub enum ConfigConditionPath {
578
592
Glob ( RcStr ) ,
579
593
Regex ( RegexComponents ) ,
580
594
}
581
595
582
- impl TryInto < ConditionPath > for ConfigConditionPath {
583
- fn try_into ( self ) -> Result < ConditionPath > {
584
- Ok ( match self {
596
+ impl TryFrom < ConfigConditionPath > for ConditionPath {
597
+ type Error = anyhow:: Error ;
598
+
599
+ fn try_from ( config : ConfigConditionPath ) -> Result < ConditionPath > {
600
+ Ok ( match config {
585
601
ConfigConditionPath :: Glob ( path) => ConditionPath :: Glob ( path) ,
586
- ConfigConditionPath :: Regex ( path) => ConditionPath :: Regex ( path. try_into ( ) ?) ,
602
+ ConfigConditionPath :: Regex ( path) => {
603
+ ConditionPath :: Regex ( EsRegex :: try_from ( path) ?. resolved_cell ( ) )
604
+ }
587
605
} )
588
606
}
607
+ }
589
608
609
+ impl TryFrom < RegexComponents > for EsRegex {
590
610
type Error = anyhow:: Error ;
591
- }
592
611
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 ( ) )
612
+ fn try_from ( components : RegexComponents ) -> Result < EsRegex > {
613
+ EsRegex :: new ( & components. source , & components. flags )
596
614
}
597
-
598
- type Error = anyhow:: Error ;
599
615
}
600
616
601
- #[ derive( Serialize , Deserialize , Clone , Debug , PartialEq ) ]
602
- pub struct ConfigConditionItem {
603
- pub path : Option < ConfigConditionPath > ,
604
- pub content : Option < RegexComponents > ,
617
+ #[ derive(
618
+ Serialize , Deserialize , Clone , PartialEq , Eq , Debug , TraceRawVcs , NonLocalValue , OperationValue ,
619
+ ) ]
620
+ // We can end up with confusing behaviors if we silently ignore extra properties, since `Base` will
621
+ // match nearly every object, since it has no required field.
622
+ #[ serde( deny_unknown_fields) ]
623
+ pub enum ConfigConditionItem {
624
+ #[ serde( rename = "all" ) ]
625
+ All ( Box < [ ConfigConditionItem ] > ) ,
626
+ #[ serde( rename = "any" ) ]
627
+ Any ( Box < [ ConfigConditionItem ] > ) ,
628
+ #[ serde( rename = "not" ) ]
629
+ Not ( Box < ConfigConditionItem > ) ,
630
+ #[ serde( untagged) ]
631
+ Builtin ( WebpackLoaderBuiltinCondition ) ,
632
+ #[ serde( untagged) ]
633
+ Base {
634
+ #[ serde( default ) ]
635
+ path : Option < ConfigConditionPath > ,
636
+ #[ serde( default ) ]
637
+ content : Option < RegexComponents > ,
638
+ } ,
605
639
}
606
640
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 ( ) ?,
641
+ impl TryFrom < ConfigConditionItem > for ConditionItem {
642
+ type Error = anyhow:: Error ;
643
+
644
+ fn try_from ( config : ConfigConditionItem ) -> Result < Self > {
645
+ let try_from_vec = |conds : Box < [ _ ] > | {
646
+ conds
647
+ . into_iter ( )
648
+ . map ( ConditionItem :: try_from)
649
+ . collect :: < Result < _ > > ( )
650
+ } ;
651
+ Ok ( match config {
652
+ ConfigConditionItem :: All ( conds) => ConditionItem :: All ( try_from_vec ( conds) ?) ,
653
+ ConfigConditionItem :: Any ( conds) => ConditionItem :: Any ( try_from_vec ( conds) ?) ,
654
+ ConfigConditionItem :: Not ( cond) => ConditionItem :: Not ( Box :: new ( ( * cond) . try_into ( ) ?) ) ,
655
+ ConfigConditionItem :: Builtin ( cond) => {
656
+ ConditionItem :: Builtin ( RcStr :: from ( cond. as_str ( ) ) )
657
+ }
658
+ ConfigConditionItem :: Base { path, content } => ConditionItem :: Base {
659
+ path : path. map ( ConditionPath :: try_from) . transpose ( ) ?,
660
+ content : content
661
+ . map ( EsRegex :: try_from)
662
+ . transpose ( ) ?
663
+ . map ( EsRegex :: resolved_cell) ,
664
+ } ,
612
665
} )
613
666
}
614
-
615
- type Error = anyhow:: Error ;
616
667
}
617
668
618
669
#[ derive(
@@ -623,6 +674,8 @@ pub struct RuleConfigItemOptions {
623
674
pub loaders : Vec < LoaderItem > ,
624
675
#[ serde( default , alias = "as" ) ]
625
676
pub rename_as : Option < RcStr > ,
677
+ #[ serde( default ) ]
678
+ pub condition : Option < ConfigConditionItem > ,
626
679
}
627
680
628
681
#[ derive(
@@ -640,8 +693,8 @@ pub enum RuleConfigItemOrShortcut {
640
693
#[ serde( rename_all = "camelCase" , untagged) ]
641
694
pub enum RuleConfigItem {
642
695
Options ( RuleConfigItemOptions ) ,
643
- Conditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
644
- Boolean ( bool ) ,
696
+ LegacyConditional ( FxIndexMap < RcStr , RuleConfigItem > ) ,
697
+ LegacyBoolean ( bool ) ,
645
698
}
646
699
647
700
#[ derive(
@@ -1320,13 +1373,16 @@ impl NextConfig {
1320
1373
NotFound ,
1321
1374
Break ,
1322
1375
}
1376
+ // This logic is needed for the `LegacyConditional`/`LegacyBoolean` configuration
1377
+ // syntax. This is technically public syntax, but was never documented and it is
1378
+ // unlikely that anyone is depending on it (outside of some Next.js internals).
1323
1379
fn find_rule < ' a > (
1324
1380
rule : & ' a RuleConfigItem ,
1325
1381
active_conditions : & BTreeSet < WebpackLoaderBuiltinCondition > ,
1326
1382
) -> FindRuleResult < ' a > {
1327
1383
match rule {
1328
1384
RuleConfigItem :: Options ( rule) => FindRuleResult :: Found ( rule) ,
1329
- RuleConfigItem :: Conditional ( map) => {
1385
+ RuleConfigItem :: LegacyConditional ( map) => {
1330
1386
for ( condition, rule) in map. iter ( ) {
1331
1387
let condition = WebpackLoaderBuiltinCondition :: from_str ( condition) ;
1332
1388
if let Ok ( condition) = condition
@@ -1346,7 +1402,7 @@ impl NextConfig {
1346
1402
}
1347
1403
FindRuleResult :: NotFound
1348
1404
}
1349
- RuleConfigItem :: Boolean ( _) => FindRuleResult :: Break ,
1405
+ RuleConfigItem :: LegacyBoolean ( _) => FindRuleResult :: Break ,
1350
1406
}
1351
1407
}
1352
1408
match rule {
@@ -1356,12 +1412,16 @@ impl NextConfig {
1356
1412
LoaderRuleItem {
1357
1413
loaders : transform_loaders ( loaders) ,
1358
1414
rename_as : None ,
1415
+ condition : None ,
1359
1416
} ,
1360
1417
) ;
1361
1418
}
1362
1419
RuleConfigItemOrShortcut :: Advanced ( rule) => {
1363
- if let FindRuleResult :: Found ( RuleConfigItemOptions { loaders, rename_as } ) =
1364
- find_rule ( rule, & active_conditions)
1420
+ if let FindRuleResult :: Found ( RuleConfigItemOptions {
1421
+ loaders,
1422
+ rename_as,
1423
+ condition,
1424
+ } ) = find_rule ( rule, & active_conditions)
1365
1425
{
1366
1426
// If the extension contains a wildcard, and the rename_as does not,
1367
1427
// emit an issue to prevent users from encountering duplicate module names.
@@ -1383,6 +1443,12 @@ impl NextConfig {
1383
1443
LoaderRuleItem {
1384
1444
loaders : transform_loaders ( loaders) ,
1385
1445
rename_as : rename_as. clone ( ) ,
1446
+ // TODO(bgw): Emit `InvalidLoaderRuleError` if this fails instead of
1447
+ // using try (?)
1448
+ condition : condition
1449
+ . clone ( )
1450
+ . map ( ConditionItem :: try_from)
1451
+ . transpose ( ) ?,
1386
1452
} ,
1387
1453
) ;
1388
1454
}
@@ -1820,3 +1886,66 @@ impl JsConfig {
1820
1886
Vc :: cell ( self . compiler_options . clone ( ) . unwrap_or_default ( ) )
1821
1887
}
1822
1888
}
1889
+
1890
+ #[ cfg( test) ]
1891
+ mod tests {
1892
+ use super :: * ;
1893
+
1894
+ #[ test]
1895
+ fn test_serde_rule_config_item_options ( ) {
1896
+ let json_value = serde_json:: json!( {
1897
+ "loaders" : [ ] ,
1898
+ "as" : "*.js" ,
1899
+ "condition" : {
1900
+ "all" : [
1901
+ "production" ,
1902
+ { "not" : "foreign" } ,
1903
+ { "any" : [
1904
+ "browser" ,
1905
+ {
1906
+ "path" : { "type" : "glob" , "value" : "*.svg" } ,
1907
+ "content" : {
1908
+ "source" : "@someTag" ,
1909
+ "flags" : ""
1910
+ }
1911
+ }
1912
+ ] } ,
1913
+ ] ,
1914
+ }
1915
+ } ) ;
1916
+
1917
+ let rule_config: RuleConfigItemOptions = serde_json:: from_value ( json_value) . unwrap ( ) ;
1918
+
1919
+ assert_eq ! (
1920
+ rule_config,
1921
+ RuleConfigItemOptions {
1922
+ loaders: vec![ ] ,
1923
+ rename_as: Some ( rcstr!( "*.js" ) ) ,
1924
+ condition: Some ( ConfigConditionItem :: All (
1925
+ [
1926
+ ConfigConditionItem :: Builtin ( WebpackLoaderBuiltinCondition :: Production ) ,
1927
+ ConfigConditionItem :: Not ( Box :: new( ConfigConditionItem :: Builtin (
1928
+ WebpackLoaderBuiltinCondition :: Foreign
1929
+ ) ) ) ,
1930
+ ConfigConditionItem :: Any (
1931
+ vec![
1932
+ ConfigConditionItem :: Builtin (
1933
+ WebpackLoaderBuiltinCondition :: Browser
1934
+ ) ,
1935
+ ConfigConditionItem :: Base {
1936
+ path: Some ( ConfigConditionPath :: Glob ( rcstr!( "*.svg" ) ) ) ,
1937
+ content: Some ( RegexComponents {
1938
+ source: rcstr!( "@someTag" ) ,
1939
+ flags: rcstr!( "" ) ,
1940
+ } ) ,
1941
+ } ,
1942
+ ]
1943
+ . into( ) ,
1944
+ ) ,
1945
+ ]
1946
+ . into( ) ,
1947
+ ) ) ,
1948
+ }
1949
+ ) ;
1950
+ }
1951
+ }
0 commit comments