# Место в бюджете JS статей

Дата: 14.10.2026
Статус: принята автономно. Владелец работает в режиме «Все автономно». Issue #425.

## Зачем

Бюджет JS статьи 100 КБ gzip (`apps/web/scripts/js-budget.ts`). Самая тяжёлая страница, `en/disease/aortic-stenosis/`, после пресетов фибрилляции желудочков и фокусной предсердной тахикардии весит около 101,7 из 102,4 тысячи байт: запаса меньше 700 байт, и следующая функция часов сердца не пройдёт проверку.

## Что выяснилось

Замеры реальной сборкой (`gzip -9`, байты на этой странице):

- react-dom 64,9 КБ, пакет часов сердца 8,2 КБ, остров 3D-модели 4,4 КБ, HeartClockDemo 4,4 КБ, react 3,3 КБ, HeartSounds 2,5 КБ, `disease.js` 1,9 КБ.
- Сборщик кладёт весь пакет `@heart-atlas/heart-clock` в один общий chunk, поэтому статья грузит и код, который нужен только встраиванию, странице `/heart-clock` или 3D-сцене.
- Модуль, который служит точкой входа острова, отдаёт клиенту все свои экспорты, в том числе списки ключей строк, нужные только при сборке страницы.

## Серия PR

| PR | Что | Экономия |
| --- | --- | --- |
| 1 | Модули островов экспортируют только компонент: ключи строк часов и плеера тонов переезжают в `messages.ts`, остров 3D подключается через `@heart-atlas/viewer/island`, а не через barrel с `viewerMessageKeys`. Поведение не меняется. | 926 Б (замер) |
| 2 | Геометрия болезней (`nearestDistances`, `zoneWeight` и прочее) переезжает из `viewer/src/disease.ts` в `disease-geometry.ts`, который импортирует только `Scene.tsx`. | около 770 Б (замер прототипа) |
| 3 | Пресеты считаются при сборке: `clockChoices()` (`heart-clock/choices.ts`) отдаёт острову готовые параметры, пределы, отведение и группу, а HeartClockDemo знает только тип `ClockChoice` (`clock-choice.ts`). Встраивание ЭКГ получает варианты из сборки, а таблицу пресетов для настроек из адреса берёт из своего модуля `embed-settings.ts`, которого статьи не грузят. Плеер тонов берёт параметры часов по умолчанию: пресет нормы их не меняет. `hasPulse` переезжает в `params.ts`, в пакете часов `"sideEffects": false`. | 1588 Б (замер) |

После трёх PR запас 3975 Б (замер). Цена PR 3: готовые варианты всех пресетов лежат в HTML страницы часов и встраивания (около 1,7 КБ gzip), а встраивание грузит и таблицу пресетов, чтобы читать адрес. На страницах статей вариантов два-три.

## Отложено

- Ленивые `cycleState` и гемодинамика для 3D (около 1,4 КБ): нужна экспериментальная настройка разбиения rolldown. Вернуться, если запаса после серии не хватит.
- Перевод островов на Preact (около −57 КБ): смена архитектуры на несколько PR и мост для react-three-fiber. Отдельный issue, решение владельца.
- Отклонено: ленивая отрисовка фибрилляции желудочков (438 Б, ломает синхронный API часов), короткие тексты ошибок (теряется диагностика), `modulePreload: false` и поздняя гидрация (бюджет считает все острова, 0 Б).

## Проверки

Каждый PR: тесты, линт, типы, e2e, `pnpm js-budget` с замером до и после в описании PR.
