Skip to content

Commit 81b915f

Browse files
committed
Turbopack: Remove fragile heuristics for detecting manually-configured sass or babel loaders, configure rules as a vec and not a map
1 parent fa7531f commit 81b915f

File tree

7 files changed

+188
-197
lines changed

7 files changed

+188
-197
lines changed

crates/next-core/src/next_config.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use turbo_tasks_env::{EnvMap, ProcessEnv};
1414
use turbo_tasks_fetch::FetchClient;
1515
use turbo_tasks_fs::FileSystemPath;
1616
use turbopack::module_options::{
17-
ConditionItem, ConditionPath, LoaderRuleItem, OptionWebpackRules,
17+
ConditionItem, ConditionPath, LoaderRuleItem, WebpackRules,
1818
module_options_context::{MdxTransformOptions, OptionWebpackConditions},
1919
};
2020
use turbopack_core::{
@@ -868,6 +868,13 @@ pub struct ExperimentalConfig {
868868
turbopack_tree_shaking: Option<bool>,
869869
turbopack_scope_hoisting: Option<bool>,
870870
turbopack_use_system_tls_certs: Option<bool>,
871+
/// Disable automatic configuration of the sass loader.
872+
#[serde(default)]
873+
turbopack_force_disable_sass: bool,
874+
/// Disable automatic configuration of the babel loader when a babel configuration file is
875+
/// present.
876+
#[serde(default)]
877+
turbopack_force_disable_babel: bool,
871878
// Whether to enable the global-not-found convention
872879
global_not_found: Option<bool>,
873880
/// Defaults to false in development mode, true in production mode.
@@ -1380,14 +1387,14 @@ impl NextConfig {
13801387
&self,
13811388
active_conditions: BTreeSet<WebpackLoaderBuiltinCondition>,
13821389
project_path: FileSystemPath,
1383-
) -> Result<Vc<OptionWebpackRules>> {
1390+
) -> Result<Vc<WebpackRules>> {
13841391
let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1385-
return Ok(Vc::cell(None));
1392+
return Ok(Vc::cell(Vec::new()));
13861393
};
13871394
if turbo_rules.is_empty() {
1388-
return Ok(Vc::cell(None));
1395+
return Ok(Vc::cell(Vec::new()));
13891396
}
1390-
let mut rules = FxIndexMap::default();
1397+
let mut rules = Vec::new();
13911398
for (glob, rule) in turbo_rules.iter() {
13921399
fn transform_loaders(loaders: &[LoaderItem]) -> ResolvedVc<WebpackLoaderItems> {
13931400
ResolvedVc::cell(
@@ -1443,14 +1450,14 @@ impl NextConfig {
14431450
let config_file_path = || project_path.join(&self.config_file_name);
14441451
match rule {
14451452
RuleConfigItemOrShortcut::Loaders(loaders) => {
1446-
rules.insert(
1453+
rules.push((
14471454
glob.clone(),
14481455
LoaderRuleItem {
14491456
loaders: transform_loaders(loaders),
14501457
rename_as: None,
14511458
condition: None,
14521459
},
1453-
);
1460+
));
14541461
}
14551462
RuleConfigItemOrShortcut::Advanced(rule) => {
14561463
if let FindRuleResult::Found(RuleConfigItemOptions {
@@ -1492,19 +1499,19 @@ impl NextConfig {
14921499
None
14931500
};
14941501

1495-
rules.insert(
1502+
rules.push((
14961503
glob.clone(),
14971504
LoaderRuleItem {
14981505
loaders: transform_loaders(loaders),
14991506
rename_as: rename_as.clone(),
15001507
condition,
15011508
},
1502-
);
1509+
));
15031510
}
15041511
}
15051512
}
15061513
}
1507-
Ok(Vc::cell(Some(ResolvedVc::cell(rules))))
1514+
Ok(Vc::cell(rules))
15081515
}
15091516

15101517
#[turbo_tasks::function]
@@ -1641,6 +1648,16 @@ impl NextConfig {
16411648
})
16421649
}
16431650

1651+
#[turbo_tasks::function]
1652+
pub fn experimental_turbopack_force_disable_babel(&self) -> Vc<bool> {
1653+
Vc::cell(self.experimental.turbopack_force_disable_babel)
1654+
}
1655+
1656+
#[turbo_tasks::function]
1657+
pub fn experimental_turbopack_force_disable_sass(&self) -> Vc<bool> {
1658+
Vc::cell(self.experimental.turbopack_force_disable_sass)
1659+
}
1660+
16441661
#[turbo_tasks::function]
16451662
pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
16461663
let options = &self.experimental.react_compiler;

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

Lines changed: 48 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ use anyhow::Result;
22
use turbo_rcstr::rcstr;
33
use turbo_tasks::{ResolvedVc, Vc};
44
use turbo_tasks_fs::{self, FileSystemEntryType, FileSystemPath};
5-
use turbopack::module_options::{LoaderRuleItem, OptionWebpackRules, WebpackRules};
5+
use turbopack::module_options::{LoaderRuleItem, WebpackRules};
66
use turbopack_core::{
77
issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
88
reference_type::{CommonJsReferenceSubType, ReferenceType},
99
resolve::{node::node_cjs_resolve_options, parse::Request, pattern::Pattern, resolve},
1010
};
1111
use turbopack_node::transforms::webpack::WebpackLoaderItem;
1212

13+
// https://babeljs.io/docs/config-files
14+
// TODO: Also support a `babel` key in a package.json file
1315
const BABEL_CONFIG_FILES: &[&str] = &[
1416
".babelrc",
1517
".babelrc.json",
@@ -22,94 +24,59 @@ const BABEL_CONFIG_FILES: &[&str] = &[
2224
"babel.config.cjs",
2325
];
2426

25-
/// If the user has a babel configuration file (see list above) alongside their
26-
/// `next.config.js` configuration, automatically add `babel-loader` as a
27-
/// webpack loader for each eligible file type if it doesn't already exist.
27+
/// If the user has a babel configuration file (see list above) alongside their `next.config.js`
28+
/// configuration, automatically add `babel-loader` as a webpack loader for each eligible file type
29+
/// if it doesn't already exist.
2830
#[turbo_tasks::function]
2931
pub async fn maybe_add_babel_loader(
3032
project_root: FileSystemPath,
31-
webpack_rules: Option<ResolvedVc<WebpackRules>>,
32-
) -> Result<Vc<OptionWebpackRules>> {
33-
let has_babel_config = {
34-
let mut has_babel_config = false;
35-
for &filename in BABEL_CONFIG_FILES {
36-
let filetype = *project_root.join(filename)?.get_type().await?;
37-
if matches!(filetype, FileSystemEntryType::File) {
38-
has_babel_config = true;
39-
break;
40-
}
33+
webpack_rules: Vc<WebpackRules>,
34+
) -> Result<Vc<WebpackRules>> {
35+
let mut has_babel_config = false;
36+
for &filename in BABEL_CONFIG_FILES {
37+
let filetype = *project_root.join(filename)?.get_type().await?;
38+
if matches!(filetype, FileSystemEntryType::File) {
39+
has_babel_config = true;
40+
break;
4141
}
42-
has_babel_config
43-
};
44-
45-
if has_babel_config {
46-
let mut rules = if let Some(webpack_rules) = webpack_rules {
47-
webpack_rules.owned().await?
48-
} else {
49-
Default::default()
50-
};
51-
let mut has_emitted_babel_resolve_issue = false;
52-
let mut has_changed = false;
53-
for pattern in ["*.js", "*.jsx", "*.ts", "*.tsx", "*.cjs", "*.mjs"] {
54-
let rule = rules.get_mut(pattern);
55-
let has_babel_loader = if let Some(rule) = rule.as_ref() {
56-
rule.loaders
57-
.await?
58-
.iter()
59-
.any(|c| c.loader == "babel-loader")
60-
} else {
61-
false
62-
};
63-
64-
if !has_babel_loader {
65-
if !has_emitted_babel_resolve_issue
66-
&& !*is_babel_loader_available(project_root.clone()).await?
67-
{
68-
BabelIssue {
69-
path: project_root.clone(),
70-
title: StyledString::Text(rcstr!(
71-
"Unable to resolve babel-loader, but a babel config is present"
72-
))
73-
.resolved_cell(),
74-
description: StyledString::Text(rcstr!(
75-
"Make sure babel-loader is installed via your package manager."
76-
))
77-
.resolved_cell(),
78-
severity: IssueSeverity::Fatal,
79-
}
80-
.resolved_cell()
81-
.emit();
82-
83-
has_emitted_babel_resolve_issue = true;
84-
}
42+
}
43+
if !has_babel_config {
44+
return Ok(webpack_rules);
45+
}
8546

86-
let loader = WebpackLoaderItem {
87-
loader: rcstr!("babel-loader"),
88-
options: Default::default(),
89-
};
90-
if let Some(rule) = rule {
91-
let mut loaders = rule.loaders.owned().await?;
92-
loaders.push(loader);
93-
rule.loaders = ResolvedVc::cell(loaders);
94-
} else {
95-
rules.insert(
96-
pattern.into(),
97-
LoaderRuleItem {
98-
loaders: ResolvedVc::cell(vec![loader]),
99-
rename_as: Some(rcstr!("*")),
100-
condition: None,
101-
},
102-
);
103-
}
104-
has_changed = true;
105-
}
106-
}
47+
let mut rules = webpack_rules.owned().await?;
48+
let pattern = rcstr!("*.{js,jsx,ts,tsx,cjs,mjs,mts,cts}");
10749

108-
if has_changed {
109-
return Ok(Vc::cell(Some(ResolvedVc::cell(rules))));
50+
if !*is_babel_loader_available(project_root.clone()).await? {
51+
BabelIssue {
52+
path: project_root.clone(),
53+
title: StyledString::Text(rcstr!(
54+
"Unable to resolve babel-loader, but a babel config is present"
55+
))
56+
.resolved_cell(),
57+
description: StyledString::Text(rcstr!(
58+
"Make sure babel-loader is installed via your package manager."
59+
))
60+
.resolved_cell(),
61+
severity: IssueSeverity::Fatal,
11062
}
63+
.resolved_cell()
64+
.emit();
11165
}
112-
Ok(Vc::cell(webpack_rules))
66+
67+
rules.push((
68+
pattern,
69+
LoaderRuleItem {
70+
loaders: ResolvedVc::cell(vec![WebpackLoaderItem {
71+
loader: rcstr!("babel-loader"),
72+
options: Default::default(),
73+
}]),
74+
rename_as: Some(rcstr!("*")),
75+
condition: None,
76+
},
77+
));
78+
79+
Ok(Vc::cell(rules))
11380
}
11481

11582
#[turbo_tasks::function]

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use turbopack::module_options::{
1010
};
1111
use turbopack_core::resolve::{ExternalTraced, ExternalType, options::ImportMapping};
1212

13-
use self::{babel::maybe_add_babel_loader, sass::maybe_add_sass_loader};
13+
use self::{babel::maybe_add_babel_loader, sass::add_sass_loader};
1414
use crate::next_config::NextConfig;
1515

1616
pub(crate) mod babel;
@@ -196,16 +196,26 @@ pub async fn webpack_loader_options(
196196
next_config: Vc<NextConfig>,
197197
builtin_conditions: BTreeSet<WebpackLoaderBuiltinCondition>,
198198
) -> Result<Option<ResolvedVc<WebpackLoadersOptions>>> {
199-
let mut rules = *next_config
200-
.webpack_rules(builtin_conditions.clone(), project_path.clone())
201-
.await?;
202-
rules = *maybe_add_sass_loader(next_config.sass_config(), rules.map(|v| *v)).await?;
203-
if !builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Foreign) {
204-
rules = *maybe_add_babel_loader(project_path.clone(), rules.map(|v| *v)).await?;
199+
let mut rules = next_config.webpack_rules(builtin_conditions.clone(), project_path.clone());
200+
201+
if !*next_config
202+
.experimental_turbopack_force_disable_sass()
203+
.await?
204+
{
205+
rules = add_sass_loader(next_config.sass_config(), rules);
206+
}
207+
208+
if !builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Foreign)
209+
&& !*next_config
210+
.experimental_turbopack_force_disable_babel()
211+
.await?
212+
{
213+
rules = maybe_add_babel_loader(project_path.clone(), rules);
205214
}
206215

207-
let conditions = next_config.webpack_conditions().to_resolved().await?;
208-
Ok(if let Some(rules) = rules {
216+
let rules = rules.to_resolved().await?;
217+
Ok(if !rules.await?.is_empty() {
218+
let conditions = next_config.webpack_conditions().to_resolved().await?;
209219
Some(
210220
WebpackLoadersOptions {
211221
rules,

0 commit comments

Comments
 (0)