WEB-COMPONENTS SERVER-SENT-EVENTS SSE REAL-TIME PROGRESSIVE-ENHANCEMENT FRONTEND WEB-DEVELOPMENT JAVASCRIPT HTML WEB-API USER-EXPERIENCE PERFORMANCE ARCHITECTURE DATA-STREAMING MODERN-WEB CLIENT-SIDE BACKEND EVENT-DRIVEN

Membangun Web Components Reaktif dengan Server-Sent Events (SSE) dan Progressive Enhancement

⏱️ 14 menit baca
👨‍💻

Membangun Web Components Reaktif dengan Server-Sent Events (SSE) dan Progressive Enhancement

1. Pendahuluan

Di era aplikasi web modern yang serba cepat, pengguna mengharapkan antarmuka yang selalu up-to-date tanpa perlu me-refresh halaman secara manual. Bayangkan aplikasi stock ticker, live score, atau dashboard yang menampilkan data real-time. Bagaimana kita bisa membangun UI yang reaktif dan modular, namun tetap tangguh dan berkinerja tinggi?

Banyak developer beralih ke framework besar dengan manajemen state kompleks untuk mencapai reaktivitas. Namun, ada cara yang lebih ringan dan berbasis standar web untuk mencapai hal serupa, terutama untuk komponen UI yang spesifik: yaitu dengan menggabungkan Web Components sebagai fondasi UI modular, Server-Sent Events (SSE) untuk aliran data real-time dari server, dan Progressive Enhancement untuk memastikan pengalaman pengguna yang solid di berbagai kondisi.

Artikel ini akan memandu Anda memahami dan membangun Web Components yang secara reaktif memperbarui dirinya sendiri berdasarkan data yang dikirim dari server melalui SSE, sambil tetap menjamin user experience dasar yang baik berkat Progressive Enhancement. Mari kita selami!

2. Web Components: Fondasi UI Modular Anda

Web Components adalah seperangkat standar web yang memungkinkan Anda membuat elemen HTML kustom yang reusable, terenkapsulasi, dan dapat berinteraksi. Tiga teknologi utamanya adalah:

Dalam konteks reaktivitas, Web Components adalah kandidat sempurna. Mereka bisa menjadi “komponen pasif” yang hanya menampilkan data yang diberikan, atau “komponen aktif” yang juga bisa mengambil data sendiri. Dengan SSE, kita akan membuat mereka menjadi lebih “aktif” dalam mendengarkan perubahan data dari server.

📌 Keuntungan Web Components:

3. Server-Sent Events (SSE): Aliran Data Satu Arah dari Server

Ketika kita berbicara tentang komunikasi real-time dari server ke client, seringkali yang terlintas adalah WebSockets. Namun, untuk skenario di mana client hanya perlu menerima pembaruan data dari server (komunikasi satu arah), Server-Sent Events (SSE) adalah pilihan yang lebih ringan dan efisien.

SSE menggunakan koneksi HTTP persisten untuk mengirimkan aliran data berbasis teks dari server ke browser. Browser secara otomatis akan mencoba menyambung kembali jika koneksi terputus.

💡 Bagaimana SSE Bekerja?

  1. Client membuat objek EventSource yang menunjuk ke endpoint SSE di server.
  2. Server merespons dengan Content-Type: text/event-stream.
  3. Server mengirimkan data dalam format khusus (misalnya, event: update\ndata: {"price": 150.25}\n\n).
  4. Client mendengarkan event ini menggunakan event listener pada objek EventSource.

Contoh Server SSE Sederhana (Node.js dengan Express):

// server.js
const express = require('express');
const cors = require('cors'); // Untuk mengizinkan CORS jika frontend di domain berbeda

const app = express();
app.use(cors());

