From 19043bdde3d89ff3c3f3e0da3095af9ab02a3478 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:52:04 +0900 Subject: [PATCH 1/5] perf(react): improve react compiler preset so that slightly more modules are filtered out --- packages/plugin-react/src/reactCompilerPreset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/src/reactCompilerPreset.ts b/packages/plugin-react/src/reactCompilerPreset.ts index 9c06590b1..e71ae0e08 100644 --- a/packages/plugin-react/src/reactCompilerPreset.ts +++ b/packages/plugin-react/src/reactCompilerPreset.ts @@ -17,7 +17,7 @@ export const reactCompilerPreset = ( code: options.compilationMode === 'annotation' ? /['"]use memo['"]/ - : /\b[A-Z]|\buse/, + : /\b[A-Z]|\buse[A-Z0-9]/, }, applyToEnvironmentHook: (env) => env.config.consumer === 'client', optimizeDeps: { From 1806a16471acd9a579bba1a331f1bc490731fc3f Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:58:14 +0900 Subject: [PATCH 2/5] feat: add tests and regex --- .../src/reactCompilerPreset.test.ts | 124 ++++++++++++++++++ .../plugin-react/src/reactCompilerPreset.ts | 5 +- 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 packages/plugin-react/src/reactCompilerPreset.test.ts diff --git a/packages/plugin-react/src/reactCompilerPreset.test.ts b/packages/plugin-react/src/reactCompilerPreset.test.ts new file mode 100644 index 000000000..cf18ce373 --- /dev/null +++ b/packages/plugin-react/src/reactCompilerPreset.test.ts @@ -0,0 +1,124 @@ +import { describe, expect, test } from 'vitest' +import { defaultCodeFilter } from './reactCompilerPreset' + +describe('defaultCodeFilter', () => { + const cases: Record = { + directive: ['"use memo";', true], + + 'component declaration': ['function App() { return <> }', true], + 'component arrow expression': [ + 'const MyComponent = () => { return <> }', + true, + ], + 'component arrow expressions': [ + 'const a = 0, MyComponent = () => { return <> }', + true, + ], + 'component arrow expressions (let)': [ + 'let a = 0, MyComponent = () => { return <> }', + true, + ], + 'component function expression': [ + 'const MyComponent = function() { return <> }', + true, + ], + 'component function expression (let)': [ + 'let MyComponent = function() { return <> }', + true, + ], + 'component function expression (var)': [ + 'var MyComponent = function() { return <> }', + true, + ], + 'exported component declaration': [ + 'export default function Page() { return <> }', + true, + ], + 'component assignment': [ + 'let MyComponent; MyComponent = function() { return <> }', + true, + ], + 'component default declaration': [ + 'const { MyComponent = function() { return <> } } = {}', + true, + ], + 'component default assignment': [ + 'let MyComponent; ({ MyComponent = function() { return <> } }) = {}', + true, + ], + 'component property': [ + 'const components = { MyComponent: () => <> }', + true, + ], + 'component method': [ + 'const components = { MyComponent() { return <> } }', + true, + ], + + 'hook declaration': ['function useEffect() { return <> }', true], + 'hook arrow expression': ['const useMyHook = () => { return <> }', true], + 'hook function expression': [ + 'const useMyHook = function() { return <> }', + true, + ], + 'hook with digit': ['function use0() { return <> }', true], + 'hook using hooks': [ + 'function useMyHook() { return useOtherHook() }', + true, + ], + 'hook using nested hooks': [ + 'function useMyHook() { return Foo.useOtherHook() }', + true, + ], + + 'React.forwardRef': ['React.forwardRef(() => <>)', true], + 'React.memo': ['React.memo(() => <>)', true], + forwardRef: [ + 'import { forwardRef } from "react"; forwardRef(() => <>)', + true, + ], + memo: ['import { memo } from "react"; memo(() => <>)', true], + + 'edge case: memo callback with hooks': [ + `import React, { useState } from "react"; +import { jsx } from "react/jsx-runtime" + +export const components = { + A: React.memo(() => { + const [state, setState] = useState(0); + + return jsx("div", { children: state }) + }) +}`, + true, + ], + 'edge case: memo without namespace': [ + `import { memo, useState } from "react"; + +export default memo(() => { + const [count, setCount] = useState(0); + return
{count}
+})`, + true, + ], + 'edge case: memo without namespace from re-export': [ + `import { memo, useState } from "my-react"; + +export default memo(() => { + const [count, setCount] = useState(0); + return
{count}
+})`, + true, + ], + + 'simple variable': ['const foo = 1', false], + 'lowercase function': ['function bar() {}', false], + 'lowercase arrow function': ['let baz = () => {}', false], + } + + for (const [name, [code, expected]] of Object.entries(cases)) { + test(name, () => { + expect(defaultCodeFilter.test(code)).toBe(expected) + }) + } +}) diff --git a/packages/plugin-react/src/reactCompilerPreset.ts b/packages/plugin-react/src/reactCompilerPreset.ts index 15930559a..a1caae0d8 100644 --- a/packages/plugin-react/src/reactCompilerPreset.ts +++ b/packages/plugin-react/src/reactCompilerPreset.ts @@ -3,6 +3,9 @@ import type { RolldownBabelPreset, } from '#optionalTypes' +export const defaultCodeFilter = + /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])\S*\s*[:=(]/ + export const reactCompilerPreset = ( options: Pick< ReactCompilerBabelPluginOptions, @@ -18,7 +21,7 @@ export const reactCompilerPreset = ( code: options.compilationMode === 'annotation' ? /['"]use memo['"]/ - : /\b[A-Z]|\buse[A-Z0-9]/, + : defaultCodeFilter, }, applyToEnvironmentHook: (env) => env.config.consumer === 'client', optimizeDeps: { From e0acfac3d76701fdda0feeaf4edc4f5ce667f56f Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:08:27 +0900 Subject: [PATCH 3/5] feat: exclude more cases --- packages/plugin-react/src/reactCompilerPreset.test.ts | 4 ++++ packages/plugin-react/src/reactCompilerPreset.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/plugin-react/src/reactCompilerPreset.test.ts b/packages/plugin-react/src/reactCompilerPreset.test.ts index cf18ce373..8736817c5 100644 --- a/packages/plugin-react/src/reactCompilerPreset.test.ts +++ b/packages/plugin-react/src/reactCompilerPreset.test.ts @@ -114,6 +114,10 @@ export default memo(() => { 'simple variable': ['const foo = 1', false], 'lowercase function': ['function bar() {}', false], 'lowercase arrow function': ['let baz = () => {}', false], + 'non assignments (1)': ['(0,useState)()', false], + 'non assignments (2)': ['[useState][0]()', false], + 'non assignments (3)': ['useState;s()', false], + 'non assignments (4)': ['useState,s()', false], } for (const [name, [code, expected]] of Object.entries(cases)) { diff --git a/packages/plugin-react/src/reactCompilerPreset.ts b/packages/plugin-react/src/reactCompilerPreset.ts index a1caae0d8..6e46e46e2 100644 --- a/packages/plugin-react/src/reactCompilerPreset.ts +++ b/packages/plugin-react/src/reactCompilerPreset.ts @@ -4,7 +4,7 @@ import type { } from '#optionalTypes' export const defaultCodeFilter = - /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])\S*\s*[:=(]/ + /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])[^\s:=(){}[\],;]*\s*[:=(]/ export const reactCompilerPreset = ( options: Pick< From a906dd50d4f6992351da18a110a9b69bcac005ce Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:30:43 +0900 Subject: [PATCH 4/5] feat: avoid object without methods --- packages/plugin-react/src/reactCompilerPreset.test.ts | 8 +++++++- packages/plugin-react/src/reactCompilerPreset.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/plugin-react/src/reactCompilerPreset.test.ts b/packages/plugin-react/src/reactCompilerPreset.test.ts index 8736817c5..7f9f8e0f7 100644 --- a/packages/plugin-react/src/reactCompilerPreset.test.ts +++ b/packages/plugin-react/src/reactCompilerPreset.test.ts @@ -46,7 +46,11 @@ describe('defaultCodeFilter', () => { 'let MyComponent; ({ MyComponent = function() { return <> } }) = {}', true, ], - 'component property': [ + 'component property function expression': [ + 'const components = { MyComponent: function() { return <> } }', + true, + ], + 'component property arrow function expression': [ 'const components = { MyComponent: () => <> }', true, ], @@ -118,6 +122,8 @@ export default memo(() => { 'non assignments (2)': ['[useState][0]()', false], 'non assignments (3)': ['useState;s()', false], 'non assignments (4)': ['useState,s()', false], + 'object without methods (1)': ['const obj = { useState: 1 }', false], + 'object without methods (2)': ['const obj = { Foo: 1 }', false], } for (const [name, [code, expected]] of Object.entries(cases)) { diff --git a/packages/plugin-react/src/reactCompilerPreset.ts b/packages/plugin-react/src/reactCompilerPreset.ts index 6e46e46e2..477b7fe79 100644 --- a/packages/plugin-react/src/reactCompilerPreset.ts +++ b/packages/plugin-react/src/reactCompilerPreset.ts @@ -4,7 +4,7 @@ import type { } from '#optionalTypes' export const defaultCodeFilter = - /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])[^\s:=(){}[\],;]*\s*[:=(]/ + /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])[^\s:=(){}[\],;]*\s*(?:\(|[:=]\s*(?:function|\())/ export const reactCompilerPreset = ( options: Pick< From 923bbdcb568000ad7f40c1ba6674d8185482e55c Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:17:46 +0900 Subject: [PATCH 5/5] fix: match code with explicit types --- .../src/reactCompilerPreset.test.ts | 32 +++++++++++++++++++ .../plugin-react/src/reactCompilerPreset.ts | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/plugin-react/src/reactCompilerPreset.test.ts b/packages/plugin-react/src/reactCompilerPreset.test.ts index 7f9f8e0f7..8cc1a8f1b 100644 --- a/packages/plugin-react/src/reactCompilerPreset.test.ts +++ b/packages/plugin-react/src/reactCompilerPreset.test.ts @@ -6,10 +6,26 @@ describe('defaultCodeFilter', () => { directive: ['"use memo";', true], 'component declaration': ['function App() { return <> }', true], + 'component declaration with types': [ + 'function App(): Type { return <> }', + true, + ], 'component arrow expression': [ 'const MyComponent = () => { return <> }', true, ], + 'component arrow expression with types (1)': [ + 'const MyComponent = (): Type => { return <> }', + true, + ], + 'component arrow expression with types (2)': [ + 'const MyComponent: Type = () => { return <> }', + true, + ], + 'component arrow expression with types (3)': [ + 'const MyComponent: SomeComplexType = () => { return <> }', + true, + ], 'component arrow expressions': [ 'const a = 0, MyComponent = () => { return <> }', true, @@ -22,6 +38,18 @@ describe('defaultCodeFilter', () => { 'const MyComponent = function() { return <> }', true, ], + 'component function expression with types (1)': [ + 'const MyComponent = function(): Type { return <> }', + true, + ], + 'component function expression with types (2)': [ + 'const MyComponent: Type = function() { return <> }', + true, + ], + 'component function expression with types (3)': [ + 'const MyComponent: SomeComplexType = function() { return <> }', + true, + ], 'component function expression (let)': [ 'let MyComponent = function() { return <> }', true, @@ -34,6 +62,10 @@ describe('defaultCodeFilter', () => { 'export default function Page() { return <> }', true, ], + 'exported component declaration with types': [ + 'export default function Page(): Type { return <> }', + true, + ], 'component assignment': [ 'let MyComponent; MyComponent = function() { return <> }', true, diff --git a/packages/plugin-react/src/reactCompilerPreset.ts b/packages/plugin-react/src/reactCompilerPreset.ts index 477b7fe79..cc8bb42af 100644 --- a/packages/plugin-react/src/reactCompilerPreset.ts +++ b/packages/plugin-react/src/reactCompilerPreset.ts @@ -4,7 +4,7 @@ import type { } from '#optionalTypes' export const defaultCodeFilter = - /forwardRef|memo|function\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])[^\s:=(){}[\],;]*\s*(?:\(|[:=]\s*(?:function|\())/ + /forwardRef|memo|(?:const|let|var|function)\s+(?:[A-Z]|use[A-Z0-9])|(?:[A-Z]|use[A-Z0-9])[^\s:=(){}[\],;]*\s*(?:\(|[:=]\s*(?:function|\())/ export const reactCompilerPreset = ( options: Pick<