Skip to content

2. Installation & Setup

RAprogramm edited this page Nov 4, 2025 · 2 revisions

Chapter 2: Installation & Setup

Professional guide to setting up a production-ready Telegram Mini App with telegram-webapp-sdk.

Prerequisites

1. Rust Toolchain

Install Rust 1.90.0 or later (MSRV for telegram-webapp-sdk 0.3.0):

# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Verify installation
rustc --version  # Should be 1.90.0 or later
cargo --version

Reference: Official Rust Installation Guide

2. WebAssembly Target

Add the wasm32-unknown-unknown target for browser-based WebAssembly:

rustup target add wasm32-unknown-unknown

This target is required for all Rust WASM applications. Verify installation:

rustup target list --installed | grep wasm32

3. Trunk Build Tool

Trunk is the official recommended bundler for Yew and Rust WASM applications:

cargo install --locked trunk

Note: Installation compiles from source and may take 5-10 minutes.

Verify installation:

trunk --version

Reference: Trunk Documentation

4. Telegram Bot

Create a bot via @BotFather:

  1. Send /newbot to BotFather
  2. Follow the prompts to choose a name and username
  3. Save your bot token (format: 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw)
  4. Keep the token secure - never commit it to version control

Reference: Telegram Bot API Documentation

Project Setup

Step 1: Create Project Structure

# Create new Cargo project
cargo new --bin my-telegram-app
cd my-telegram-app

# Create additional directories
mkdir -p src/components
mkdir -p src/pages
mkdir -p assets

Standard Yew Project Structure:

my-telegram-app/
├── Cargo.toml              # Project manifest
├── Trunk.toml              # Trunk configuration (optional)
├── index.html              # HTML entry point
├── assets/                 # Static assets
│   ├── styles.css
│   └── images/
├── src/
│   ├── main.rs            # Application entry point
│   ├── components/        # Reusable components
│   │   └── mod.rs
│   ├── pages/             # Page components
│   │   └── mod.rs
│   └── lib.rs             # Optional library exports
└── dist/                  # Build output (generated)

Reference: Yew Project Structure

Step 2: Configure Dependencies

Edit Cargo.toml:

[package]
name = "my-telegram-app"
version = "0.1.0"
edition = "2021"
rust-version = "1.90"

# Optimize for WebAssembly
[profile.release]
opt-level = "z"          # Optimize for size
lto = true               # Enable link-time optimization
codegen-units = 1        # Better optimization (slower compile)
strip = true             # Strip symbols from binary
panic = "abort"          # Smaller code without unwinding

[dependencies]
# Telegram WebApp SDK
telegram-webapp-sdk = { version = "0.3", features = ["yew", "macros"] }

# Yew Framework (CSR = Client-Side Rendering)
yew = { version = "0.21", features = ["csr"] }

# Utilities
wasm-bindgen = "0.2"
web-sys = "0.3"
gloo = "0.11"           # Utility library for web APIs
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Optional: For HTTP requests
reqwasm = { version = "0.5", optional = true }

# Optional: For state management
yewdux = { version = "0.10", optional = true }

[dev-dependencies]
wasm-bindgen-test = "0.3"

[features]
default = []
http = ["reqwasm"]
state = ["yewdux"]

Reference: Yew Dependencies

Step 3: Configure Trunk

Create Trunk.toml in project root:

[build]
# Target directory for build output
target = "dist"

# Base URL for assets (change for production)
public_url = "/"

# Release optimizations
[build.release]
minify = "on_release"

# Serve configuration
[serve]
port = 8080
address = "127.0.0.1"
# Enable automatic page reload on changes
open = false
# Proxy API requests (optional)
# [[proxy]]
# backend = "http://localhost:3000/api/"

# Build hooks
[[hooks]]
stage = "pre_build"
command = "echo"
command_arguments = ["Starting build..."]

[[hooks]]
stage = "post_build"
command = "echo"
command_arguments = ["Build complete!"]

# wasm-opt optimization (requires binaryen installed)
[[hooks]]
stage = "post_build"
command = "sh"
command_arguments = [
    "-c",
    "if command -v wasm-opt >/dev/null 2>&1; then wasm-opt -Oz -o dist/*.wasm dist/*.wasm; fi"
]

Reference: Trunk Configuration

Step 4: Create HTML Entry Point