app.get('/events/stock-price', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    // Jika menggunakan Nginx/proxy, perlu header ini untuk memastikan stream tidak di-buffer
    res.setHeader('X-Accel-Buffering', 'no');

    let price = 100.00;

    // Kirim harga awal
    res.write(`data: ${JSON.stringify({ price: price.toFixed(2), timestamp: new Date().toLocaleTimeString() })}\n\n`);

    const intervalId = setInterval(() => {
        price += (Math.random() - 0.5) * 2; // Naik turun secara acak
        const data = {
            price: price.toFixed(2),
            timestamp: new Date().toLocaleTimeString()
        };
        // Format SSE: data: [JSON string]\n\n
        res.write(`data: ${JSON.stringify(data)}\n\n`);
        console.log('Sent:', data);
    }, 2000); // Kirim update setiap 2 detik

    // Tangani penutupan koneksi oleh client
    req.on('close', () => {
        console.log('Client disconnected');
        clearInterval(intervalId);
        res.end();
    });
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`SSE Server running on http://localhost:${PORT}`);
});

✅ SSE lebih mudah diimplementasikan daripada WebSockets untuk skenario satu arah, dan browser secara native menangani reconnection yang terputus.

4. Menjembatani SSE dan Web Components

Sekarang, mari kita buat Web Component yang mendengarkan event dari server SSE kita. Kita akan membuat komponen <stock-ticker> yang menampilkan harga saham yang terus diperbarui.

1. Desain Web Component (stock-ticker.js):

