Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 44 additions & 100 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{collections::BTreeSet, str::FromStr};

use anyhow::{Context, Result, bail};
use either::Either;
use rustc_hash::FxHashSet;
Expand Down Expand Up @@ -667,21 +665,11 @@ impl TryFrom<ConfigConditionItem> for ConditionItem {
}
}

#[derive(
Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RuleConfigItem {
Options(RuleConfigItemOptions),
LegacyConditional(FxIndexMap<RcStr, RuleConfigItem>),
LegacyBoolean(bool),
}

#[derive(
Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, NonLocalValue, OperationValue,
)]
#[serde(rename_all = "camelCase")]
pub struct RuleConfigItemOptions {
pub struct RuleConfigItem {
pub loaders: Vec<LoaderItem>,
#[serde(default, alias = "as")]
pub rename_as: Option<RcStr>,
Expand Down Expand Up @@ -1401,11 +1389,7 @@ impl NextConfig {
}

#[turbo_tasks::function]
pub async fn webpack_rules(
&self,
active_conditions: BTreeSet<WebpackLoaderBuiltinCondition>,
project_path: FileSystemPath,
) -> Result<Vc<WebpackRules>> {
pub async fn webpack_rules(&self, project_path: FileSystemPath) -> Result<Vc<WebpackRules>> {
let Some(turbo_rules) = self.turbopack.as_ref().and_then(|t| t.rules.as_ref()) else {
return Ok(Vc::cell(Vec::new()));
};
Expand All @@ -1429,43 +1413,6 @@ impl NextConfig {
.collect(),
)
}
enum FindRuleResult<'a> {
Found(&'a RuleConfigItemOptions),
NotFound,
Break,
}
// This logic is needed for the `LegacyConditional`/`LegacyBoolean` configuration
// syntax. This is technically public syntax, but was never documented and it is
// unlikely that anyone is depending on it (outside of some Next.js internals).
fn find_rule<'a>(
rule: &'a RuleConfigItem,
active_conditions: &BTreeSet<WebpackLoaderBuiltinCondition>,
) -> FindRuleResult<'a> {
match rule {
RuleConfigItem::Options(rule) => FindRuleResult::Found(rule),
RuleConfigItem::LegacyConditional(map) => {
for (condition, rule) in map.iter() {
let condition = WebpackLoaderBuiltinCondition::from_str(condition);
if let Ok(condition) = condition
&& (condition == WebpackLoaderBuiltinCondition::Default
|| active_conditions.contains(&condition))
{
match find_rule(rule, active_conditions) {
FindRuleResult::Found(rule) => {
return FindRuleResult::Found(rule);
}
FindRuleResult::Break => {
return FindRuleResult::Break;
}
FindRuleResult::NotFound => {}
}
}
}
FindRuleResult::NotFound
}
RuleConfigItem::LegacyBoolean(_) => FindRuleResult::Break,
}
}
let config_file_path = || project_path.join(&self.config_file_name);
for item in &rule_collection.0 {
match item {
Expand All @@ -1479,55 +1426,52 @@ impl NextConfig {
},
));
}
RuleConfigCollectionItem::Full(rule_config_item) => {
if let FindRuleResult::Found(RuleConfigItemOptions {
loaders,
rename_as,
condition,
}) = find_rule(rule_config_item, &active_conditions)
RuleConfigCollectionItem::Full(RuleConfigItem {
loaders,
rename_as,
condition,
}) => {
// If the extension contains a wildcard, and the rename_as does not,
// emit an issue to prevent users from encountering duplicate module
// names.
if glob.contains("*")
&& let Some(rename_as) = rename_as.as_ref()
&& !rename_as.contains("*")
{
// If the extension contains a wildcard, and the rename_as does not,
// emit an issue to prevent users from encountering duplicate module
// names.
if glob.contains("*")
&& let Some(rename_as) = rename_as.as_ref()
&& !rename_as.contains("*")
{
InvalidLoaderRuleRenameAsIssue {
glob: glob.clone(),
InvalidLoaderRuleRenameAsIssue {
glob: glob.clone(),
config_file_path: config_file_path()?,
rename_as: rename_as.clone(),
}
.resolved_cell()
.emit();
}

// convert from Next.js-specific condition type to internal Turbopack
// condition type
let condition = if let Some(condition) = condition {
if let Ok(cond) = ConditionItem::try_from(condition.clone()) {
Some(cond)
} else {
InvalidLoaderRuleConditionIssue {
condition: condition.clone(),
config_file_path: config_file_path()?,
rename_as: rename_as.clone(),
}
.resolved_cell()
.emit();
}

// convert from Next.js-specific condition type to internal Turbopack
// condition type
let condition = if let Some(condition) = condition {
if let Ok(cond) = ConditionItem::try_from(condition.clone()) {
Some(cond)
} else {
InvalidLoaderRuleConditionIssue {
condition: condition.clone(),
config_file_path: config_file_path()?,
}
.resolved_cell()
.emit();
None
}
} else {
None
};
rules.push((
glob.clone(),
LoaderRuleItem {
loaders: transform_loaders(&mut loaders.iter()),
rename_as: rename_as.clone(),
condition,
},
));
}
}
} else {
None
};
rules.push((
glob.clone(),
LoaderRuleItem {
loaders: transform_loaders(&mut loaders.iter()),
rename_as: rename_as.clone(),
condition,
},
));
}
}
}
Expand Down Expand Up @@ -2001,11 +1945,11 @@ mod tests {
}
});