Create index.html in project root:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <meta name="theme-color" content="#000000" />
    <meta name="description" content="Telegram Mini App" />

    <title>My Telegram App</title>

    <!-- CRITICAL: Telegram WebApp SDK must load before WASM -->
    <script src="https://telegram.org/js/telegram-web-app.js"></script>

    <!-- Trunk will inject WASM bundle here -->
    <link data-trunk rel="rust" data-wasm-opt="z" />

    <!-- Optional: Load CSS -->
    <link data-trunk rel="css" href="assets/styles.css" />

    <!-- Optional: Preload assets -->
    <!-- <link data-trunk rel="copy-dir" href="assets/images" /> -->
</head>
<body>
    <!-- Yew will mount here -->
    <noscript>
        <p>This application requires JavaScript to run.</p>
    </noscript>
</body>
</html>

Key Points:

  • data-trunk rel="rust" - Tells Trunk to compile and inject WASM
  • data-wasm-opt="z" - Optimize WASM for size
  • Telegram script loads before WASM bundle

Reference: Trunk Assets

Step 5: Create Application Code

src/main.rs

use telegram_webapp_sdk::{telegram_app, telegram_router};
use yew::prelude::*;

mod components;
mod pages;

use pages::{Home, Profile};

/// Main application component with routing
#[telegram_app(auto_init)]
#[function_component(App)]
fn app() -> Html {
    telegram_router! {
        "/" => Home,
        "/profile" => Profile,
    }
}

/// Application entry point
fn main() {
    // Initialize panic hook for better error messages
    #[cfg(debug_assertions)]
    console_error_panic_hook::set_once();

    // Render the application
    yew::Renderer::<App>::new().render();
}

src/components/mod.rs

// Export all components
pub mod header;
pub mod footer;

pub use header::Header;
pub use footer::Footer;

src/components/header.rs

use telegram_webapp_sdk::TelegramWebApp;
use yew::prelude::*;

#[function_component(Header)]
pub fn header() -> Html {
    let webapp = TelegramWebApp::new();

    let theme = webapp.theme_params();
    let bg_color = theme.header_bg_color
        .as_ref()
        .unwrap_or(&"#000000".to_string())
        .clone();

    html! {
        <header style={format!("background-color: {}", bg_color)}>
            <h1>{ "My Telegram App" }</h1>
        </header>
    }
}

src/pages/mod.rs

// Export all pages
pub mod home;
pub mod profile;

pub use home::Home;
pub use profile::Profile;

src/pages/home.rs

use telegram_webapp_sdk::{telegram_page, TelegramWebApp};
use yew::prelude::*;

#[telegram_page(path = "/")]
#[function_component(Home)]
pub fn home() -> Html {
    let webapp = TelegramWebApp::new();

    // Get user information
    let user_name = webapp
        .init_data_unsafe()
        .user
        .as_ref()
        .map(|u| u.first_name.clone())
        .unwrap_or_else(|| "Guest".to_string());

    // Setup main button
    use_effect_with((), move |_| {
        webapp.main_button().set_text("Continue");
        webapp.main_button().show();
        webapp.main_button().on_click(Callback::from(|_| {
            // Handle button click
        }));

        || ()
    });

    html! {
        <div class="page">
            <h1>{ format!("Hello, {}!", user_name) }</h1>
            <p>{ "Welcome to your Telegram Mini App" }</p>
        </div>
    }
}

src/pages/profile.rs

use telegram_webapp_sdk::{telegram_page, TelegramWebApp};
use yew::prelude::*;

#[telegram_page(path = "/profile")]
#[function_component(Profile)]
pub fn profile() -> Html {
    let webapp = TelegramWebApp::new();
    let user = webapp.init_data_unsafe().user.clone();

    html! {
        <div class="page">
            if let Some(user) = user {
                <div>
                    <h1>{ "Profile" }</h1>
                    <p>{ format!("ID: {}", user.id) }</p>
                    <p>{ format!("Name: {}", user.first_name) }</p>
                    if let Some(username) = user.username {
                        <p>{ format!("Username: @{}", username) }</p>
                    }
                </div>
            } else {
                <p>{ "No user data available" }</p>
            }
        </div>
    }
}