// stock-ticker.js
class StockTicker extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' }); // Menggunakan Shadow DOM
        this.source = null; // Untuk menyimpan instance EventSource
    }

    // Metode ini dipanggil saat komponen ditambahkan ke DOM
    connectedCallback() {
        this.render();
        this.connectSSE();
    }

    // Metode ini dipanggil saat komponen dihapus dari DOM
    disconnectedCallback() {
        this.disconnectSSE();
    }

    render(price = this.getAttribute('initial-price') || 'N/A', timestamp = 'Loading...') {
        this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: block;
                    font-family: Arial, sans-serif;
                    border: 1px solid #ccc;
                    padding: 10px;
                    margin: 10px 0;
                    border-radius: 5px;
                    background-color: #f9f9f9;
                }
                .price {
                    font-size: 2em;
                    font-weight: bold;
                    color: #333;
                }
                .timestamp {
                    font-size: 0.8em;
                    color: #777;
                }
                .label {
                    font-size: 1em;
                    color: #555;
                    margin-bottom: 5px;
                }
            </style>
            <div class="label">Harga Saham Terkini:</div>
            <div class="price">Rp <span id="current-price">${price}</span></div>
            <div class="timestamp">Terakhir diperbarui: <span id="last-update">${timestamp}</span></div>
        `;
    }

    connectSSE() {
        // Ambil URL SSE dari atribut, atau gunakan default
        const sseUrl = this.getAttribute('sse-url') || 'http://localhost:3000/events/stock-price';
        
        // Pastikan hanya ada satu koneksi EventSource
        if (this.source) {
            this.disconnectSSE();
        }

        this.source = new EventSource(sseUrl);

        this.source.onopen = () => {
            console.log('SSE connection opened.');
        };

        this.source.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                const priceElement = this.shadowRoot.getElementById('current-price');
                const timestampElement = this.shadowRoot.getElementById('last-update');
                
                if (priceElement) priceElement.textContent = data.price;
                if (timestampElement) timestampElement.textContent = data.timestamp;
                
            } catch (error) {
                console.error('Failed to parse SSE data:', error);
            }
        };

        this.source.onerror = (error) => {
            console.error('SSE error:', error);
            // Browser akan mencoba reconnect otomatis, tapi kita bisa log atau menampilkan pesan
        };
    }

    disconnectSSE() {
        if (this.source) {
            this.source.close();
            console.log('SSE connection closed.');
            this.source = null;
        }
    }
}

// Daftarkan Custom Element
customElements.define('stock-ticker', StockTicker);

2. Gunakan di HTML (index.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components Reaktif dengan SSE</title>
    <script type="module" src="stock-ticker.js"></script>
</head>
<body>
    <h1>Aplikasi Real-time Stock Ticker</h1>
    
    <!-- Web Component kita -->
    <stock-ticker initial-price="100.00" sse-url="http://localhost:3000/events/stock-price"></stock-ticker>

    <p>Ini adalah contoh bagaimana Web Component dapat menerima pembaruan data secara real-time dari server menggunakan Server-Sent Events.</p>

    <!-- Contoh komponen lain yang tidak reaktif -->
    <div>
        <h2>Berita Terkini</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
    </div>
</body>
</html>

Sekarang, jika Anda menjalankan server.js dan membuka index.html di browser, Anda akan melihat harga saham di dalam <stock-ticker> secara otomatis diperbarui setiap 2 detik!

5. Progressive Enhancement: Pengalaman Dasar yang Solid

Progressive Enhancement adalah filosofi desain web yang menekankan pada penyediaan konten dan fungsionalitas inti kepada semua pengguna, terlepas dari kemampuan browser mereka atau koneksi jaringan. Kemudian, secara bertahap, fungsionalitas yang lebih canggih (seperti JavaScript atau API modern) ditambahkan untuk browser yang mendukung, meningkatkan user experience mereka.

Dalam contoh <stock-ticker> kita, Progressive Enhancement dapat diterapkan dengan cara berikut:

  1. HTML Inti: Pastikan elemen <stock-ticker> memiliki data awal yang bermakna langsung di HTML. Ini bisa melalui atribut seperti initial-price yang sudah kita gunakan, atau bahkan dengan markup di dalam slot jika komponen lebih kompleks.
  2. CSS: Pastikan styling dasar (misalnya, tata letak dan warna) tetap terlihat baik bahkan jika JavaScript tidak berjalan.
  3. JavaScript (Web Component + SSE): Ini adalah lapisan enhancement yang menambahkan reaktivitas real-time. Jika JavaScript gagal dimuat atau dinonaktifkan, pengguna masih melihat harga saham awal yang bermakna.

Contoh Penerapan di <stock-ticker>:

Dalam kode stock-ticker.js kita:

    render(price = this.getAttribute('initial-price') || 'N/A', timestamp = 'Loading...') {
        // ... (HTML markup)
    }

Dan di index.html:

<stock-ticker initial-price="100.00" sse-url="http://localhost:3000/events/stock-price"></stock-ticker>

Jika JavaScript tidak aktif, elemen <stock-ticker> akan tetap ada di DOM, dan shadowRoot akan diisi dengan Rp 100.00 dan Terakhir diperbarui: Loading.... Ini memberikan baseline experience yang bagus. Begitu JavaScript dimuat, connectSSE() akan dipanggil, dan data akan mulai mengalir secara real-time.

🎯 Manfaat Progressive Enhancement:

6. Tips Praktis dan Best Practices

⚠️ Error Handling dan Reconnection Logic

Browser secara native menangani reconnection untuk EventSource ketika koneksi terputus. Namun, Anda tetap perlu menangani error yang mungkin terjadi pada data (misalnya, JSON tidak valid) atau error koneksi di sisi server.

📉 Skalabilitas SSE

Meskipun SSE lebih ringan dari WebSockets, koneksi HTTP persisten tetap mengonsumsi sumber daya server. Untuk aplikasi skala besar:

🔄 Kapan Menggunakan SSE vs. WebSockets?

📦 Manajemen State Internal Web Component

Untuk komponen yang lebih kompleks, Anda mungkin perlu manajemen state internal yang lebih canggih.

✅ Pastikan Atribut Semantik

Gunakan atribut HTML yang relevan untuk memberikan hint kepada komponen tentang bagaimana ia harus berperilaku atau dari mana ia harus mengambil data, seperti sse-url atau initial-price. Ini meningkatkan deklaratifitas dan Progressive Enhancement.

Kesimpulan

Menggabungkan Web Components, Server-Sent Events, dan Progressive Enhancement adalah strategi yang kuat untuk membangun aplikasi web modern yang modular, reaktif, dan tangguh. Anda mendapatkan manfaat dari:

Pendekatan ini memungkinkan Anda untuk menambahkan fitur real-time ke aplikasi Anda tanpa ketergantungan pada framework besar, menjaga bundle size tetap kecil, dan meningkatkan performa serta skalabilitas. Mulailah bereksperimen dengan pola ini dan saksikan aplikasi Anda menjadi lebih dinamis dan responsif!

🔗 Baca Juga