- ссылки на tasks/ROADMAP.md переведены на BACKLOG.md и на openspec/specs, упоминания целей — на задачи, которые эту работу делают; - судьи документации нашли восемь расхождений: Purpose спеки pipeline объявлял неописанным то, что уже нормирован пятью требованиями, вид времени в конвенции спорил со схемой, а квоты, шесть часов и отказ от Web Push жили сразу в двух документах без ссылки друг на друга; - два числа получили провенанс: 259 200 запросов в сутки и потолок в шесть часов теперь ведут к записке разведки, а не читаются как замер.
141 lines
11 KiB
Markdown
141 lines
11 KiB
Markdown
# Фреймворк приложения: Svelte, Vue и React на одном экране
|
||
|
||
Отвечает на вопрос разведки `spa-framework-choice`: какой фреймворк берём под
|
||
приложение на четыре экрана, которое собирается в статику, вшивается в бинарник
|
||
через `go:embed` и ставится на телефон.
|
||
|
||
Кандидатов назвал владелец: Svelte, Vue и React, все с Vite. Мера тоже названа им
|
||
— размер собранной статики, простота вшивания и цена шага сборки в гейте, а не
|
||
популярность. Серверную отрисовку и надстройки над фреймворками — SvelteKit,
|
||
Nuxt, Next — не рассматривали: конвенция
|
||
[../conventions/web-ui.md](../conventions/web-ui.md) уже требует статику в
|
||
бинарнике, а все три надстройки по умолчанию ждут процесс Node рядом.
|
||
|
||
## Как снималось
|
||
|
||
Дата замеров — 2026-08-11. Всё считал в каталоге вне репозитория, который удалён
|
||
вместе с песочницей. Node 24.18.0, npm 11.16.0, Vite 8.2.1, TypeScript 6.0.3.
|
||
|
||
- **Каркасы** — `npm create vite@latest <имя> -- --template svelte-ts|vue-ts|react-ts`.
|
||
Из каждого удалил демонстрационные картинки и компонент-счётчик, чтобы в сборку
|
||
попал только пробный экран.
|
||
- **Пробный экран** один и тот же по смыслу: список записей, опрос состояния
|
||
незавершённых раз в две секунды, полоса ошибки, пустое состояние, разбор даты.
|
||
60 строк на Vue, 64 на Svelte, 69 на React; стили — один и тот же файл на 15
|
||
правил, и в сборке он у всех троих совпал до байта (883 Б), что и подтверждает
|
||
одинаковость экрана.
|
||
- **Четыре маршрута** — тот же экран плюс три заглушки и переходы между ними:
|
||
столько экранов заводит задача
|
||
[spa-skeleton](../../tasks/items/spa-skeleton.md). Роутеры
|
||
`svelte-spa-router` 5.1.1, `vue-router` 5.2.0 и 4.6.4, `react-router` 8.3.0.
|
||
- **Размеры** — `stat -c%s` и `gzip -9c | wc -c` по файлам `dist/`. Числа Vite в
|
||
своём выводе печатает по другому уровню сжатия, поэтому в таблицах ниже стоят
|
||
мои.
|
||
- **Установка** — `npm ci --cache <свой пустой каталог>`; у каждого каркаса кэш
|
||
свой, иначе первый прогон скачивает общие пакеты за остальных.
|
||
- **Сборка** — `npm run build` трижды подряд с удалением `dist` и кэша Vite,
|
||
в таблице лучшее из трёх. Числа сняты на машине разработчика, не в гейте.
|
||
- **Совместимость `svelte-spa-router` со Svelte 5** взята из его описания в
|
||
реестре, а не проверена: `peerDependencies` объявляет `svelte: ^5.0.0`.
|
||
|
||
## Числа
|
||
|
||
| Мера | Svelte 5.56.8 | Vue 3.5.41 | React 19.2.8 |
|
||
| --- | --- | --- | --- |
|
||
| Пробный экран, скрипт | 35 598 Б / 13 931 Б gzip | 62 493 / 24 372 | 191 797 / 59 679 |
|
||
| Четыре маршрута с роутером | 45 053 / **17 314** | 86 890 / **33 326** | 228 750 / **72 402** |
|
||
| Стили, у всех один файл | 883 / 474 | 883 / 474 | 883 / 474 |
|
||
| Файлов в `dist/` | 3 плюс значок | то же | то же |
|
||
| Пакетов в установке, каркас | 49 | 48 | 27 |
|
||
| Пакетов с роутером | 51 | 84 (роутер 5) / 50 (роутер 4) | 29 |
|
||
| `node_modules` с роутером | 74 МБ | 92 МБ | 91 МБ |
|
||
| Установка с пустым кэшем | 7,4 с | 9,1 с | 9,4 с |
|
||
| `npm ci` с тёплым кэшем | 0,44 с | 0,41 с | 0,39 с |
|
||
| Сборка и проверка типов | 0,32 с плюс 1,16 с | 1,08 с | 0,81 с |
|
||
|
||
Пакеты считал так: имена первого уровня в `node_modules` плюс имена второго
|
||
уровня внутри областей `@…`. В `package-lock.json` записей больше — 74, 72 и 69
|
||
у каркасов, — потому что он перечисляет двоичные сборки Rollup и oxlint под все
|
||
платформы, а ставится одна.
|
||
|
||
Проверка типов у Vue и React входит в `npm run build` (`vue-tsc -b && vite build`
|
||
и `tsc -b && vite build`), у Svelte вынесена в отдельную команду `npm run check`
|
||
и в сборке не участвует — отсюда две цифры в последней строке.
|
||
|
||
## Что оказалось одинаковым и потому ничего не выбирает
|
||
|
||
- **Вшивание в бинарник.** У всех троих `dist/` — это `index.html`, один файл
|
||
скрипта и один файл стилей с хешем в имени плюс значок. Ни один не кладёт
|
||
файлов, начинающихся с точки или подчёркивания, поэтому `go:embed` берёт
|
||
каталог обычной строкой, без `all:`.
|
||
- **Node в гейте и в образе.** Шаг сборки статики нужен всем троим одинаково: на
|
||
машине разработчика, в `task gate` и слоем сборки в `Dockerfile`.
|
||
- **Установка на телефон.** `vite-plugin-pwa` 1.3.0 от фреймворка не зависит: в
|
||
его `peerDependencies` стоит Vite, и ни одного фреймворка там нет.
|
||
- **Цена шага сборки.** Секунда с небольшим у всех троих, и на фоне сборки Go и
|
||
`golangci-lint` в гейте это не различие.
|
||
|
||
Различает единственное — **размер того, что скачивает телефон**, и он расходится
|
||
вчетверо.
|
||
|
||
## Кандидаты
|
||
|
||
### Svelte
|
||
|
||
Компилятор, а не библиотека времени выполнения: в собранный файл попадает почти
|
||
только свой код, отсюда 17 314 Б на четыре экрана — вчетверо меньше React.
|
||
Реактивность и хранилище состояния встроены, третьей библиотеки под них не нужно.
|
||
|
||
Против: своего роутера у Svelte нет, а `svelte-spa-router` держит один человек.
|
||
Проверка типов идёт отдельной командой, то есть в гейте это второй шаг.
|
||
|
||
### Vue
|
||
|
||
Библиотека с официальным роутером и официальным хранилищем состояния. 33 326 Б на
|
||
четыре экрана — вдвое легче React и вдвое тяжелее Svelte. Разметка отделена от
|
||
кода однофайловым компонентом, документация переведена на русский.
|
||
|
||
Пятая версия роутера тянет в установку 34 пакета сверх четвёртой (84 против 50):
|
||
в неё встроена сборочная надстройка под маршруты по файлам. На собранный файл это
|
||
не влияет — 33 326 Б против 33 848 Б у четвёртой версии, то есть пятая даже чуть
|
||
легче, — и **надстройка не обязательна**: замер шёл на своей таблице маршрутов
|
||
через `createRouter`, ни один плагин Vite для этого не регистрировался.
|
||
|
||
Пятая версия — стабильная, а не предварительная: 5.0.0 вышла 29 января 2026,
|
||
текущая 5.2.0 — 15 июля, метка `latest` стоит на ней.
|
||
|
||
### React
|
||
|
||
Экосистема больше, чем у двух других, — числом я её не мерил, — а пакетов в
|
||
установке меньше всех: 27. Всё остальное против: 72 402 Б на четыре экрана, и ниже этого пола он не опускается, потому что
|
||
пол задаёт сама библиотека. Роутер, хранилище состояния и работа с запросами —
|
||
третьими библиотеками, каждая со своим сроком жизни.
|
||
|
||
## Что решено и от чего отказались
|
||
|
||
Решение — **Vue с роутером пятой версии**, записано в
|
||
[ADR-2026-08-11-spa-on-vue](../adr/ADR-2026-08-11-spa-on-vue.md).
|
||
|
||
Отвергнуты:
|
||
|
||
- **Svelte** — легче Vue вдвое, но своего роутера не имеет, а тот, что есть,
|
||
держит один человек. Владелец выбрал экосистему, которая переживёт проект, а не
|
||
минимальный размер: 33 КБ на телефоне не отличаются от 17 КБ на глаз, а
|
||
брошенная зависимость отличается;
|
||
- **React** — вчетверо тяжелее Svelte и вдвое тяжелее Vue, а взамен даёт
|
||
экосистему, которой приложению на четыре экрана не на что потратиться: чужих
|
||
компонентов оно не берёт, весь показ данных — список, форма загрузки и текст.
|
||
|
||
## Чего разведка не узнала
|
||
|
||
- **Как числа изменятся на настоящих экранах.** Мерил один экран и три заглушки;
|
||
загрузка файла с полосой хода, форма настроек и таблица расхода вырастут у всех
|
||
трёх. Переносится отношение, а не абсолютные значения.
|
||
- **Цену готовых наборов компонентов.** Не мерил вовсе, а именно она способна
|
||
удвоить собранный файл.
|
||
- **Во что обходится шаг сборки в образе.** Слой Node в `Dockerfile` не
|
||
собирался: время сборки образа и его вес после добавления слоя неизвестны.
|
||
Замер сделает `spa-skeleton`, которая этот слой и пишет.
|
||
- **Сколько живёт сборочная надстройка роутера пятой версии.** 34 пакета в
|
||
установке — число, а не суждение о том, как часто они ломаются.
|