assets/styles.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background-color: var(--tg-theme-bg-color, #ffffff);
    color: var(--tg-theme-text-color, #000000);
}

.page {
    padding: 20px;
    max-width: 600px;
    margin: 0 auto;
}

header {
    padding: 16px;
    text-align: center;
    border-bottom: 1px solid var(--tg-theme-hint-color, #cccccc);
}

button {
    background-color: var(--tg-theme-button-color, #0088cc);
    color: var(--tg-theme-button-text-color, #ffffff);
    border: none;
    border-radius: 8px;
    padding: 12px 24px;
    font-size: 16px;
    cursor: pointer;
}

button:hover {
    opacity: 0.9;
}

Development Workflow

Local Development with Mock

For testing without deploying, add mock feature:

[dependencies]
telegram-webapp-sdk = { version = "0.3", features = ["yew", "macros", "mock"] }

Initialize mock in main.rs:

#[cfg(feature = "mock")]
fn init_mock_env() {
    use telegram_webapp_sdk::mock::{init_mock, MockConfig, MockUser};

    init_mock(MockConfig {
        user: Some(MockUser {
            id: 12345,
            first_name: "Test User".into(),
            last_name: Some("Developer".into()),
            username: Some("testdev".into()),
            language_code: Some("en".into()),
            is_premium: Some(false),
            ..Default::default()
        }),
        platform: "web",
        version: "7.0",
        ..Default::default()
    });
}

fn main() {
    #[cfg(feature = "mock")]
    init_mock_env();

    yew::Renderer::<App>::new().render();
}

Run Development Server

# Start with hot reload
trunk serve

# Or with custom port
trunk serve --port 3000

# With release optimizations (slower build, faster runtime)
trunk serve --release

Reference: Trunk Serve Command

Build for Production

# Build optimized bundle
trunk build --release

# Build with custom public URL
trunk build --release --public-url "https://your-domain.com/app/"

# Output will be in dist/ directory
ls -lh dist/

Testing with Real Telegram

Telegram requires HTTPS. Use tunneling tools:

Option 1: cloudflared (Recommended)

# Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/

# Terminal 1: Start dev server
trunk serve

# Terminal 2: Create tunnel
cloudflared tunnel --url http://localhost:8080

You'll receive a URL like: https://random-words.trycloudflare.com

Option 2: ngrok

# Install: https://ngrok.com/download

# Terminal 1: Start dev server
trunk serve

# Terminal 2: Create tunnel
ngrok http 8080

You'll receive a URL like: https://abc123.ngrok-free.app

Configure Telegram Bot

1. Set WebApp URL in BotFather

1. Open @BotFather in Telegram
2. Send: /mybots
3. Select your bot
4. Bot Settings → Menu Button → Edit menu button
5. Choose "Edit menu button URL"
6. Enter your URL (tunnel or production)
7. Done

2. Test Your App

1. Open your bot in Telegram
2. Click the menu button (bottom-left)
3. Your WebApp opens in Telegram

Advanced Configuration

Enable Source Maps for Debugging

In Trunk.toml:

[build]
# Enable source maps in debug builds
dist = "dist"

[build.debug]
minify = "never"

Proxy Backend API

In Trunk.toml:

[[proxy]]
# Proxy /api requests to backend
backend = "http://localhost:3000/api/"

Then in your app:

// Requests to /api/users will proxy to http://localhost:3000/api/users
reqwasm::http::Request::get("/api/users")
    .send()
    .await?;

Reference: Trunk Proxy Configuration

Optimize WASM Size

Install wasm-opt:

# macOS
brew install binaryen

# Ubuntu/Debian
sudo apt-get install binaryen

# Windows (scoop)
scoop install binaryen

Trunk will automatically use it during release builds.

Troubleshooting

Error: "Telegram is not defined"

Cause: Telegram script not loaded before WASM

Solution: Ensure index.html has:

<script src="https://telegram.org/js/telegram-web-app.js"></script>

Before:

<link data-trunk rel="rust" />

Error: wasm-bindgen version mismatch

Cause: CLI version differs from library version

Solution:

# Check versions
grep wasm-bindgen Cargo.lock | head -1

# Install matching CLI
cargo install wasm-bindgen-cli --version 0.2.XX

Reference: wasm-bindgen Docs

Error: Failed to fetch WASM

Cause: Incorrect public URL or CORS issues

Solution:

# Rebuild with correct URL
trunk build --release --public-url "https://your-actual-domain.com/"

# Check server CORS headers
curl -I https://your-domain.com/app.wasm

Large WASM Bundle Size

Solutions:

  1. Enable all optimizations in Cargo.toml
  2. Use wasm-opt -Oz
  3. Remove unused dependencies
  4. Use cargo-bloat to find large dependencies:
cargo install cargo-bloat
cargo bloat --release --target wasm32-unknown-unknown

Reference: Rust WASM Optimization

Next Steps

Your project is now set up. Learn the core concepts:

Chapter 3: Core Concepts

References

Clone this wiki locally