React Compiler в экосистеме Oxc: ускорение компиляции в 10 раз и 22 правила валидации в Oxlint
Автоматическая мемоизация компонентов и хуков — одна из ключевых возможностей современной экосистемы React. Компилятор React (ранее известный как React Forget) берет на себя рутинную расстановку useMemo и useCallback, анализируя поток данных и зависимости на этапе сборки. Однако широкое внедрение компилятора в рабочие пайплайны сдерживалось высокими накладными расходами Babel на трансформацию больших кодовых баз.
Проблема первого поколения: почему Rust-порт React Compiler требовал нативного AST
React Compiler 1.0 изначально вышел как плагин Babel (babel-plugin-react-compiler). Позже команда React представила официальный порт компилятора на язык Rust. При попытке интегрировать его в современные Rust-инструменты сборки разработчики столкнулись с архитектурным барьером.
Автономный Rust-порт компилятора сохранял собственное внутреннее представление синтаксического дерева в формате Babel AST (Abstract Syntax Tree, абстрактное синтаксическое дерево — структура программы в памяти компилятора). Для высокопроизводительного Rust-тулчейна Oxc это приводило к двойной конвертации:
- Трансляция нативного AST Oxc в формат Babel-совместимого AST перед запуском анализа.
- Выполнение фаз оптимизации и мемоизации компилятора.
- Обратная конвертация полученного дерева в AST Oxc для генерации итогового JavaScript-кода.
Такой двойной проход создавал паразитные выделения памяти, замедлял сборку и раздувал размер бинарного файла более чем на 5 МБ.
Вендоринг и прямое исполнение: 10-кратный прирост скорости и оптимизация бинарника
Команда Oxc приняла решение завендорить кодовую базу компилятора внутрь Oxc и переписать его фазы для прямого исполнения поверх нативного AST Oxc без промежуточных слоев.
Результаты оптимизации:
- Скорость компиляции. В локальных бенчмарках Oxc трансформация компонентов пакетом
oxc-transform-reactвыполняется более чем в 10 раз быстрее по сравнению сbabel-plugin-react-compiler. Файлы, требовавшие около 100 мс на обработку в Babel, компилируются примерно за 10 мс. По сравнению с исходным Rust-портом скорость выросла примерно в 2 раза за счет сокращения аллокаций памяти. - Снижение размера бинарника. Первая форк-версия весила 8.66 МБ (для архитектуры macOS ARM64). После удаления лишнего AST, ликвидации промежуточного JSON round-trip, замены тяжелого regex-движка и очистки мертвого кода размер биндинга
oxc-transform-reactверсии v0.144.0 снизился до 3.97 МБ. - Изоляция зависимостей. Компилятор упакован в отдельный опциональный пакет
oxc-transform-react, благодаря чему базовый тулчейн Oxc Transform не увеличивается в объеме.
22 правила валидации в Oxlint: диагностика с понятными кодфреймами
React Compiler требует строгого соблюдения правил React (Rules of React): неизменяемости состояния, чистоты функций рендеринга и корректной работы с хуками и рефами. Любое нарушение приводит к тому, что компилятор молча пропускает оптимизацию проблемного компонента.
Статический анализатор Oxlint получил 22 специализированных правила на базе компилятора, объединенных в категорию correctness:
{
"plugins": ["react"],
"categories": {
"correctness": "error"
}
}
Ранее использовавшееся экспериментальное правило react/react-compiler устарело и должно быть удалено из конфигурации.
В категорию correctness вошли рекомендованные правила: error-boundaries, globals, immutability, incompatible-library, preserve-manual-memoization, purity, refs, set-state-in-effect, set-state-in-render, static-components, use-memo и void-use-memo. Правила из категорий ограничений и подозрительного кода (unsupported-syntax, hooks, memo-dependencies) настраиваются отдельно.
Пример информативной диагностики Oxlint при попытке прямой мутации состояния:
⚠ react(immutability): This value cannot be modified
╭─[immutability.tsx:7:11]
6 │ const [state, setState] = useState({a: 0});
7 │ state.a = 1;
· ──┬──
· ╰── value cannot be modified
8 │ return <div>{props.foo}</div>;
╰────
help: Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead
note: React Compiler skipped optimizing this component or hook.
Диагностика наглядно объясняет причину пропуска оптимизации разработчикам и кодинг-агентам, предлагая готовое исправление.
Программный интерфейс и интеграция со сборщиками
Трансформация компонентов выполняется через синхронный API:
import { transformSync } from "oxc-transform-react";
const result = transformSync(
"Component.tsx",
`export function Component({ name }: { name: string }) {
return <div>Hello {name}</div>;
}`,
{
reactCompiler: { target: "19" },
jsx: { runtime: "automatic" },
}
);
if (result.fatal) {
console.error(result.errors);
} else {
console.log(result.code);
}
Нативная интеграция со сборщиком Vite реализуется в рамках PR #1419 в плагине @vitejs/plugin-react. Архитектурное решение Oxc оставляет ядра Vite и Rolldown нейтральными к фреймворкам, изолируя специфику React внутри плагина.
Верификация соответствия, исходные карты и стратегия внедрения
- Проверка соответствия (Conformance). Команда Oxc протестировала генерацию кода на более чем 100 популярных репозиториях (свыше 100 000 исходных файлов), подтвердив идентичность скомпилированного вывода эталонной реализации Babel. По умолчанию настройки соответствуют стабильной версии React Compiler v1.
- Поддержка Source Maps. Доработана сквозная генерация исходных карт с поддержкой TypeScript, JSX и механизма горячей перезагрузки React Fast Refresh.
- Статус разработки. В кодовой базе вендоренного компилятора Oxc осталось 10 прямых маркеров TODO и 57 диагностических конструкторов (в исходных крейтах их было 16 и 62 соответственно).
Рекомендации по поэтапному переходу
- Активация линтера. Включить категорию
correctnessв Oxlint на тестовой ветке и устранить выявленные предупреждения по чистоте функций и мутациям. - Сохранение существующей мемоизации. Правило
preserve-manual-memoizationпредотвращает случайную поломку критичных ручных оптимизаций. - Инкрементальный запуск. Согласно рекомендациям официальной документации React Compiler, оптимизацию следует внедрять поэтапно по отдельным директориям проекта, проверяя снапшоты и регрессионные тесты интерфейса.
