Запуск React-библиотек в Preact с помощью preact/compat
Используйте preact/compat, чтобы запускать React-библиотеки в Preact, с alias для Vite, webpack, Rollup, Jest, TypeScript и типичных сбоев.
preact/compat — это слой совместимости, который поставляется внутри основного пакета preact начиная с Preact X. Он отображает публичный API React на Preact, благодаря чему большинство React-библиотек работают без изменений, а ваше приложение получает фреймворк размером около 9,5 КБ вместо куда более объёмного рантайма React.
Прописать алиас — дело пяти минут. А вот выяснить три дня спустя, что какой-то date picker выбрасывает ошибки откуда-то из глубин node_modules, — это та часть, о которой никто не предупреждает. Слой включается алиасингом react и react-dom на preact/compat в вашем сборщике: без изменений в коде компонентов и без установки отдельного пакета. В этом руководстве приведены точные конфигурации алиасов для всех основных инструментов, пошаговый разбор миграции с оценкой выигрыша в размере бандла, а также честный рассказ о том, какие библиотеки ломаются.
Ключевые выводы
preact/compatпоставляется внутри пакетаpreact. Compat теперь живёт в ядре, поэтому отдельный пакетpreact-compatустарел, и достаточноnpm install preact.- Весь механизм — это алиасинг четырёх путей импорта:
react,react-dom,react-dom/test-utilsиreact/jsx-runtimeуказывают на Preact. - С
@preact/preset-viteалиасинг выполняется автоматически, так что писатьresolve.aliasвручную не нужно. - В webpack алиас
react-domдолжен быть указан нижеreact-dom/test-utils, иначе более широкое правило перекроет отображение test-utils. - Compat покрывает публичный API React, но не его внутренности. Библиотеки, обращающиеся к глубоким внутренним путям
react-domили зависящие от самых новых API React 19, всё ещё могут ломаться.
Что такое preact/compat и зачем он нужен?
preact/compat транслирует публичную поверхность API React (React.Component, хуки, createPortal, forwardRef, memo, JSX-рантайм) в эквиваленты Preact, так что сторонние компоненты, написанные под React, на этапе сборки разрешаются в Preact. Собственная страница на npm продвигает библиотеку именно за счёт широкой поддержки React через один алиас, и эта совместимость позволяет переиспользовать экосистему React без переписывания кода.
Пакета preact-compat для установки больше не существует. Официальное руководство по обновлению объясняет, что этот слой когда-то поставлялся отдельно, а затем был перенесён в основной репозиторий для упрощения координации, поэтому всем, кто обновляется, нужно заменить старые импорты и алиасы preact-compat на preact/compat. Пакет без скоупа — тупиковый путь: его репозиторий на GitHub заархивирован и доступен только для чтения с декабря 2021 года, а его страница на npm предлагает его удалить, поскольку Preact X содержит compat по умолчанию. Актуальная стабильная линия — Preact 10.x, версия 11.0.0 находится на стадии release candidate, а не общедоступного релиза; точные номера версий перечислены на странице релизов Preact.
Весь фокус — в алиасинге
Discover how at OpenReplay.com.
Весь механизм — это алиасинг: вы направляете react, react-dom, react-dom/test-utils и react/jsx-runtime на Preact, так что каждый существующий импорт, включая сторонние библиотеки в глубинах node_modules, разрешается в preact/compat вместо React. В коде ваших компонентов не меняется ничего. Инструкция import { useState } from 'react' остаётся в точности такой, как написана; сборщик лишь переопределяет, куда разрешается react.
Четыре канонические записи из руководства Preact по алиасингу React на Preact:
| Путь импорта | Цель алиаса | Зачем |
|---|---|---|
react | preact/compat | Основной API React |
react-dom/test-utils | preact/test-utils | Утилиты для тестирования |
react-dom | preact/compat | DOM-рендерер (должен идти ниже test-utils) |
react/jsx-runtime | preact/jsx-runtime | Автоматическое JSX-преобразование |
Алиасинг только react и react-dom — распространённое упрощение в старых руководствах — оставляет библиотеки, импортирующие JSX-рантайм или react-dom/test-utils, разрешаться в React, что возвращает те самые байты, от которых вы пытались избавиться.
Конфигурация алиасов для каждого инструмента
Vite (рекомендуемый вариант по умолчанию)
С @preact/preset-vite алиасинг выполняется автоматически, и писать resolve.alias вручную не следует. Пресет включает React-алиасы за вас: ими управляет опция reactAliasesEnabled, установленная в true, пока вы её не отключите. Он также настраивает JSX-преобразование.
// vite.config.ts
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
export default defineConfig({
plugins: [preact()], // JSX + react→preact/compat aliasing handled automatically
});
Если вы используете Vite без пресета, добавьте ручной вариант:
export default defineConfig({
resolve: {
alias: {
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat',
'react/jsx-runtime': 'preact/jsx-runtime',
},
},
});
Webpack
В webpack алиас react-dom должен быть указан ниже react-dom/test-utils, иначе более широкое правило react-dom перекроет отображение test-utils, и утилиты для тестирования незаметно разрешатся в неверный модуль.
const config = {
resolve: {
alias: {
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat', // Must be below test-utils
'react/jsx-runtime': 'preact/jsx-runtime',
},
},
};
Rollup
Установите @rollup/plugin-alias и зарегистрируйте его до @rollup/plugin-node-resolve, чтобы подмена путей происходила прежде, чем Rollup разрешит модули.
import alias from '@rollup/plugin-alias';
export default {
plugins: [
alias({
entries: [
{ find: 'react', replacement: 'preact/compat' },
{ find: 'react-dom/test-utils', replacement: 'preact/test-utils' },
{ find: 'react-dom', replacement: 'preact/compat' },
{ find: 'react/jsx-runtime', replacement: 'preact/jsx-runtime' },
],
}),
],
};
Node / Next.js (без алиасов сборщика)
Рантаймы Node игнорируют алиасы сборщика, включая Next.js, поэтому алиас прописывается в package.json с использованием опубликованного пакета @preact/compat. Этот scoped-пакет существует только для того, чтобы встроенному механизму алиасинга npm было на что указывать; его единственная задача — реэкспортировать preact/compat без изменений. Обратите внимание: scoped-пакет @preact/compat — это не мёртвый preact-compat без скоупа.
{
"dependencies": {
"react": "npm:@preact/compat",
"react-dom": "npm:@preact/compat"
}
}
Jest
Jest переопределяет пути модулей через regex-записи в moduleNameMapper:
{
"moduleNameMapper": {
"^react$": "preact/compat",
"^react-dom/test-utils$": "preact/test-utils",
"^react-dom$": "preact/compat",
"^react/jsx-runtime$": "preact/jsx-runtime"
}
}
TypeScript
TypeScript разрешает типы независимо от вашего сборщика, поэтому пропишите пути в tsconfig.json и включите skipLibCheck. Включить skipLibCheck нужно потому, что несколько React-библиотек опираются на типы, которых нет в compat, и полная проверка каждого .d.ts в node_modules завалится на этих объявлениях.
{
"compilerOptions": {
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"react": ["./node_modules/preact/compat/"],
"react/jsx-runtime": ["./node_modules/preact/jsx-runtime"],
"react-dom": ["./node_modules/preact/compat/"],
"react-dom/*": ["./node_modules/preact/compat/*"]
}
}
}
Минимальный разбор миграции
Миграция существующего React-приложения на Preact с современным инструментарием — операция из четырёх шагов:
- Замените зависимости. Удалите
react,react-domи их@types(Preact поставляет собственные типы TypeScript), затем выполнитеnpm install preactиnpm install -D @preact/preset-vite. - Добавьте пресет. Поместите
preact()в список плагинов Vite. Он берёт на себя и JSX-преобразование, и алиасreact → preact/compat, так что любые ручные настройкиesbuild.jsxInject/jsxFactoryиз конфигураций эпохи Vite 2 можно удалить. - Измените точку входа рендеринга. Замените вызов монтирования React DOM на
renderиз Preact:
// Before
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// After
import { render } from 'preact';
render(<App />, document.getElementById('root'));
- Соберите и проверьте размер. Выигрыш здесь и есть суть: ядро Preact плюс
preact/compatзанимают примерно 9,5 КБ min+gzip против величины, близкой к 60 КБ для React 19 плюс React DOM, причём почти весь этот объём приходится на точку входаreact-dom/client. Для приложения, компоненты которого уже разрешаются через compat, такая разница достаётся практически бесплатно.
Когда preact/compat ломается?
Compat покрывает публичный API React, но не его внутренности. Библиотеки, которые обращаются к приватным внутренним путям react-dom или опираются на некоторые из новых API React 19, могут ломаться даже при корректном алиасе, поэтому проверяйте каждую зависимость перед выпуском. Несоответствия типов от библиотек с типизацией под React ожидаемы и решаются через skipLibCheck; следить нужно за отказами во время выполнения, и session replay таких интеграций часто выявляет их как ошибки в консоли, выброшенные изнутри зависимости, а не из вашего собственного кода.
Быстрая предполётная проверка перед внедрением библиотеки:
- Просмотрите пакет через grep на наличие глубоких внутренних импортов
react-dom/— это самый частый признак поломки. - Проверьте API, доступные только в React 19, от которых зависит библиотека; сверяйтесь с текущим релизом Preact, а не полагайтесь на предположения.
- Запустите собственный набор тестов библиотеки с приведённым выше
moduleNameMapperдля Jest, чтобы поймать сбои заранее. - Проведите дымовое тестирование в dev-режиме, отслеживая в консоли ошибки, возникающие внутри зависимости.
Пользователей SSR и фреймворков ждёт отдельный класс проблем: поскольку алиасы сборщика не действуют в Node, Next.js и подобным рантаймам нужен алиас в package.json, а путь ssrLoadModule в Vite может обходить часть конфигурации алиасов — поэтому убедитесь, что и клиент, и сервер разрешаются в preact/compat.
Пропишите алиасы для четырёх записей в вашем инструментарии, прогоните тесты зависимостей через то же отображение — и вы сможете переиспользовать большую часть экосистемы React за малую долю байтов. Честные исключения — библиотеки, связанные с внутренностями React, а не с его публичным API. Начните с добавления @preact/preset-vite в отдельной ветке и измерьте продакшен-бандл до и после.
Часто задаваемые вопросы
В чём разница между @preact/compat и старым пакетом preact-compat?
Scoped-пакет @preact/compat — это живой npm-пакет, реэкспортирующий preact/compat; он используется только для алиасинга react через package.json в Node-рантаймах вроде Next.js, где алиасы сборщика не действуют. Пакет preact-compat без скоупа — это другой, заархивированный пакет, репозиторий которого доступен только для чтения с декабря 2021 года; он был рассчитан на Preact 8.x, а Preact X теперь поставляет compat внутри ядра. Никогда не устанавливайте версию без скоупа.
Нужно ли по-прежнему прописывать resolve.alias вручную, если я использую @preact/preset-vite?
Нет. С @preact/preset-vite алиасинг react и react-dom на preact/compat выполняется за вас и управляется опцией reactAliasesEnabled, которая включена, пока вы её не отключите. Добавление preact() в плагины Vite берёт на себя и JSX-преобразование, и алиасинг, поэтому ручное написание resolve.alias избыточно и может привести к конфликту. Ручной блок из четырёх записей нужен только при запуске Vite без пресета.
Почему после алиасинга в webpack мои тестовые утилиты разрешаются в неверный модуль?
Потому что алиас react-dom указан выше react-dom/test-utils в вашей конфигурации webpack. Webpack сначала сопоставляет более широкое правило react-dom, из-за чего оно перекрывает более специфичное отображение test-utils, и тестовые утилиты незаметно разрешаются в preact/compat вместо preact/test-utils. Исправляется размещением записи react-dom ниже react-dom/test-utils. В Rollup есть похожее правило порядка: подключайте @rollup/plugin-alias перед @rollup/plugin-node-resolve.
Почему React-библиотека ломается, хотя мой алиас preact/compat указан правильно?
Потому что compat отображает публичный API React, но не его внутренности. Библиотеки, импортирующие глубокие внутренние пути react-dom или зависящие от самых новых API React 19, могут падать во время выполнения даже при корректном алиасе. Это проявляется как ошибки в консоли, выброшенные изнутри зависимости, а не из вашего кода. Прежде чем внедрять библиотеку, проверьте её через grep на глубокие импорты react-dom/ и прогоните её собственный набор тестов с moduleNameMapper для Jest.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k