Skip to content

Commit 2f2f6c1

Browse files
committed
Turbopack: Fix babel-loader (automatic and manual configuration)
1 parent 3ef1768 commit 2f2f6c1

File tree

34 files changed

+489
-443
lines changed

34 files changed

+489
-443
lines changed
Lines changed: 42 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
use std::sync::LazyLock;
22

3-
use anyhow::Result;
3+
use anyhow::{Context, Result};
44
use regex::Regex;
55
use turbo_rcstr::{RcStr, rcstr};
6-
use turbo_tasks::{ResolvedVc, Vc};
7-
use turbo_tasks_fs::{self, FileSystemEntryType, FileSystemPath};
6+
use turbo_tasks::ResolvedVc;
7+
use turbo_tasks_fs::{self, FileSystemEntryType, FileSystemPath, to_sys_path};
88
use turbopack::module_options::{ConditionItem, LoaderRuleItem};
9-
use turbopack_core::{
10-
issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
11-
reference_type::{CommonJsReferenceSubType, ReferenceType},
12-
resolve::{node::node_cjs_resolve_options, parse::Request, pattern::Pattern, resolve},
13-
};
149
use turbopack_node::transforms::webpack::WebpackLoaderItem;
1510

1611
use crate::next_shared::webpack_rules::WebpackLoaderBuiltinCondition;
@@ -32,6 +27,10 @@ const BABEL_CONFIG_FILES: &[&str] = &[
3227
static BABEL_LOADER_RE: LazyLock<Regex> =
3328
LazyLock::new(|| Regex::new(r"(^|/)@?babel[-/]loader($|/|\.)").unwrap());
3429

30+
/// The forked version of babel-loader that we should use for automatic configuration. This version
31+
/// is always available, as it's installed as part of next.js.
32+
const NEXT_JS_BABEL_LOADER: &str = "next/dist/build/babel/loader";
33+
3534
pub async fn detect_likely_babel_loader(
3635
webpack_rules: &[(RcStr, LoaderRuleItem)],
3736
) -> Result<Option<RcStr>> {
@@ -54,41 +53,44 @@ pub async fn detect_likely_babel_loader(
5453
pub async fn get_babel_loader_rules(
5554
project_root: FileSystemPath,
5655
) -> Result<Vec<(RcStr, LoaderRuleItem)>> {
57-
let mut has_babel_config = false;
56+
let mut babel_config_path = None;
5857
for &filename in BABEL_CONFIG_FILES {
59-
let filetype = *project_root.join(filename)?.get_type().await?;
58+
let path = project_root.join(filename)?;
59+
let filetype = *path.get_type().await?;
6060
if matches!(filetype, FileSystemEntryType::File) {
61-
has_babel_config = true;
61+
babel_config_path = Some(path);
6262
break;
6363
}
6464
}
65-
if !has_babel_config {
65+
let Some(babel_config_path) = babel_config_path else {
6666
return Ok(Vec::new());
67-
}
67+
};
6868

69-
if !*is_babel_loader_available(project_root.clone()).await? {
70-
BabelIssue {
71-
path: project_root.clone(),
72-
title: StyledString::Text(rcstr!(
73-
"Unable to resolve babel-loader, but a babel config is present"
74-
))
75-
.resolved_cell(),
76-
description: StyledString::Text(rcstr!(
77-
"Make sure babel-loader is installed via your package manager."
78-
))
79-
.resolved_cell(),
80-
severity: IssueSeverity::Fatal,
81-
}
82-
.resolved_cell()
83-
.emit();
84-
}
69+
// - See `packages/next/src/build/babel/loader/types.d.ts` for all the configuration options.
70+
// - See `packages/next/src/build/get-babel-loader-config.ts` for how we use this in webpack.
71+
let mut loader_options = serde_json::Map::new();
72+
73+
// `transformMode: default` (what the webpack implementation does) would run all of the
74+
// Next.js-specific transforms as babel transforms. Because we always have to pay the cost
75+
// of parsing with SWC after the webpack loader runs, we want to keep running those
76+
// transforms using SWC, so use `standalone` instead.
77+
loader_options.insert("transformMode".to_owned(), "standalone".into());
78+
79+
loader_options.insert(
80+
"cwd".to_owned(),
81+
to_sys_path_str(project_root).await?.into(),
82+
);
83+
loader_options.insert(
84+
"configFile".to_owned(),
85+
to_sys_path_str(babel_config_path).await?.into(),
86+
);
8587

8688
Ok(vec![(
8789
rcstr!("*.{js,jsx,ts,tsx,cjs,mjs,mts,cts}"),
8890
LoaderRuleItem {
8991
loaders: ResolvedVc::cell(vec![WebpackLoaderItem {
90-
loader: rcstr!("babel-loader"),
91-
options: Default::default(),
92+
loader: rcstr!(NEXT_JS_BABEL_LOADER),
93+
options: loader_options,
9294
}]),
9395
rename_as: Some(rcstr!("*")),
9496
condition: Some(ConditionItem::Not(Box::new(ConditionItem::Builtin(
@@ -98,49 +100,13 @@ pub async fn get_babel_loader_rules(
98100
)])
99101
}
100102

101-
#[turbo_tasks::function]
102-
pub async fn is_babel_loader_available(project_path: FileSystemPath) -> Result<Vc<bool>> {
103-
let result = resolve(
104-
project_path.clone(),
105-
ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
106-
Request::parse(Pattern::Constant(rcstr!("babel-loader/package.json"))),
107-
node_cjs_resolve_options(project_path),
108-
);
109-
let assets = result.primary_sources().await?;
110-
Ok(Vc::cell(!assets.is_empty()))
111-
}
112-
113-
#[turbo_tasks::value]
114-
struct BabelIssue {
115-
path: FileSystemPath,
116-
title: ResolvedVc<StyledString>,
117-
description: ResolvedVc<StyledString>,
118-
severity: IssueSeverity,
119-
}
120-
121-
#[turbo_tasks::value_impl]
122-
impl Issue for BabelIssue {
123-
#[turbo_tasks::function]
124-
fn stage(&self) -> Vc<IssueStage> {
125-
IssueStage::Transform.into()
126-
}
127-
128-
fn severity(&self) -> IssueSeverity {
129-
self.severity
130-
}
131-
132-
#[turbo_tasks::function]
133-
fn file_path(&self) -> Vc<FileSystemPath> {
134-
self.path.clone().cell()
135-
}
136-
137-
#[turbo_tasks::function]
138-
fn title(&self) -> Vc<StyledString> {
139-
*self.title
140-
}
141-
142-
#[turbo_tasks::function]
143-
fn description(&self) -> Vc<OptionStyledString> {
144-
Vc::cell(Some(self.description))
145-
}
103+
/// A system path that can be passed to the webpack loader
104+
async fn to_sys_path_str(path: FileSystemPath) -> Result<String> {
105+
let sys_path = to_sys_path(path)
106+
.await?
107+
.context("path should use a DiskFileSystem")?;
108+
Ok(sys_path
109+
.to_str()
110+
.with_context(|| "{sys_path:?} is not valid utf-8")?
111+
.to_owned())
146112
}

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
"@babel/parser": "7.27.0",
101101
"@babel/plugin-syntax-explicit-resource-management": "7.25.7",
102102
"@babel/plugin-transform-object-rest-spread": "7.25.9",
103-
"@babel/preset-flow": "7.25.9",
104103
"@babel/preset-react": "7.26.3",
105104
"@changesets/changelog-github": "0.5.1",
106105
"@changesets/cli": "2.29.3",

packages/next/errors.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,5 +808,6 @@
808808
"807": "Expected a %s response header.",
809809
"808": "Invalid binary HMR message: insufficient data (expected %s bytes, got %s)",
810810
"809": "Invalid binary HMR message of type %s",
811-
"810": "React debug channel stream error"
811+
"810": "React debug channel stream error",
812+
"811": "unsupported transformMode in loader options: %s"
812813
}

0 commit comments

Comments
 (0)