React Compiler
Automatically optimize re-renders with React Compiler.
React Compiler is an optional build-time optimization that automatically memoizes your components, eliminating the need for manual useMemo, useCallback, and React.memo.
Why It Matters Here
In a standard React DOM app, unnecessary re-renders cause DOM mutations -- relatively cheap operations. In @fcannizzaro/streamdeck-react, every re-render triggers an expensive pipeline:
- VNode tree reconstruction
- Layout computation (Takumi / Rust)
- Rasterization to PNG (Rust native binding)
- Image push to Stream Deck hardware
React Compiler prevents unnecessary re-renders at the source, before the VNode tree is even touched. This is especially valuable for components using useTick, useInterval, or frequent state updates.
Setup via Scaffolding
The easiest way is to enable it during project creation:
npm create streamdeck-react@latestThe CLI will ask "Use React Compiler?" during setup. You can also pass it as a flag:
npm create streamdeck-react@latest --react-compiler trueWhen enabled, the generated config uses babel-plugin-react-compiler -- via @rollup/plugin-babel for Rollup or @rolldown/plugin-babel for Vite 8.
Manual Setup
Rollup
If you have an existing Rollup project using esbuild, replace it with Babel:
1. Swap Dependencies
Remove esbuild:
bun remove rollup-plugin-esbuildInstall Babel + React Compiler:
bun add -d @rollup/plugin-babel @babel/core @babel/preset-typescript @babel/preset-react babel-plugin-react-compiler2. Update Rollup Config
Replace the esbuild import and plugin:
// rollup.config.mjs
import { builtinModules } from "node:module";
import { babel } from "@rollup/plugin-babel";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import { streamDeckReact } from "@fcannizzaro/streamdeck-react/rollup";
const PLUGIN_DIR = "com.example.my-plugin.sdPlugin";
const builtins = new Set(builtinModules.flatMap((m) => [m, `node:${m}`]));
export default {
input: "src/plugin.ts",
output: {
file: `${PLUGIN_DIR}/bin/plugin.mjs`,
format: "es",
sourcemap: true,
inlineDynamicImports: true,
},
external: (id) => builtins.has(id),
plugins: [
resolve({ preferBuiltins: true }),
commonjs(),
json(),
babel({
babelHelpers: "bundled",
extensions: [".js", ".jsx", ".ts", ".tsx"],
exclude: "**/node_modules/**",
plugins: ["babel-plugin-react-compiler"],
presets: ["@babel/preset-typescript", ["@babel/preset-react", { runtime: "automatic" }]],
}),
streamDeckReact({
targets: [{ platform: "darwin", arch: "arm64" }],
}),
],
};Key differences from the esbuild config:
babel()replacesesbuild()exclude: '**/node_modules/**'prevents Babel from processing dependenciesbabel-plugin-react-compilermust be listed inplugins(runs first)@babel/preset-typescriptand@babel/preset-reacthandle TypeScript and JSX
Vite 8 (Rolldown)
If you have an existing Vite 8 project, add the Babel plugin:
1. Install Dependencies
bun add -d @rolldown/plugin-babel @babel/core babel-plugin-react-compiler2. Update Vite Config
Add the babel plugin with reactCompilerPreset():
// vite.config.ts
import { builtinModules } from "node:module";
import { resolve } from "node:path";
import { defineConfig, esmExternalRequirePlugin } from "vite";
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
import babel from "@rolldown/plugin-babel";
import { streamDeckReact } from "@fcannizzaro/streamdeck-react/vite";
const PLUGIN_DIR = "com.example.my-plugin.sdPlugin";
const builtins = builtinModules.flatMap((m) => [m, `node:${m}`]);
export default defineConfig({
resolve: {
conditions: ["node"],
},
plugins: [
esmExternalRequirePlugin({ external: builtins }),
react(),
// @ts-expect-error — @rolldown/plugin-babel types incorrectly mark inherited babel fields as required
await babel({
presets: [reactCompilerPreset()],
}),
streamDeckReact({
uuid: "com.example.my-plugin",
targets: [{ platform: "darwin", arch: "arm64" }],
}),
],
build: {
target: "node20",
outDir: resolve(PLUGIN_DIR, "bin"),
emptyOutDir: false,
sourcemap: true,
minify: false,
lib: {
entry: resolve("src/plugin.ts"),
formats: ["es"],
fileName: () => "plugin.mjs",
},
rolldownOptions: {
output: {
codeSplitting: false,
},
},
},
});Key differences from the non-compiler Vite config:
- Import
reactCompilerPresetfrom@vitejs/plugin-react - Add
@rolldown/plugin-babelwithreactCompilerPreset()afterreact()
Verifying It Works
After building, check the output bundle for the compiler's runtime marker:
grep "react.memo_cache_sentinel" com.example.my-plugin.sdPlugin/bin/plugin.mjsIf the compiler is active, you'll see Symbol.for("react.memo_cache_sentinel") in the output.
Opting Out Per Component
If a specific component causes issues after compilation, use the "use no memo" directive:
function ProblematicComponent() {
"use no memo";
// This component won't be optimized by the compiler
}No Code Changes Required
React Compiler works with standard React patterns. Your existing components, hooks, and state management (Zustand, Jotai, TanStack Query) all work without modification.