let rule_config: RuleConfigItemOptions = serde_json::from_value(json_value).unwrap();
let rule_config: RuleConfigItem = serde_json::from_value(json_value).unwrap();

assert_eq!(
rule_config,
RuleConfigItemOptions {
RuleConfigItem {
loaders: vec![],
rename_as: Some(rcstr!("*.js")),
condition: Some(ConfigConditionItem::All(
Expand Down
8 changes: 6 additions & 2 deletions crates/next-core/src/next_shared/webpack_rules/babel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ use regex::Regex;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks_fs::{self, FileSystemEntryType, FileSystemPath};
use turbopack::module_options::LoaderRuleItem;
use turbopack::module_options::{ConditionItem, LoaderRuleItem};
use turbopack_core::{
issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
reference_type::{CommonJsReferenceSubType, ReferenceType},
resolve::{node::node_cjs_resolve_options, parse::Request, pattern::Pattern, resolve},
};
use turbopack_node::transforms::webpack::WebpackLoaderItem;

use crate::next_shared::webpack_rules::WebpackLoaderBuiltinCondition;

// https://babeljs.io/docs/config-files
// TODO: Also support a `babel` key in a package.json file
const BABEL_CONFIG_FILES: &[&str] = &[
Expand Down Expand Up @@ -89,7 +91,9 @@ pub async fn get_babel_loader_rules(
options: Default::default(),
}]),
rename_as: Some(rcstr!("*")),
condition: None,
condition: Some(ConditionItem::Not(Box::new(ConditionItem::Builtin(
RcStr::from(WebpackLoaderBuiltinCondition::Foreign.as_str()),
)))),
},
)])
}
Expand Down
2 changes: 1 addition & 1 deletion crates/next-core/src/next_shared/webpack_rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ pub async fn webpack_loader_options(
builtin_conditions: BTreeSet<WebpackLoaderBuiltinCondition>,
) -> Result<Vc<OptionWebpackLoadersOptions>> {
let mut rules = next_config
.webpack_rules(builtin_conditions.clone(), project_path.clone())
.webpack_rules(project_path.clone())
.owned()
.await?;

Expand Down
72 changes: 31 additions & 41 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import type {
TurbopackRuleCondition,
TurbopackRuleConfigCollection,
TurbopackRuleConfigItem,
TurbopackRuleConfigItemOptions,
} from '../../server/config-shared'
import { isDeepStrictEqual } from 'util'
import { type DefineEnvOptions, getDefineEnv } from '../define-env'
Expand Down Expand Up @@ -836,29 +835,32 @@ function bindingToApi(

for (const key of ruleKeys) {
nextConfig.turbopack.conditions[`#reactCompiler/${key}`] = {
path: key,
content:
options.compilationMode === 'annotation'
? /['"]use memo['"]/
: !options.compilationMode ||
options.compilationMode === 'infer'
? // Matches declaration or useXXX or </ (closing jsx) or /> (self closing jsx)
/['"]use memo['"]|\Wuse[A-Z]|<\/|\/>/
: undefined,
all: [
'browser',
{ not: 'foreign' },
{
path: key,
content:
options.compilationMode === 'annotation'
? /['"]use memo['"]/
: !options.compilationMode ||
options.compilationMode === 'infer'
? // Matches declaration or useXXX or </ (closing jsx) or /> (self closing jsx)
/['"]use memo['"]|\Wuse[A-Z]|<\/|\/>/
: undefined,
},
],
}
nextConfig.turbopack.rules[`#reactCompiler/${key}`] = {
browser: {
foreign: false,
loaders: [
getReactCompilerLoader(
reactCompilerOptions,
projectPath,
nextConfig.dev,
/* isServer */ false,
/* reactCompilerExclude */ undefined
),
],
},
loaders: [
getReactCompilerLoader(
reactCompilerOptions,
projectPath,
nextConfig.dev,
/* isServer */ false,
/* reactCompilerExclude */ undefined
),
],
}
}
}
Expand Down Expand Up @@ -1039,26 +1041,14 @@ function bindingToApi(
glob: string
): any {
if (!rule) return rule
for (const item of rule.loaders) {
checkLoaderItem(item, glob)
}
let serializedRule: any = rule
if ('loaders' in rule) {
const narrowedRule = rule as TurbopackRuleConfigItemOptions
for (const item of narrowedRule.loaders) {
checkLoaderItem(item, glob)
}
if (narrowedRule.condition != null) {
serializedRule = {
...rule,
condition: serializeRuleCondition(narrowedRule.condition),
}
}
} else {
serializedRule = {}
for (const [key, value] of Object.entries(rule)) {
if (typeof value === 'object' && value) {
serializedRule[key] = serializeConfigItem(value, glob)
} else {
serializedRule[key] = value
}
if (rule.condition != null) {
serializedRule = {
...rule,
condition: serializeRuleCondition(rule.condition),
}
}
return serializedRule
Expand Down
14 changes: 2 additions & 12 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type {
DeprecatedExperimentalTurboOptions,
TurbopackOptions,
TurbopackRuleConfigItem,
TurbopackRuleConfigItemOptions,
TurbopackRuleConfigCollection,
TurbopackRuleCondition,
TurbopackLoaderBuiltinCondition,
Expand Down Expand Up @@ -135,26 +134,17 @@ const zTurbopackCondition: zod.ZodType<TurbopackRuleCondition> = z.union([
}),
])

const zTurbopackRuleConfigItemOptions: zod.ZodType<TurbopackRuleConfigItemOptions> =
const zTurbopackRuleConfigItem: zod.ZodType<TurbopackRuleConfigItem> =
z.strictObject({
loaders: z.array(zTurbopackLoaderItem),
as: z.string().optional(),
condition: zTurbopackCondition.optional(),
})

const zTurbopackRuleConfigItem: zod.ZodType<TurbopackRuleConfigItem> = z.union([
z.literal(false),
z.record(
zTurbopackLoaderBuiltinCondition,
z.lazy(() => zTurbopackRuleConfigItem)
),
zTurbopackRuleConfigItemOptions,
])

const zTurbopackRuleConfigCollection: zod.ZodType<TurbopackRuleConfigCollection> =
z.union([
zTurbopackRuleConfigItem,
z.array(z.union([zTurbopackLoaderItem, zTurbopackRuleConfigItemOptions])),
z.array(z.union([zTurbopackLoaderItem, zTurbopackRuleConfigItem])),
])

const zTurbopackConfig: zod.ZodType<TurbopackOptions> = z.strictObject({
Expand Down
9 changes: 2 additions & 7 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,12 @@ export type TurbopackRuleCondition =
content?: RegExp
}

export type TurbopackRuleConfigItemOptions = {
export type TurbopackRuleConfigItem = {
loaders: TurbopackLoaderItem[]
as?: string
condition?: TurbopackRuleCondition
}

export type TurbopackRuleConfigItem =
| TurbopackRuleConfigItemOptions
| { [condition in TurbopackLoaderBuiltinCondition]?: TurbopackRuleConfigItem }
| false

/**
* This can be an object representing a single configuration, or a list of
* loaders and/or rule configuration objects.
Expand All @@ -300,7 +295,7 @@ export type TurbopackRuleConfigItem =
*/
export type TurbopackRuleConfigCollection =
| TurbopackRuleConfigItem
| (TurbopackLoaderItem | TurbopackRuleConfigItemOptions)[]
| (TurbopackLoaderItem | TurbopackRuleConfigItem)[]

export interface TurbopackOptions {
/**
Expand Down
Loading
Loading