Skip to content

Commit cf0a6f4

Browse files
committed
Turbopack: Add new webpack loader rule/condition syntax in config
1 parent 11c9953 commit cf0a6f4

File tree

11 files changed

+656
-110
lines changed

11 files changed

+656
-110
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/next-core/src/next_client/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,11 @@ pub async fn get_client_module_options_context(
248248
let mut foreign_conditions = loader_conditions.clone();
249249
foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign);
250250
let foreign_enable_webpack_loaders =
251-
webpack_loader_options(project_path.clone(), next_config, true, foreign_conditions).await?;
251+
webpack_loader_options(project_path.clone(), next_config, foreign_conditions).await?;
252252

253253
// Now creates a webpack rules that applies to all code.
254254
let enable_webpack_loaders =
255-
webpack_loader_options(project_path.clone(), next_config, false, loader_conditions).await?;
255+
webpack_loader_options(project_path.clone(), next_config, loader_conditions).await?;
256256

257257
let tree_shaking_mode_for_user_code = *next_config
258258
.tree_shaking_mode_for_user_code(next_mode.is_development())

crates/next-core/src/next_config.rs

Lines changed: 143 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -566,53 +566,90 @@ pub struct TurbopackConfig {
566566
pub module_ids: Option<ModuleIds>,
567567
}
568568

569-
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
569+
#[derive(
570+
Serialize, Deserialize, Clone, PartialEq, Eq, Debug, TraceRawVcs, NonLocalValue, OperationValue,
571+
)]
570572
pub struct RegexComponents {
571573
source: RcStr,
572574
flags: RcStr,
573575
}
574576

575-
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
577+
#[derive(
578+
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
579+
)]
576580
#[serde(tag = "type", content = "value", rename_all = "camelCase")]
577581
pub enum ConfigConditionPath {
578582
Glob(RcStr),
579583
Regex(RegexComponents),
580584
}
581585

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 {
585591
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+
}
587595
})
588596
}
597+
}
589598

599+
impl TryFrom<RegexComponents> for EsRegex {
590600
type Error = anyhow::Error;
591-
}
592601

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)
596604
}
597-
598-
type Error = anyhow::Error;
599605
}
600606

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+
},
605625
}
606626

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+
},
612651
})
613652
}
614-
615-
type Error = anyhow::Error;
616653
}
617654

618655
#[derive(
@@ -623,6 +660,7 @@ pub struct RuleConfigItemOptions {
623660
pub loaders: Vec<LoaderItem>,
624661
#[serde(default, alias = "as")]
625662
pub rename_as: Option<RcStr>,
663+
pub condition: Option<ConfigConditionItem>,
626664
}
627665

628666
#[derive(
@@ -640,8 +678,8 @@ pub enum RuleConfigItemOrShortcut {
640678
#[serde(rename_all = "camelCase", untagged)]
641679
pub enum RuleConfigItem {
642680
Options(RuleConfigItemOptions),
643-
Conditional(FxIndexMap<RcStr, RuleConfigItem>),
644-
Boolean(bool),
681+
LegacyConditional(FxIndexMap<RcStr, RuleConfigItem>),
682+
LegacyBoolean(bool),
645683
}
646684

647685
#[derive(
@@ -1320,13 +1358,16 @@ impl NextConfig {
13201358
NotFound,
13211359
Break,
13221360
}
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).
13231364
fn find_rule<'a>(
13241365
rule: &'a RuleConfigItem,
13251366
active_conditions: &BTreeSet<WebpackLoaderBuiltinCondition>,
13261367
) -> FindRuleResult<'a> {
13271368
match rule {
13281369
RuleConfigItem::Options(rule) => FindRuleResult::Found(rule),
1329-
RuleConfigItem::Conditional(map) => {
1370+
RuleConfigItem::LegacyConditional(map) => {
13301371
for (condition, rule) in map.iter() {
13311372
let condition = WebpackLoaderBuiltinCondition::from_str(condition);
13321373
if let Ok(condition) = condition
@@ -1346,7 +1387,7 @@ impl NextConfig {
13461387
}
13471388
FindRuleResult::NotFound
13481389
}
1349-
RuleConfigItem::Boolean(_) => FindRuleResult::Break,
1390+
RuleConfigItem::LegacyBoolean(_) => FindRuleResult::Break,
13501391
}
13511392
}
13521393
match rule {
@@ -1356,12 +1397,16 @@ impl NextConfig {
13561397
LoaderRuleItem {
13571398
loaders: transform_loaders(loaders),
13581399
rename_as: None,
1400+
condition: None,
13591401
},
13601402
);
13611403
}
13621404
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)
13651410
{
13661411
// If the extension contains a wildcard, and the rename_as does not,
13671412
// emit an issue to prevent users from encountering duplicate module names.
@@ -1383,6 +1428,12 @@ impl NextConfig {
13831428
LoaderRuleItem {
13841429
loaders: transform_loaders(loaders),
13851430
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()?,
13861437
},
13871438
);
13881439
}
@@ -1820,3 +1871,66 @@ impl JsConfig {
18201871
Vc::cell(self.compiler_options.clone().unwrap_or_default())
18211872
}
18221873
}
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+
}

crates/next-core/src/next_server/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,11 @@ pub async fn get_server_module_options_context(
493493
let mut foreign_conditions = loader_conditions.clone();
494494
foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign);
495495
let foreign_enable_webpack_loaders =
496-
webpack_loader_options(project_path.clone(), next_config, true, foreign_conditions).await?;
496+
webpack_loader_options(project_path.clone(), next_config, foreign_conditions).await?;
497497

498498
// Now creates a webpack rules that applies to all code.
499499
let enable_webpack_loaders =
500-
webpack_loader_options(project_path.clone(), next_config, false, loader_conditions).await?;
500+
webpack_loader_options(project_path.clone(), next_config, loader_conditions).await?;
501501

502502
let tree_shaking_mode_for_user_code = *next_config
503503
.tree_shaking_mode_for_user_code(next_mode.is_development())

crates/next-core/src/next_shared/webpack_rules/babel.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub async fn maybe_add_babel_loader(
9797
LoaderRuleItem {
9898
loaders: ResolvedVc::cell(vec![loader]),
9999
rename_as: Some(rcstr!("*")),
100+
condition: None,
100101
},
101102
);
102103
}

0 commit comments

Comments
 (0)