Архитектурный бунт: пишем Client-Side Data-Driven квест без фреймворков и воюем с ИИ за SOLID
Текстовый ретро-экспрессионизм: как разработать масштабируемый движок, оживляя книги-игры Р.Л. Стайна на ванильном TypeScript.
Дисклеймер: Про Хабр и линтер на стероидах
Эта статья должна была выйти на Хабре. Но местная модерация Песочницы устроила параноидальный рейд: вместо возврата текста в черновики для правок, они полностью снесли мой двухнедельный труд под предлогом «использования ИИ для литредактирования». Доказывать очевидное вахтёрам я не стала, хладнокровно потребовала удалить аккаунт и ушла на vc.ru.
Реакция поддержки на мое требование снести профиль оказалась бесценной. Ребята мгновенно осознали масштаб факапа, запутались в собственных CRM-ссылках и судорожно побежали спасать мой текст на Google Диск.
Заявляю прямо: Фактура, архитектура, код и коммиты в этом проекте — полностью мои. Но я ценю своё время, поэтому причёсывать текст, структурировать мысли и расставлять запятые мне помогал ИИ-ассистент. Если у кого-то от этого факта срабатывает триггер — это ваши проблемы. А мы переходим к мясу.
Проблема кривых EPUB и Р.Л. Стайн
В детстве многие зачитывались интерактивными книгами-квестами Р. Л. Стайна («Ужастики-2» от издательства «Росмэн», 2002–2006 гг.). Логика там была классической: прочитал блок текста — сделай выбор. «Если откроешь сундук — перейди на страницу 42. Если побежишь к выходу — на страницу 11».
Решив перечитать серию, я столкнулась с техническим адом. Бумажные оригиналы — дефицит, а электронные версии свёрстаны настолько криво, что постоянные прыжки по внутренним ссылкам в читалках превращают процесс в пытку.
Идея напросилась сама: если нет удобного интерфейса, нужно написать его под себя. Изначально планировалась простая веб-страничка с переходами. Однако в процессе проектирования данные потребовали строгой структуры, абстракции — чистоты, а проект за две недели плотной автономной работы эволюционировал в полноценный Client-Side Data-Driven SPA-движок на чистом Native TypeScript. Без единого фреймворка, внешних зависимостей и легаси-костылей.
Архитектурные ограничения и паттерны
Проект проектировался с прицелом на максимальную автономность (Client-Side) и управляемость данными (Data-Driven). Чтобы не превратить кодовую базу в кашу, я заложила три архитектурных правила:
- Чистый MVP (Model-View-Presenter): Полная изоляция слоёв. View ничего не знает о данных, Model ничего не знает об интерфейсе. Presenter управляет логикой вслепую, коммуницируя со слоями строго через абстрактные интерфейсы. Это прямая реализация принципов SOLID, в частности DIP (Dependency Inversion Principle).
- Data-driven подход & In-Memory Cache: Ноль хардкода. Весь контент квестов вынесен в JSON-конфиги. Глобальный config.json содержит лишь реестр доступных книг, а движок выкачивает структуру конкретного произведения один раз при старте. Переключение страниц и книг происходит мгновенно, в оперативной памяти, без повторных сетевых запросов и дебильных спиннеров.
- Паттерн Repository для персистентности: Изначально планировался простой хелпер для работы с памятью, но логика быстро потребовала выделения полноценного слоя репозитория (BookStorage). Он инкапсулирует работу с LocalStorage, суффиксами сохранений и состоянием инвентаря пользователя (проверка наличия предметов для открытия заблокированных страниц). Presenter не имеет понятия, куда и как пишутся данные.
Вот так выглядит корневой JSON-реестр, управляющий каталогом доступных интерактивных миров:
Вот реализация класса BookStorage, изолирующего персистентное состояние и инвентарь игрока:
Принцип KISS и разделение контента (Демо vs Локал)
Разделение прав на контент стало главным триггером для оптимизации TCO проекта. В публичный деплой на Vercel улетела исключительно легальная ознакомительная демо-версия. Полный массив книг со всеми ветвлениями крутится только локально на моей машине.
Публичная модалка срабатывает как триггер окончания демо-сценария. Сброс игрового состояния в демо-режиме решён строго по принципу KISS — банальной перезагрузкой страницы при закрытии этого модального окна. Никакого оверхеда, сложных стейт-менеджеров и тонны кода для очистки памяти. Система просто возвращается в исходное состояние.
Стерильный ретро-канон без мобильной разметки
Проект принципиально проектировался исключительно под Desktop. Никакого мобильного адаптива здесь нет и не планировалось. Продукт должен ощущаться как старая печатная книга.
Для этого были приняты два жестких ограничения:
- Шрифт: Базовый кастомный шрифт Venom Mincho физически не имеет начертания bold. Весь интерфейс завязан на фиксированную толщину символов, что отсекает любой современный UI-дизайн.
- Графика: Для иконок собран производительный SVG-спрайт через тег <use>. Это исключает лишнюю нагрузку на браузер и рендеринг векторов через JS на лету.
Битва с ИИ за SOLID и ошибка 403
Самым изматывыющим процессом в эти две недели стало управление ИИ-ассистентом. Я использовала нейросеть для черновой работы и генерации бойлерплейта под TypeScript, но это была регулярная война за архитектуру.
Языковые модели по своей природе ленивы. ИИ постоянно пытался скатиться в процедурную кашу, навязать спагетти-код, галлюцинировал методами и упорно игнорировал мою готовую утилиту `getStorageItem`, пытаясь связать модули напрямую и нарушить инкапсуляцию.
Мне приходилось регулярно бить алгоритм по виртуальным рукам жесткими промптами:
- «Нет, мы не импортируем модель во View. Презентер управляет этим вслепую».
- «Нет, этот метод должен быть скрыт за контрактом интерфейса, а не торчать наружу».
- «Включай DIP, мы пишем расширяемую систему, а не формочку-однодневку».
В ходе этой дискуссии нейросеть под конец, кажется, сломалась от моих требований к SOLID. В один момент, когда мы проектировали сложную логику проверки предметов в инвентаре, ИИ просто обрушил контекст чата и выдал ошибку 403 Forbidden. Но архитектурное эго победило — структуру приложения я отстояла, код остался чистым, а каплинг модулей — слабым.
Выводы по TCO и техдолгу
Разработка кастомного MVP-движка с нуля без фреймворков — лучший способ прокачать понимание системного анализа, работы DOM, кэширования и управления памятью в оперативной памяти. Когда ты сам контролируешь каждый байт и архитектурную границу, у тебя нет скрытого техдолга, который обычно приносят за собой тяжелые фреймворки. Две недели работы полностью себя оправдали.
Проект успешно задеплоен на Vercel и автономен.
Поиграть и оценить ретро-атмосферу (демо-версия):
Посмотреть на чистый TS-код и SOLID-архитектуру на GitHub:
Буду рада циничному инженерному фидбеку и разбору архитектуры в комментариях!