Skip to content

Commit 6016558

Browse files
authored
Turbopack: use a source content regexp for the react compiler (#82631)
### What? Depending on compilation mode of the react compiler, check the source code if matches a certain regex This also adds `content` as condition for webpack loaders Closes PACK-5252
1 parent 88b3223 commit 6016558

File tree

7 files changed

+115
-51
lines changed

7 files changed

+115
-51
lines changed

crates/next-core/src/next_config.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -577,24 +577,32 @@ impl TryInto<ConditionPath> for ConfigConditionPath {
577577
fn try_into(self) -> Result<ConditionPath> {
578578
Ok(match self {
579579
ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
580-
ConfigConditionPath::Regex(path) => {
581-
ConditionPath::Regex(EsRegex::new(&path.source, &path.flags)?.resolved_cell())
582-
}
580+
ConfigConditionPath::Regex(path) => ConditionPath::Regex(path.try_into()?),
583581
})
584582
}
585583

586584
type Error = anyhow::Error;
587585
}
588586

587+
impl TryInto<ResolvedVc<EsRegex>> for RegexComponents {
588+
fn try_into(self) -> Result<ResolvedVc<EsRegex>> {
589+
Ok(EsRegex::new(&self.source, &self.flags)?.resolved_cell())
590+
}
591+
592+
type Error = anyhow::Error;
593+
}
594+
589595
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
590596
pub struct ConfigConditionItem {
591-
pub path: ConfigConditionPath,
597+
pub path: Option<ConfigConditionPath>,
598+
pub content: Option<RegexComponents>,
592599
}
593600

594601
impl TryInto<ConditionItem> for ConfigConditionItem {
595602
fn try_into(self) -> Result<ConditionItem> {
596603
Ok(ConditionItem {
597-
path: self.path.try_into()?,
604+
path: self.path.map(|p| p.try_into()).transpose()?,
605+
content: self.content.map(|r| r.try_into()).transpose()?,
598606
})
599607
}
600608

packages/next/src/build/swc/index.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
1010
import { downloadNativeNextSwc, downloadWasmSwc } from '../../lib/download-swc'
1111
import type {
1212
NextConfigComplete,
13+
ReactCompilerOptions,
1314
TurbopackLoaderItem,
1415
TurbopackRuleConfigItem,
1516
TurbopackRuleConfigItemOptions,
@@ -805,13 +806,15 @@ function bindingToApi(
805806
originalNextConfig: NextConfigComplete,
806807
projectPath: string
807808
): Record<string, any> {
808-
let nextConfig = { ...(originalNextConfig as any) }
809+
let nextConfig = { ...(originalNextConfig as NextConfigComplete) }
809810

810811
const reactCompilerOptions = nextConfig.experimental?.reactCompiler
811812

812813
// It is not easy to set the rules inside of rust as resolving, and passing the context identical to the webpack
813814
// config is bit hard, also we can reuse same codes between webpack config in here.
814815
if (reactCompilerOptions) {
816+
const options: ReactCompilerOptions =
817+
typeof reactCompilerOptions === 'object' ? reactCompilerOptions : {}
815818
const ruleKeys = ['*.ts', '*.js', '*.jsx', '*.tsx']
816819
if (
817820
Object.keys(nextConfig?.turbopack?.rules ?? {}).some((key) =>
@@ -826,15 +829,27 @@ function bindingToApi(
826829
)
827830
} else {
828831
nextConfig.turbopack ??= {}
832+
nextConfig.turbopack.conditions ??= {}
829833
nextConfig.turbopack.rules ??= {}
830834

831835
for (const key of ruleKeys) {
832-
nextConfig.turbopack.rules[key] = {
836+
nextConfig.turbopack.conditions[`#reactCompiler/${key}`] = {
837+
path: key,
838+
content:
839+
options.compilationMode === 'annotation'
840+
? /['"]use memo['"]/
841+
: !options.compilationMode ||
842+
options.compilationMode === 'infer'
843+
? // Matches declaration or useXXX or </ (closing jsx) or /> (self closing jsx)
844+
/['"]use memo['"]|\Wuse[A-Z]|<\/|\/>/
845+
: undefined,
846+
}
847+
nextConfig.turbopack.rules[`#reactCompiler/${key}`] = {
833848
browser: {
834849
foreign: false,
835850
loaders: [
836851
getReactCompilerLoader(
837-
originalNextConfig.experimental.reactCompiler,
852+
reactCompilerOptions,
838853
projectPath,
839854
nextConfig.dev,
840855
/* isServer */ false,
@@ -905,23 +920,36 @@ function bindingToApi(
905920
if (conditions) {
906921
type SerializedConditions = {
907922
[key: string]: {
908-
path:
923+
path?:
909924
| { type: 'regex'; value: { source: string; flags: string } }
910925
| { type: 'glob'; value: string }
926+
content?: { source: string; flags: string }
927+
}
928+
}
929+
930+
function regexComponents(regex: RegExp): {
931+
source: string
932+
flags: string
933+
} {
934+
return {
935+
source: regex.source,
936+
flags: regex.flags,
911937
}
912938
}
913939

914940
const serializedConditions: SerializedConditions = {}
915941
for (const [key, value] of Object.entries(conditions)) {
916942
serializedConditions[key] = {
917943
...value,
918-
path:
919-
value.path instanceof RegExp
944+
path: !value.path
945+
? undefined
946+
: value.path instanceof RegExp
920947
? {
921948
type: 'regex',
922-
value: { source: value.path.source, flags: value.path.flags },
949+
value: regexComponents(value.path),
923950
}
924951
: { type: 'glob', value: value.path },
952+
content: !value.content ? undefined : regexComponents(value.content),
925953
}
926954
}
927955
nextConfigSerializable.turbopack.conditions = serializedConditions

packages/next/src/server/config-schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ const zTurboRuleConfigItemOrShortcut: zod.ZodType<TurbopackRuleConfigItemOrShort
129129
z.union([z.array(zTurboLoaderItem), zTurboRuleConfigItem])
130130

131131
const zTurboCondition: zod.ZodType<TurbopackRuleCondition> = z.object({
132-
path: z.union([z.string(), z.instanceof(RegExp)]),
132+
path: z.union([z.string(), z.instanceof(RegExp)]).optional(),
133+
content: z.instanceof(RegExp).optional(),
133134
})
134135

135136
const zTurbopackConfig: zod.ZodType<TurbopackOptions> = z.strictObject({

packages/next/src/server/config-shared.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ export type TurbopackLoaderItem =
262262
}
263263

264264
export type TurbopackRuleCondition = {
265-
path: string | RegExp
265+
path?: string | RegExp
266+
content?: RegExp
266267
}
267268

268269
export type TurbopackRuleConfigItemOrShortcut =

turbopack/crates/turbopack/src/module_options/mod.rs

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -489,43 +489,58 @@ impl ModuleOptions {
489489
)
490490
};
491491
for (key, rule) in webpack_loaders_options.rules.await?.iter() {
492-
rules.push(ModuleRule::new(
493-
RuleCondition::All(vec![
494-
if key.starts_with("#") {
495-
// This is a custom marker requiring a corresponding condition entry
496-
let conditions = (*webpack_loaders_options.conditions.await?)
497-
.context(
498-
"Expected a condition entry for the webpack loader rule \
499-
matching {key}. Create a `conditions` mapping in your \
500-
next.config.js",
501-
)?
502-
.await?;
503-
504-
let condition = conditions.get(key).context(
505-
"Expected a condition entry for the webpack loader rule matching \
506-
{key}.",
507-
)?;
508-
509-
match &condition.path {
510-
ConditionPath::Glob(glob) => RuleCondition::ResourcePathGlob {
492+
let mut rule_conditions = Vec::new();
493+
if key.starts_with("#") {
494+
// This is a custom marker requiring a corresponding condition entry
495+
let conditions = (*webpack_loaders_options.conditions.await?)
496+
.context(
497+
"Expected a condition entry for the webpack loader rule matching \
498+
{key}. Create a `conditions` mapping in your next.config.js",
499+
)?
500+
.await?;
501+
502+
let condition = conditions.get(key).context(
503+
"Expected a condition entry for the webpack loader rule matching {key}.",
504+
)?;
505+
506+
let ConditionItem { path, content } = &condition;
507+
508+
match &path {
509+
Some(ConditionPath::Glob(glob)) => {
510+
if glob.contains('/') {
511+
rule_conditions.push(RuleCondition::ResourcePathGlob {
511512
base: execution_context.project_path().owned().await?,
512513
glob: Glob::new(glob.clone()).await?,
513-
},
514-
ConditionPath::Regex(regex) => {
515-
RuleCondition::ResourcePathEsRegex(regex.await?)
516-
}
517-
}
518-
} else if key.contains('/') {
519-
RuleCondition::ResourcePathGlob {
520-
base: execution_context.project_path().owned().await?,
521-
glob: Glob::new(key.clone()).await?,
514+
});
515+
} else {
516+
rule_conditions.push(RuleCondition::ResourceBasePathGlob(
517+
Glob::new(glob.clone()).await?,
518+
));
522519
}
523-
} else {
524-
RuleCondition::ResourceBasePathGlob(Glob::new(key.clone()).await?)
525-
},
526-
RuleCondition::not(RuleCondition::ResourceIsVirtualSource),
527-
module_css_external_transform_conditions.clone(),
528-
]),
520+
}
521+
Some(ConditionPath::Regex(regex)) => {
522+
rule_conditions.push(RuleCondition::ResourcePathEsRegex(regex.await?));
523+
}
524+
None => {}
525+
}
526+
if let Some(content) = content {
527+
rule_conditions.push(RuleCondition::ResourceContentEsRegex(content.await?));
528+
}
529+
} else if key.contains('/') {
530+
rule_conditions.push(RuleCondition::ResourcePathGlob {
531+
base: execution_context.project_path().owned().await?,
532+
glob: Glob::new(key.clone()).await?,
533+
});
534+
} else {
535+
rule_conditions.push(RuleCondition::ResourceBasePathGlob(
536+
Glob::new(key.clone()).await?,
537+
));
538+
};
539+
rule_conditions.push(RuleCondition::not(RuleCondition::ResourceIsVirtualSource));
540+
rule_conditions.push(module_css_external_transform_conditions.clone());
541+
542+
rules.push(ModuleRule::new(
543+
RuleCondition::All(rule_conditions),
529544
vec![ModuleRuleEffect::SourceTransforms(ResolvedVc::cell(vec![
530545
ResolvedVc::upcast(
531546
WebpackLoaders::new(

turbopack/crates/turbopack/src/module_options/module_options_context.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ pub enum ConditionPath {
5050
#[turbo_tasks::value(shared)]
5151
#[derive(Clone, Debug)]
5252
pub struct ConditionItem {
53-
pub path: ConditionPath,
53+
pub path: Option<ConditionPath>,
54+
pub content: Option<ResolvedVc<EsRegex>>,
5455
}
5556

5657
#[turbo_tasks::value(shared)]

turbopack/crates/turbopack/src/module_options/rule_condition.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
33
use smallvec::SmallVec;
44
use turbo_esregex::EsRegex;
55
use turbo_tasks::{NonLocalValue, ReadRef, ResolvedVc, primitives::Regex, trace::TraceRawVcs};
6-
use turbo_tasks_fs::{FileSystemPath, glob::Glob};
6+
use turbo_tasks_fs::{FileContent, FileSystemPath, glob::Glob};
77
use turbopack_core::{
8-
reference_type::ReferenceType, source::Source, virtual_source::VirtualSource,
8+
asset::Asset, reference_type::ReferenceType, source::Source, virtual_source::VirtualSource,
99
};
1010

1111
#[derive(Debug, Clone, Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, NonLocalValue)]
@@ -24,6 +24,7 @@ pub enum RuleCondition {
2424
ContentTypeEmpty,
2525
ResourcePathRegex(#[turbo_tasks(trace_ignore)] Regex),
2626
ResourcePathEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
27+
ResourceContentEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
2728
/// For paths that are within the same filesystem as the `base`, it need to
2829
/// match the relative path from base to resource. This includes `./` or
2930
/// `../` prefix. For paths in a different filesystem, it need to match
@@ -166,6 +167,15 @@ impl RuleCondition {
166167
RuleCondition::ResourcePathEsRegex(regex) => {
167168
return Ok(regex.is_match(&path.path));
168169
}
170+
RuleCondition::ResourceContentEsRegex(regex) => {
171+
let content = source.content().file_content().await?;
172+
match &*content {
173+
FileContent::Content(file_content) => {
174+
return Ok(regex.is_match(&file_content.content().to_str()?));
175+
}
176+
FileContent::NotFound => return Ok(false),
177+
}
178+
}
169179
}
170180
}
171181
}

0 commit comments

Comments
 (0)