Skip to content

Commit 3fc1a68

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 1b0f6ce commit 3fc1a68

File tree

10 files changed

+407
-221
lines changed

10 files changed

+407
-221
lines changed

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, 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, 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: 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_disable_builtin_sass: Option<bool>,
874+
/// Disable automatic configuration of the babel loader when a babel configuration file is
875+
/// present.
876+
#[serde(default)]
877+
turbopack_disable_builtin_babel: Option<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.
@@ -1377,14 +1384,14 @@ impl NextConfig {
13771384
&self,
13781385
active_conditions: BTreeSet<WebpackLoaderBuiltinCondition>,
13791386
project_path: FileSystemPath,
1380-
) -> Result<Vc<OptionWebpackRules>> {
1387+
) -> Result<Vc<WebpackRules>> {
13811388
let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
1382-
return Ok(Vc::cell(None));
1389+
return Ok(Vc::cell(Vec::new()));
13831390
};
13841391
if turbo_rules.is_empty() {
1385-
return Ok(Vc::cell(None));
1392+
return Ok(Vc::cell(Vec::new()));
13861393
}
1387-
let mut rules = FxIndexMap::default();
1394+
let mut rules = Vec::new();
13881395
for (glob, rule) in turbo_rules.iter() {
13891396
fn transform_loaders(loaders: &[LoaderItem]) -> ResolvedVc<WebpackLoaderItems> {
13901397
ResolvedVc::cell(
@@ -1440,14 +1447,14 @@ impl NextConfig {
14401447
let config_file_path = || project_path.join(&self.config_file_name);
14411448
match rule {
14421449
RuleConfigItemOrShortcut::Loaders(loaders) => {
1443-
rules.insert(
1450+
rules.push((
14441451
glob.clone(),
14451452
LoaderRuleItem {
14461453
loaders: transform_loaders(loaders),
14471454
rename_as: None,
14481455
condition: None,
14491456
},
1450-
);
1457+
));
14511458
}
14521459
RuleConfigItemOrShortcut::Advanced(rule) => {
14531460
if let FindRuleResult::Found(RuleConfigItemOptions {
@@ -1489,19 +1496,19 @@ impl NextConfig {
14891496
None
14901497
};
14911498

1492-
rules.insert(
1499+
rules.push((
14931500
glob.clone(),
14941501
LoaderRuleItem {
14951502
loaders: transform_loaders(loaders),
14961503
rename_as: rename_as.clone(),
14971504
condition,
14981505
},
1499-
);
1506+
));
15001507
}
15011508
}
15021509
}
15031510
}
1504-
Ok(Vc::cell(Some(ResolvedVc::cell(rules))))
1511+
Ok(Vc::cell(rules))
15051512
}
15061513

15071514
#[turbo_tasks::function]
@@ -1638,6 +1645,16 @@ impl NextConfig {
16381645
})
16391646
}
16401647

1648+
#[turbo_tasks::function]
1649+
pub fn experimental_turbopack_disable_builtin_babel(&self) -> Vc<Option<bool>> {
1650+
Vc::cell(self.experimental.turbopack_disable_builtin_babel)
1651+
}
1652+
1653+
#[turbo_tasks::function]
1654+
pub fn experimental_turbopack_disable_builtin_sass(&self) -> Vc<Option<bool>> {
1655+
Vc::cell(self.experimental.turbopack_disable_builtin_sass)
1656+
}
1657+
16411658
#[turbo_tasks::function]
16421659
pub fn react_compiler(&self) -> Vc<OptionalReactCompilerOptions> {
16431660
let options = &self.experimental.react_compiler;

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, 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, 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: 66 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
use std::sync::LazyLock;
2+
13
use anyhow::Result;
2-
use turbo_rcstr::rcstr;
4+
use regex::Regex;
5+
use turbo_rcstr::{RcStr, rcstr};
36
use turbo_tasks::{ResolvedVc, Vc};
47
use turbo_tasks_fs::{self, FileSystemEntryType, FileSystemPath};
5-
use turbopack::module_options::{LoaderRuleItem, OptionWebpackRules, WebpackRules};
8+
use turbopack::module_options::LoaderRuleItem;
69
use turbopack_core::{
710
issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
811
reference_type::{CommonJsReferenceSubType, ReferenceType},
912
resolve::{node::node_cjs_resolve_options, parse::Request, pattern::Pattern, resolve},
1013
};
1114
use turbopack_node::transforms::webpack::WebpackLoaderItem;
1215

16+
// https://babeljs.io/docs/config-files
17+
// TODO: Also support a `babel` key in a package.json file
1318
const BABEL_CONFIG_FILES: &[&str] = &[
1419
".babelrc",
1520
".babelrc.json",
@@ -22,94 +27,71 @@ const BABEL_CONFIG_FILES: &[&str] = &[
2227
"babel.config.cjs",
2328
];
2429

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.
28-
#[turbo_tasks::function]
29-
pub async fn maybe_add_babel_loader(
30-
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-
}
41-
}
42-
has_babel_config
43-
};
30+
static BABEL_LOADER_RE: LazyLock<Regex> =
31+
LazyLock::new(|| Regex::new(r"(^|/)@?babel[-/]loader($|/|\.)").unwrap());
4432

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-
}
33+
pub async fn detect_likely_babel_loader(
34+
webpack_rules: &[(RcStr, LoaderRuleItem)],
35+
) -> Result<Option<RcStr>> {
36+
for (glob, rule) in webpack_rules {
37+
if rule
38+
.loaders
39+
.await?
40+
.iter()
41+
.any(|item| BABEL_LOADER_RE.is_match(&item.loader))
42+
{
43+
return Ok(Some(glob.clone()));
44+
}
45+
}
46+
Ok(None)
47+
}
8548

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-
}
49+
/// If the user has a babel configuration file (see list above) alongside their `next.config.js`
50+
/// configuration, automatically add `babel-loader` as a webpack loader for each eligible file type
51+
/// if it doesn't already exist.
52+
pub async fn get_babel_loader_rules(
53+
project_root: FileSystemPath,
54+
) -> Result<Vec<(RcStr, LoaderRuleItem)>> {
55+
let mut has_babel_config = false;
56+
for &filename in BABEL_CONFIG_FILES {
57+
let filetype = *project_root.join(filename)?.get_type().await?;
58+
if matches!(filetype, FileSystemEntryType::File) {
59+
has_babel_config = true;
60+
break;
10661
}
62+
}
63+
if !has_babel_config {
64+
return Ok(Vec::new());
65+
}
10766

108-
if has_changed {
109-
return Ok(Vc::cell(Some(ResolvedVc::cell(rules))));
67+
if !*is_babel_loader_available(project_root.clone()).await? {
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,
11079
}
80+
.resolved_cell()
81+
.emit();
11182
}
112-
Ok(Vc::cell(webpack_rules))
83+
84+
Ok(vec![(
85+
rcstr!("*.{js,jsx,ts,tsx,cjs,mjs,mts,cts}"),
86+
LoaderRuleItem {
87+
loaders: ResolvedVc::cell(vec![WebpackLoaderItem {
88+
loader: rcstr!("babel-loader"),
89+
options: Default::default(),
90+
}]),
91+
rename_as: Some(rcstr!("*")),
92+
condition: None,
93+
},
94+
)])
11395
}
11496

11597
#[turbo_tasks::function]

0 commit comments

Comments
 (0)