Русский
Фронтенд: $mol
Фронтенд trip2g построен на $mol — реактивном UI-фреймворке Дмитрия Карловского. Он покрывает два отдельных интерфейса: панель администратора для владельцев сайта и виджеты на default-шаблоне (вход, поиск, пейволл и др.).
Что такое $mol
$mol — фреймворк компонентов с реактивной системой на основе файберов. Ключевые отличия от React/Vue:
- Нет виртуального DOM — компоненты привязаны к реальным DOM-узлам напрямую
- Весь прикладной код пишется синхронно; асинхронные операции прозрачно интегрируются через
$mol_wire_sync - Компоненты описываются декларативно в
.view.tree-файлах; поведение — в.view.ts - Встроенная i18n: строки с маркером
@вытягиваются в файлы*.view.tree.locale=ru.json - Часть монорепозитория MAM — модули резолвятся по имени директории
Фреймворк небольшой (~10k строк), модульный — билд-тул написан на нём же.
Структура директорий
assets/ui/ ← симлинк → ../mam/trip2g/
admin/ ← дерево компонентов панели администратора
auth/ ← вход / выход
user/ ← виджеты для читателей
search/ ← поиск по сайту
space/ ← пространство подписки
graphql/ ← сгенерированные типы + mol-адаптер
monkeypatch/ ← патчи рантайма (локаль, URL)
assets/ui/ — симлинк в рабочее пространство MAM. Все компоненты имеют префикс $trip2g_.
Панель администратора
Панель ($trip2g_admin) оборачивает флоу аутентификации ($trip2g_auth) и после входа рендерит каталог из нескольких разделов. Разделы: дашборд, пользователи, контент, telegram, монетизация, интеграции, SEO, система. Навигация управляется URL через $mol_state_arg.
Каждый раздел следует одному шаблону CRUD:
| Директория | Назначение |
|---|---|
catalog/ |
Таблица всех записей |
show/ |
Детальный просмотр одной записи |
update/ |
Форма редактирования |
create/ |
Форма создания (где применимо) |
button/ |
Кнопки действий (удалить, запустить и т.п.) |
В подвале — переключатель языка ($mol_locale_select, ru/en) и переключатель светлой/тёмной темы ($mol_lights_toggle).
Виджеты на страницах сайта
Встраиваются дефолтным шаблоном в обычные страницы сайта:
| Компонент | Назначение |
|---|---|
$trip2g_auth |
Вход через email → код; кнопки OAuth |
$trip2g_user_search |
Полнотекстовый и векторный поиск по сайту |
$trip2g_user_paywall |
Пейволл / управление доступом |
$trip2g_user_signinwall |
Запрос входа перед пейволлом |
$trip2g_user_favoritenote |
Переключатель избранного |
Флоу аутентификации: пользователь вводит email → сервер отправляет 6-значный код → пользователь вводит код → JWT сохраняется в куки. OAuth (Google, GitHub) поддерживается как альтернатива.
Виджет поиска отправляет запрос к GraphQL-операции siteSearch, рендерит результаты с подсвеченными фрагментами и показывает предупреждение при векторном поиске, если точных совпадений нет.
Интеграция с GraphQL
На бэкенде используется gqlgen, на фронтенде — кастомный кодогенератор (graphqlmol.js). Генератор читает schema.graphqls и queries.ts и создаёт:
- Полные TypeScript-типы для каждого query/mutation/subscription
- Адаптер
$trip2g_graphql_request, интегрирующийся с реактивной системой файберов $mol
Паттерн использования — всегда вызывать фабрику вне класса, чтобы объект запроса был общим для всех экземпляров компонента:
// На уровне модуля: создаём переиспользуемый реактивный запрос
const data_request = $trip2g_graphql_request(/* GraphQL */ `
query AdminBackgroundQueues {
admin {
allBackgroundQueues {
nodes { id pendingCount stopped }
}
}
}
`)
export class $trip2g_admin_backgroundqueue_catalog extends ... {
@$mol_mem
data(reset?: null) {
// Вызов data_request() участвует в реактивном графе mol.
// При первом вызове запускает HTTP-запрос; при повторных — возвращает
// кешированный результат. reset=null инвалидирует кеш.
const res = data_request()
return $trip2g_graphql_make_map(res.admin.allBackgroundQueues.nodes)
}
}
$trip2g_graphql_make_map конвертирует массив узлов в Map<id, node>, по которой могут реактивно итерироваться каталог-компоненты.
После изменения schema.graphqls или internal/db/queries.sql нужно перегенерировать типы:
npm run graphqlgen
Локальная разработка
MAM-билдер резолвит модули $trip2g_* по совпадению имён директорий в рабочем пространстве. Настройка:
git clone https://github.com/hyoo-ru/mam.git
ln -s /path/to/trip2g/assets/ui mam/trip2g
cd mam
npm start trip2g/admin # или trip2g/user, trip2g/forms и т.д.
Конфликт имён GraphQL-переменных: MAM воспринимает каждый токен вида $name в GraphQL-запросе как потенциальную ссылку на модуль и пытается найти директорию с таким именем. Для переменных $filter, $id, $input нужно заранее создать пустые заглушки:
cd mam
mkdir filter id input limit format fragment note
Это известная особенность — директории не используются при сборке, но их отсутствие вызывает ошибки резолвинга.
Docker-сборка
Сборка $mol-фронтенда занимает ~65с в Docker. mam хранит кэш компиляции только в памяти процесса — каждый новый запуск начинается с нуля. Дискового кэша нет, инкрементальной пересборки между запусками npm start нет.
Проверенные подходы, которые не помогают:
- Разбивка
COPYна отдельныйexternaldeps-слой — mam пересобирает всё независимо от Docker layer cache - BuildKit cache mounts на
-директории с артефактами — экономят ~9с в лучшем случае, mam игнорирует собственный вывод при перезапуске
Единственное архитектурное решение — держать mam в watch mode (где in-memory кэш сохраняется) и собирать фронтенд вне Docker, затем копировать готовые артефакты через COPY. До реализации этого ~65с компиляции — базовая стоимость сборки.
Монкипатчи
assets/ui/monkeypatch/monkeypatch.ts применяет два патча в рантайме:
1. Сброс кеша локализации
JSON-файлы локалей (web.locale=ru.json) отдаются с долгоживущими заголовками кеша. Патч добавляет в URL локали хеш JS-бандла:
web.locale=ru.json?h=<bundle-hash>
Хеш считывается из параметра ?h= URL текущего тега <script src="...?h=...">. При изменении бандла меняется хеш — браузеры запрашивают свежие файлы локализации.
2. Удаление хвостового #! из ссылок
$mol использует hashbang-URL (#!) для клиентской маршрутизации. Когда $mol покрывает лишь часть страницы (виджеты, не полноценное SPA), каждая внутренняя ссылка получает хвостовой #! — видимый в адресной строке. Патч переопределяет $mol_state_arg.make_link, убирая хвостовой #! и формируя чистые URL вида https://trip2g.com/ru/user/protocol вместо https://trip2g.com/ru/user/protocol#!.
$trip2g_monkeypatch_apply() нужно вызвать один раз при инициализации приложения, до рендера любого компонента.