Telegram WebApp SDK на Rust: как я полюбил фронтенд

Предисловие
Это моя первая статья на Хабре. На первый взгляд может показаться, что она хорошо структурирована и грамотна, но не переживайте — Илон Маск до меня не добрался, это не очередной AI-спам.
Просто я такой. Можете зайти в мои open source проекты и посмотреть на код — сами убедитесь. При желании можете связаться и я расскажу про свои загоны. Короче, я педант, люблю красиво. Не надо думать что это "AI spam" — это просто я такой дотошный.
Структура статьи:
Как и зачем я начал делать SDK
Почему Rust на фронтенде имеет смысл
Технические детали и архитектура
Реальный опыт использования
Честные минусы и проблемы
Практические примеры и туториалы
А теперь поехали по тексту.
Как всё началось
Три месяца назад я делал очередной Telegram бот и подумал: "А на *** мне опять тащить npm, React и весь этот зоопарк зависимостей?" У меня бэкенд на Rust, модели данных на Rust, валидаторы на Rust, API на Rust... А фронтенд — на JavaScript. Приходится дублировать типы, поддерживать два разных стека, синхронизировать зависимости.
Это реально бесило.
Я открыл документацию Telegram WebApp API, посмотрел на официальный JS SDK из проекта telegram-apps и решил: "Ладно, попробую обернуть это в Rust через wasm-bindgen. Сколько там может быть? Пара методов?"
Через неделю у меня был рабочий прототип. Ещё через месяц — полноценный SDK на 6300+ строк кода с поддержкой всех методов API. Теперь это опубликованный крейт на crates.io с растущим числом скачиваний.
И знаете что? Это реально работает. Не "работает в теории", не "можно использовать для toy-проектов", а именно работает в продакшене.
Ссылка на проект: github.com/RAprogramm/telegram-webapp-sdk
Зачем вообще Rust на фронтенде?
Все мне говорили:
"Мало библиотек, экосистема сырая"
"Toolchain тяжелый, сборка долгая"
"Всё равно будут мосты с JS — смысла нет"
"Developer Experience не такой удобный"
"Sooo, wha’chu gon git, maaan?"
1. Compile-time безопасность
// Это не скомпилируется
let button_color: u32 = "red"; // ❌ error[E0308]: mismatched types
// А это в JavaScript упадёт в runtime
Telegram.WebApp.MainButton.setColor(undefined); // 💥
Вызовы API Telegram проверяются на этапе компиляции. Нет места для undefined is not a function, Cannot read property of null, и прочих JS-радостей.
2. Единый стек
Одна и та же структура данных работает и на сервере, и на клиенте. Одна кодовая база, одни валидаторы, один набор тестов. Никаких TypeScript-интерфейсов, которые нужно держать в синхронности с бэкендом.
3. Производительность под контролем
WASM предсказуем. Нет garbage collection пауз, нет "внезапных" тормозов из-за Vue/React reconciliation. Всё детерминировано и измеряемо.
4. Независимость от npm
Помните истории про left-pad, про colors и faker? С Rust крейтами такого не происходит. cargo.io имеет политику immutability — опубликованные версии нельзя удалить или изменить.
Что внутри SDK: цифры и факты
Без воды: 6,284 строк кода, 55 файлов, 2,469 строк документации. Покрывает полностью весь Telegram WebApp API версии 9.2. За 3 месяца вышло 15 релизов.
SDK поддерживает:
MainButton, SecondaryButton, BackButton
Theme и Viewport управление
Cloud Storage (для хранения данных в облаке Telegram)
Biometric Authentication
Haptic Feedback
Device Sensors (акселерометр, гироскоп)
Location Manager
Invoice Payments
И ещё куча всего
Архитектура: как это работает
Я не изобретал велосипед. Я взял официальный API и обернул его в типобезопасную Rust-обёртку через wasm-bindgen.
JavaScript API (Telegram.WebApp)
↓
wasm-bindgen биндинги
↓
Rust API (типобезопасный)
↓
Yew/Leptos интеграция
Модули SDK:
telegram_webapp_sdk::webapp— базовые методы, initData, событияmain_button,secondary_button,back_button— управление кнопкамиtheme,viewport— внешний вид и размерыhaptic_feedback— вибрация устройстваcloud_storage— хранилище в облаке Telegrambiometric— биометрическая аутентификацияlocation_manager,accelerometer,gyroscope— датчикиМакросы:
telegram_app!,telegram_page!,telegram_router!Интеграции с Yew и Leptos
Mock-окружение для локальной разработки
Проблема, которая чуть не убила проект
Самый сложный момент был с обработкой событий. В JavaScript это просто:
Telegram.WebApp.onEvent('mainButtonClicked', () => {
console.log("Clicked!");
});
Но в Rust через wasm-bindgen нельзя просто так передать замыкание — оно должно жить достаточно долго. Первая версия приводила к segfault при клике на кнопку. Вот это было весело: "Рустовая безопасность памяти", ага.
// ❌ Это НЕ РАБОТАЛО — замыкание умирает сразу
app.on_event("mainButtonClicked", || {
console::log_1(&"Clicked!".into());
})?;
Проблема в том, что wasm-bindgen требует, чтобы замыкание было 'static. Мне пришлось делать обёртку EventHandle, которая управляет временем жизни через Box::leak() и держит указатель на замыкание.
// ✅ Это РАБОТАЕТ
let handle = app.on_event("mainButtonClicked", |_| {
console::log_1(&"Clicked!".into());
})?;
// handle автоматически управляет памятью
Эта одна проблема заняла 3 дня дебага с gdb, чтения исходников wasm-bindgen и проклятий в адрес lifetime анализатора. Три. Дня.
Но зато теперь это работает надёжно. И когда я это починил, �� понял всю мощь Rust: проблемы жёсткие, но когда решаешь — они решены навсегда. В JS я бы словил этот баг в проде через неделю, потратил бы час на поиски, пофиксил бы... и забыл через месяц, откуда он вообще взялся.
Сравнение: JS SDK vs Rust SDK
Покажу на живом примере. Управление главной кнопкой:
JavaScript (официальный SDK)
Telegram.WebApp.MainButton.setText("Pay $10");
Telegram.WebApp.MainButton.setParams({
color: "#FF0000",
text_color: "#FFFFFF"
});
Telegram.WebApp.MainButton.show();
Telegram.WebApp.MainButton.onClick(() => {
console.log("Processing payment...");
// тут может прилететь что угодно
});
Rust
use telegram_webapp_sdk::main_button::{set_text, show, on_click, set_params};
use telegram_webapp_sdk::webapp::MainButtonParams;
fn setup_payment_button() {
set_text("Pay $10").unwrap();
set_params(&MainButtonParams {
color: Some("#FF0000".into()),
text_color: Some("#FFFFFF".into()),
..Default::default()
}).unwrap();
show().unwrap();
on_click(|| {
console::log_1(&"Processing payment...".into());
// типы проверены на компиляции
}).unwrap();
}
В JavaScript вы можете передать null, неправильный тип, забыть поле — и узнаете об этом в runtime. В Rust код просто не скомпилируется, если вы передадите что-то неверное.
Интеграция с UI-фреймворками
SDK работает с Yew (React-like) и Leptos (Solid.js-like). Вот как выглядит код на Yew:
use telegram_webapp_sdk::yew::{use_telegram_context, BottomButton};
use yew::prelude::*;
#[function_component(PaymentPage)]
fn payment_page() -> Html {
let ctx = use_telegram_context().expect("Telegram context");
html! {
<div>
<p>{ format!("User ID: {:?}", ctx.init_data.user) }</p>
<BottomButton
text="Pay $10"
color="#FF0000"
on_click={Callback::from(|_| { /* обработка */ })}
/>
</div>
}
}
Если вы предпочитаете Leptos — он тоже поддерживается из коробки.
Mock окружение для разработки
Одна из проблем разработки Telegram WebApp — нужно запускать приложение внутри Telegram. Дебажить неудобно.
Поэтому я добавил mock-окружение. Создаёте конфиг файл с тестовыми данными пользователя и темы, подключаете фичу mock — и всё, можно запускать приложение локально в браузере без Telegram. Mock автоматически эмулирует весь Telegram.WebApp API.
Это сэкономило мне недели времени на разработку.
Макросы для удобства
Чтобы не писать boilerplate, я добавил макросы telegram_app!, telegram_page! и telegram_router!. Они автоматически инициализируют WebApp, загружают mock в debug-режиме, настраивают роутинг и запускают приложение. Меньше шаблонного кода — больше пользы.
Что не получилось (честно о минусах)
Не всё идеально. Вот с чем я столкнулся:
1. Sourcemaps плохие
В JavaScript у вас отличные sourcemaps, и вы видите точную строку ошибки. В WASM sourcemaps работают плохо. Дебажить сложнее.
Решение: я использую console_error_panic_hook для более читаемых panic-сообщений.
2. Нет Hot Module Replacement
В Vite/Webpack есть HMR — изменения применяются мгновенно. В Rust нужно пересобирать WASM. Это занимает ~5-10 секунд.
Решение: использую trunk с --watch, но это всё равно медленнее чем HMR.
3. Размер бинаря
Даже после оптимизации WASM весит больше чем минифицированный JS. Минимальное Yew приложение ~150KB (gzipped ~50KB) против React ~80KB (gzipped ~30KB).
Решение: для больших приложений разница нивелируется. Плюс есть wasm-opt для дополнительной оптимизации. Но да, "Hello World" на Rust тяжелее.
4. Экосистема UI компонентов
В React есть Material-UI, Ant Design, Chakra. В Rust есть... ну, yew-components и пара библиотек. Всё.
Решение: пишу компоненты сам или использую CSS-фреймворки (Tailwind работает отлично). В итоге даже хорошо — меньше зависимостей, больше контроля.
5. IDE поддержка
rust-analyzer иногда тупит на сложных макросах. Автокомплит не всегда работает идеально.
Решение: терпение и cargo check.
Производительность: цифры
Детальных бенчмарков не делал, но вот что заметил:
Сборка prod: React+TS ~45 сек, Rust+Yew ~12 сек
Размер бандла: React ~245 KB (gzip), Rust ~156 KB (gzip)
Runtime ошибки (первая неделя в проде): React — 3 бага, Rust — 0
Самое главное — все баги ловятся на компиляции, не в проде.
Пример реального использования
Использую SDK в нескольких проектах. Один из них — бот для управления подписками: список активных подписок, оплата через Telegram Invoice API, история транзакций, биометрическая аутентификация для критичных операций.
Стек: Rust везде. Backend (Axum), Frontend (Yew + telegram-webapp-sdk), PostgreSQL, Docker + Fly.io.
Что работает отлично: единые типы между бэком и фронтом, валидация на compile-time, Cloud Storage для кеша, Haptic Feedback для UX.
Что было сложно: настройка CI/CD для WASM, интеграция с Telegram Payments, дебаг в Safari на iOS (sourcemaps бесполезны).
Как начать использовать
Добавляете в Cargo.toml:
telegram-webapp-sdk = { version = "0.2", features = ["yew", "mock"] }
Пишете компонент:
use telegram_webapp_sdk::yew::use_telegram_context;
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let ctx = use_telegram_context().expect("Telegram context");
html! {
<div>
<h1>{ "Hello, Telegram!" }</h1>
<p>{ format!("User: {:?}", ctx.init_data.user) }</p>
</div>
}
}
Собираете через trunk serve — и всё работает.
Полная документация в README и на docs.rs.
Что дальше?
Продолжаю использовать SDK в продакшене и активно развиваю проект.
Планы:
Поддержка Telegram Stars API
Улучшить mock-окружение (визуальный конфигуратор)
Больше примеров и туториалов
Интеграция с Dioxus
Если вы пишете на Rust, делаете Telegram боты, устали от npm-зоопарка и хотите типобезопасность на фронтенде — попробуйте: telegram-webapp-sdk = "0.2"
Ссылки
crates.io: https://crates.io/crates/telegram-webapp-sdk
docs.rs: https://docs.rs/telegram-webapp-sdk
Telegram WebApp API: https://core.telegram.org/bots/webapps
Заключение
Rust на фронтенде — это не "будущее", это настоящее. Да, экосистема ещё развивается. Да, есть шероховатости. Но если вы хотите типобезопасность, единый стек с бэкендом, предсказуемую производительность и меньше runtime-багов — WASM + Rust это реальная альтернатива JavaScript-стеку.
Я написал SDK, опубликовал его, использую в проде. И это работает. Не в теории, а прямо сейчас.
Если вы дочитали до конца — поздравляю! Значит, AI не вложил в в��с дурные мысли о том, что Rust на фронтенде — это безумие 😄
Кстати, изначально хотел сделать краткую структурированную статейку, чисто информативную. Но видимо не ценится такое... Поэтому получилось как получилось — с подробностями, примерами и честным рассказом о проблемах.
Просто попробуйте. Предсказуемый фронтенд на Rust — это круто. Возможно, вам зайдёт так же, как зашло мне.
Буду рад PR, issue и просто фидбеку в GitHub репозитории. Особенно если найдёте баги — их точно ещё есть :-)
Спасибо за внимание!