Files
transcriber/docs/research/spa-framework.md
T
av 54ca4c0e50 docs: фреймворком приложения выбран Vue 3 с роутером 5 и сборкой Vite
- заведены записка разведки docs/research/spa-framework.md со сравнением Svelte,
  Vue и React на одном экране и решение ADR-2026-08-11-spa-on-vue
- docs/conventions/web-ui.md переписан под Vue: компоненты, маршруты, состояние,
  обращение к API и показ ошибок
- закрыт вопрос «Приложение» в docs/architecture.md, уточнена задача spa-skeleton
2026-08-11 14:51:55 +03:00

140 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Фреймворк приложения: 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 Б), что и подтверждает
одинаковость экрана.
- **Четыре маршрута** — тот же экран плюс три заглушки и переходы между ними:
столько экранов у цели [web-access](../../tasks/items/web-access.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 пакета в
установке — число, а не суждение о том, как часто они ломаются.