diff --git a/.yarnrc.yml b/.yarnrc.yml
index 9a689b3..ac26c69 100644
--- a/.yarnrc.yml
+++ b/.yarnrc.yml
@@ -8,7 +8,7 @@ catalog:
catalogs:
rolldown:
- "@rollipop/rolldown": 1.0.19
+ "@rollipop/rolldown": 1.0.20
enableScripts: true
diff --git a/docs/content/docs/features/meta.json b/docs/content/docs/features/meta.json
index 86d1a4e..2beea08 100644
--- a/docs/content/docs/features/meta.json
+++ b/docs/content/docs/features/meta.json
@@ -11,6 +11,7 @@
"sse",
"mcp",
"custom-command",
+ "react-compiler",
"reanimated-worklets",
"module-federation",
"experimental"
diff --git a/docs/content/docs/features/react-compiler.mdx b/docs/content/docs/features/react-compiler.mdx
new file mode 100644
index 0000000..02f3845
--- /dev/null
+++ b/docs/content/docs/features/react-compiler.mdx
@@ -0,0 +1,73 @@
+---
+title: React Compiler
+---
+
+Rollipop can enable rolldown's native React Compiler transform for React and React Native application code. The transform runs in the bundler pipeline, so you can remove manual memoization such as `useMemo`, `useCallback`, and `React.memo` when the compiler can safely optimize the component.
+
+
+ React Compiler changes generated application code. Enable it after validating your app locally,
+ and keep any manual memoization that exists for semantic behavior rather than render performance.
+
+
+## Setup
+
+React Compiler is disabled by default. Pass an empty object to opt in with Rollipop's defaults:
+
+```ts title="rollipop.config.ts"
+import { defineConfig } from 'rollipop';
+
+export default defineConfig({
+ transformer: {
+ reactCompiler: {},
+ },
+});
+```
+
+With the empty object form, Rollipop skips files whose module id matches `/node_modules/`.
+
+## Configuration
+
+`transformer.reactCompiler` accepts rolldown's React Compiler options.
+
+| Property | Type | Description |
+| ----------------- | ---------------------------------------------- | ------------------------------------------------------------------------- |
+| `include` | `Array` | File patterns to compile. Empty means all files that enter the transform. |
+| `exclude` | `Array` | File patterns to skip. Overrides Rollipop's default exclude list. |
+| `compilationMode` | `'infer' \| 'syntax' \| 'annotation' \| 'all'` | Which functions to compile. |
+| `panicThreshold` | `'none' \| 'critical_errors' \| 'all_errors'` | How strictly compilation failures should be reported. |
+| `target` | `'17' \| '18' \| '19'` | React runtime version target. |
+| `noEmit` | `boolean` | Report diagnostics without emitting transformed code. |
+
+### Excluding dependencies
+
+If you omit `exclude`, Rollipop uses `/node_modules/` by default:
+
+```ts title="rollipop.config.ts"
+export default defineConfig({
+ transformer: {
+ reactCompiler: {},
+ },
+});
+```
+
+If you provide `exclude`, your value replaces that default. Include `/node_modules/` yourself if you still want dependency code skipped:
+
+```ts title="rollipop.config.ts"
+export default defineConfig({
+ transformer: {
+ reactCompiler: {
+ exclude: [/node_modules/, /vendor/],
+ },
+ },
+});
+```
+
+## Manual Memoization
+
+After enabling React Compiler, remove memoization that exists only to avoid re-renders:
+
+- `useMemo` for derived values that can be recomputed safely
+- `useCallback` for event handlers and callbacks passed to children
+- `React.memo` around components that do not need a custom equality check
+
+Keep memoization when it is part of observable behavior, protects expensive non-render work, or relies on a custom comparison that the compiler cannot infer.
diff --git a/docs/content/docs/get-started/configuration.mdx b/docs/content/docs/get-started/configuration.mdx
index 90dd55f..5abd65e 100644
--- a/docs/content/docs/get-started/configuration.mdx
+++ b/docs/content/docs/get-started/configuration.mdx
@@ -164,6 +164,27 @@ Flow type syntax transformation configuration.
Filter for Flow transformation pipeline. Uses Rolldown's [hook filter feature](https://rolldown.rs/apis/plugin-hook-filters) to determine which files should be processed.
+### transformer.reactCompiler
+
+React Compiler transformation configuration.
+
+- **Type:** `OxcReactCompilerOptions`
+- **Default:** `undefined`
+
+React Compiler is disabled by default. Pass an empty object to enable it with Rollipop's defaults:
+
+```ts
+export default defineConfig({
+ transformer: {
+ reactCompiler: {},
+ },
+});
+```
+
+When `exclude` is omitted, Rollipop skips files whose module id matches `/node_modules/`. If you specify `exclude`, your value replaces that default.
+
+See [React Compiler](/docs/features/react-compiler) for setup guidance and option details.
+
### transformer.babel
Custom Babel transformation rules.
diff --git a/examples/0.84/rollipop.config.ts b/examples/0.84/rollipop.config.ts
index 058f8cd..259a7ef 100644
--- a/examples/0.84/rollipop.config.ts
+++ b/examples/0.84/rollipop.config.ts
@@ -14,6 +14,9 @@ export default defineConfig({
enabled: true,
autoOpen: true,
},
+ transformer: {
+ reactCompiler: {},
+ },
plugins: [
svg(),
myPlugin(),
diff --git a/examples/0.84/src/screens/DetailsScreen.tsx b/examples/0.84/src/screens/DetailsScreen.tsx
index 79a83ba..cbbfe30 100644
--- a/examples/0.84/src/screens/DetailsScreen.tsx
+++ b/examples/0.84/src/screens/DetailsScreen.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useMemo, useState } from 'react';
+import { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { AppButton } from '../components/AppButton';
@@ -38,15 +38,12 @@ export function DetailsScreen() {
const [rows, setRows] = useState(toIdleRows);
const [running, setRunning] = useState(false);
- const summary = useMemo(() => {
- const reports = rows.filter((row): row is RuntimeCheckReport => {
- return row.status === 'passed' || row.status === 'failed';
- });
+ const reports = rows.filter((row): row is RuntimeCheckReport => {
+ return row.status === 'passed' || row.status === 'failed';
+ });
+ const summary = summarizeReports(reports);
- return summarizeReports(reports);
- }, [rows]);
-
- const handleRunAll = useCallback(async () => {
+ async function handleRunAll() {
setRunning(true);
setRows((currentRows) =>
currentRows.map((row) => ({
@@ -60,7 +57,7 @@ export function DetailsScreen() {
setRows(reports.map(toReportRow));
setRunning(false);
- }, []);
+ }
const summaryText =
summary.total === 0
diff --git a/packages/rollipop/src/config/load-config.ts b/packages/rollipop/src/config/load-config.ts
index e69224d..3888f55 100644
--- a/packages/rollipop/src/config/load-config.ts
+++ b/packages/rollipop/src/config/load-config.ts
@@ -8,6 +8,7 @@ import type { Plugin, PluginConfig, ResolvedPluginConfig } from '../core/plugins
import { getDefaultConfig, type ResolvedConfig } from './defaults';
import { DefineConfigContext } from './define-config';
import { mergeConfig } from './merge-config';
+import { printConfigNotice } from './notice';
import type { Config, PluginOption } from './types';
const CONFIG_FILE_NAME = 'rollipop';
@@ -57,6 +58,7 @@ export async function loadConfig(options: LoadConfigOptions = {}) {
}
await invokeConfigResolved(resolvedConfig, plugins);
+ printConfigNotice(resolvedConfig);
return resolvedConfig;
}
diff --git a/packages/rollipop/src/config/notice.ts b/packages/rollipop/src/config/notice.ts
new file mode 100644
index 0000000..ff713d5
--- /dev/null
+++ b/packages/rollipop/src/config/notice.ts
@@ -0,0 +1,8 @@
+import { logger } from '../logger';
+import type { ResolvedConfig } from './defaults';
+
+export function printConfigNotice(config: ResolvedConfig) {
+ if (config.transformer.reactCompiler != null) {
+ logger.info('✨ React Compiler is enabled');
+ }
+}
diff --git a/packages/rollipop/src/core/__tests__/rolldown.spec.ts b/packages/rollipop/src/core/__tests__/rolldown.spec.ts
index 6508f24..4a7eeec 100644
--- a/packages/rollipop/src/core/__tests__/rolldown.spec.ts
+++ b/packages/rollipop/src/core/__tests__/rolldown.spec.ts
@@ -28,7 +28,77 @@ function findReporterPlugin(options: Awaited,
+ contextId: string,
+) {
+ config.devMode.hmr = false;
+ config.reactNative.assetRegistryPath = path.join(config.root, 'package.json');
+
+ return resolveRolldownOptions(
+ {
+ id: contextId,
+ root: config.root,
+ buildType: 'build',
+ storage: {
+ get: () => ({ build: {} }),
+ set: () => {},
+ } as unknown as BundlerContext['storage'],
+ state: { revision: 0, latestBuildStartTime: 0 },
+ },
+ config,
+ resolveBuildOptions(config, { platform: 'ios', dev: true }),
+ );
+}
+
describe('resolveRolldownOptions', () => {
+ it('keeps react compiler disabled by default', async () => {
+ resolveRolldownOptions.cache.clear();
+
+ const options = await resolveTestRolldownOptions(
+ createTestConfig(process.cwd()),
+ 'test-bundler-react-compiler-disabled',
+ );
+
+ expect(options.input?.transform?.reactCompiler).toBeUndefined();
+ });
+
+ it('enables react compiler with default exclude when configured with an empty object', async () => {
+ resolveRolldownOptions.cache.clear();
+
+ const config = createTestConfig(process.cwd());
+ config.transformer.reactCompiler = {};
+
+ const options = await resolveTestRolldownOptions(
+ config,
+ 'test-bundler-react-compiler-empty-object',
+ );
+
+ expect(options.input?.transform?.reactCompiler).toEqual({
+ exclude: [/node_modules/],
+ });
+ });
+
+ it('uses user react compiler exclude patterns instead of the default node_modules rule', async () => {
+ resolveRolldownOptions.cache.clear();
+
+ const config = createTestConfig(process.cwd());
+ config.transformer.reactCompiler = {
+ exclude: [/vendor/],
+ target: '18',
+ };
+
+ const options = await resolveTestRolldownOptions(
+ config,
+ 'test-bundler-react-compiler-custom-exclude',
+ );
+
+ expect(options.input?.transform?.reactCompiler).toEqual({
+ exclude: [/vendor/],
+ target: '18',
+ });
+ });
+
it('transforms only polyfills that opt into Rollipop transform', async () => {
resolveRolldownOptions.cache.clear();
diff --git a/packages/rollipop/src/core/rolldown.ts b/packages/rollipop/src/core/rolldown.ts
index 2d4a94c..87a1150 100644
--- a/packages/rollipop/src/core/rolldown.ts
+++ b/packages/rollipop/src/core/rolldown.ts
@@ -163,7 +163,10 @@ export async function resolveRolldownOptions(
mode: 'Runtime',
},
} satisfies TransformOptions,
- rolldownTransform,
+ {
+ ...rolldownTransform,
+ reactCompiler: resolveReactCompilerTransformOptions(rolldownTransform.reactCompiler),
+ },
);
const entryPluginOptions = resolveEntryPluginOptions(config);
@@ -339,6 +342,19 @@ function resolveWorkletsConfig(
);
}
+function resolveReactCompilerTransformOptions(
+ reactCompiler: TransformOptions['reactCompiler'],
+): TransformOptions['reactCompiler'] {
+ if (reactCompiler == null) {
+ return undefined;
+ }
+
+ return {
+ ...reactCompiler,
+ exclude: reactCompiler.exclude ?? [/node_modules/],
+ };
+}
+
function resolveBabelPluginOptions(
config: ResolvedConfig,
context: BundlerContext,
diff --git a/yarn.lock b/yarn.lock
index 7e61f72..1ab8b55 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8613,68 +8613,68 @@ __metadata:
languageName: unknown
linkType: soft
-"@rollipop/rolldown-binding-darwin-arm64@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-darwin-arm64@npm:1.0.19"
+"@rollipop/rolldown-binding-darwin-arm64@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-darwin-arm64@npm:1.0.20"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-darwin-x64@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-darwin-x64@npm:1.0.19"
+"@rollipop/rolldown-binding-darwin-x64@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-darwin-x64@npm:1.0.20"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-linux-arm64-gnu@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-linux-arm64-gnu@npm:1.0.19"
+"@rollipop/rolldown-binding-linux-arm64-gnu@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-linux-arm64-gnu@npm:1.0.20"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-linux-arm64-musl@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-linux-arm64-musl@npm:1.0.19"
+"@rollipop/rolldown-binding-linux-arm64-musl@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-linux-arm64-musl@npm:1.0.20"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-linux-x64-gnu@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-linux-x64-gnu@npm:1.0.19"
+"@rollipop/rolldown-binding-linux-x64-gnu@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-linux-x64-gnu@npm:1.0.20"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-linux-x64-musl@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-linux-x64-musl@npm:1.0.19"
+"@rollipop/rolldown-binding-linux-x64-musl@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-linux-x64-musl@npm:1.0.20"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@rollipop/rolldown-binding-win32-x64-msvc@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown-binding-win32-x64-msvc@npm:1.0.19"
+"@rollipop/rolldown-binding-win32-x64-msvc@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown-binding-win32-x64-msvc@npm:1.0.20"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@rollipop/rolldown@npm:1.0.19":
- version: 1.0.19
- resolution: "@rollipop/rolldown@npm:1.0.19"
+"@rollipop/rolldown@npm:1.0.20":
+ version: 1.0.20
+ resolution: "@rollipop/rolldown@npm:1.0.20"
dependencies:
"@oxc-project/types": "npm:=0.137.0"
"@rolldown/pluginutils": "npm:^1.0.0"
- "@rollipop/rolldown-binding-darwin-arm64": "npm:1.0.19"
- "@rollipop/rolldown-binding-darwin-x64": "npm:1.0.19"
- "@rollipop/rolldown-binding-linux-arm64-gnu": "npm:1.0.19"
- "@rollipop/rolldown-binding-linux-arm64-musl": "npm:1.0.19"
- "@rollipop/rolldown-binding-linux-x64-gnu": "npm:1.0.19"
- "@rollipop/rolldown-binding-linux-x64-musl": "npm:1.0.19"
- "@rollipop/rolldown-binding-win32-x64-msvc": "npm:1.0.19"
+ "@rollipop/rolldown-binding-darwin-arm64": "npm:1.0.20"
+ "@rollipop/rolldown-binding-darwin-x64": "npm:1.0.20"
+ "@rollipop/rolldown-binding-linux-arm64-gnu": "npm:1.0.20"
+ "@rollipop/rolldown-binding-linux-arm64-musl": "npm:1.0.20"
+ "@rollipop/rolldown-binding-linux-x64-gnu": "npm:1.0.20"
+ "@rollipop/rolldown-binding-linux-x64-musl": "npm:1.0.20"
+ "@rollipop/rolldown-binding-win32-x64-msvc": "npm:1.0.20"
dependenciesMeta:
"@rollipop/rolldown-binding-darwin-arm64":
optional: true
@@ -8692,7 +8692,7 @@ __metadata:
optional: true
bin:
rolldown: ./bin/cli.mjs
- checksum: 10c0/1b2f51f4ace09024f32d4434991729898d46674f618fdef942042657b992919f3afb4d344a1243a77386497323ddb85f8dd5be9f82bfb9dbe49ca033c8a20889
+ checksum: 10c0/4f05ca4474832bfc4bb2a31ad8e7fe96d983ed1de91567310598814a6923c8f577e609463bfd1895361bf9c03188e15f2008d42a49985a856c66f02eecbe4491
languageName: node
linkType: hard