WEB-COMPONENTS FRONTEND UI-UX DESIGN-SYSTEM FRAMEWORK-AGNOSTIC JAVASCRIPT REACT VUE ANGULAR INTEROPERABILITY WEB-DEVELOPMENT COMPONENT-LIBRARY SHADOW-DOM CUSTOM-ELEMENTS MAINTAINABILITY

Web Components untuk Design System Framework-Agnostic: Fondasi UI yang Konsisten di Berbagai Framework

⏱️ 19 menit baca
👨‍💻

Web Components untuk Design System Framework-Agnostic: Fondasi UI yang Konsisten di Berbagai Framework

1. Pendahuluan

Di dunia pengembangan web yang dinamis, kita sering dihadapkan pada pilihan framework JavaScript yang melimpah: React, Vue, Angular, Svelte, dan masih banyak lagi. Setiap framework memiliki kelebihan dan ekosistemnya sendiri. Namun, bagaimana jika Anda perlu membangun antarmuka pengguna (UI) yang konsisten di seluruh aplikasi Anda, bahkan jika aplikasi-aplikasi tersebut dibangun dengan framework yang berbeda? Atau, bagaimana jika Anda ingin memastikan komponen UI inti Anda tetap bisa digunakan di masa depan, terlepas dari tren framework yang datang dan pergi?

Di sinilah Web Components bersinar terang. 💡

Web Components menawarkan solusi elegan untuk membangun komponen UI yang framework-agnostic – artinya, mereka dapat digunakan di framework mana pun, atau bahkan tanpa framework sama sekali. Ini adalah kunci untuk menciptakan Design System yang benar-benar kuat dan tahan lama. Bayangkan Web Components sebagai “LEGO bricks” standar yang bisa Anda gunakan untuk membangun apa saja, tidak peduli merek mainan lain yang Anda miliki.

Artikel ini akan membawa Anda menyelami mengapa Web Components adalah pilihan ideal untuk design system Anda, bagaimana cara mengintegrasikannya dengan framework populer, serta tips praktis untuk menghadapi tantangan yang mungkin muncul. Mari kita bangun fondasi UI yang konsisten dan masa depan-bukti!

2. Mengapa Web Components untuk Design System Anda?

Design System adalah kumpulan prinsip, pedoman, dan komponen UI yang dapat digunakan kembali untuk membangun produk digital yang konsisten dan berkualitas tinggi. Tantangan utamanya adalah bagaimana memastikan komponen-komponen ini dapat diimplementasikan dan digunakan secara seragam di berbagai proyek yang mungkin menggunakan teknologi frontend yang berbeda.

Web Components menjawab tantangan ini dengan beberapa keunggulan:

Bayangkan Anda memiliki tim yang mengerjakan dua aplikasi berbeda: satu dengan React, yang lain dengan Vue. Dengan Web Components, tim Anda bisa berbagi komponen tombol, modal, atau kartu yang sama persis, memastikan branding dan pengalaman pengguna yang seragam. Ini adalah efisiensi dan konsistensi yang nyata. ✅

3. Membangun Web Component Sederhana: Fondasi “LEGO Brick” Kita

Sebelum membahas integrasi, mari kita buat Web Component sederhana. Kita akan menggunakan vanilla JavaScript untuk demonstrasi, tetapi Anda bisa menggunakan library seperti Lit atau Stencil untuk pengalaman pengembangan yang lebih nyaman.

Kita akan membuat komponen my-button yang bisa menerima properti variant (primary, secondary) dan label.

<!-- 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 Demo</title>
    <style>
        body { font-family: sans-serif; padding: 20px; }
        my-button { margin-right: 10px; }
    </style>
</head>
<body>
    <h1>Web Components di Aksi</h1>
    <my-button label="Klik Saya!" variant="primary"></my-button>
    <my-button label="Batal" variant="secondary"></my-button>

    <script src="my-button.js"></script>
</body>
</html>
// my-button.js
class MyButton extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' }); // Mengaktifkan Shadow DOM
        this.button = document.createElement('button');
        this.shadowRoot.appendChild(this.button);
    }

    // Dipanggil saat komponen ditambahkan ke DOM
    connectedCallback() {
        this.render(); // Render awal

        // Menambahkan event listener
        this.button.addEventListener('click', this._handleClick.bind(this));
    }

    // Dipanggil saat komponen dihapus dari DOM
    disconnectedCallback() {
        this.button.removeEventListener('click', this._handleClick.bind(this));
    }

    // Mengamati perubahan atribut
    static get observedAttributes() {
        return ['label', 'variant'];
    }

    // Dipanggil saat atribut yang diamati berubah
    attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue !== newValue) {
            this.render();
        }
    }

    _handleClick() {
        // Memicu custom event
        this.dispatchEvent(new CustomEvent('buttonClick', {
            detail: { label: this.getAttribute('label'), variant: this.getAttribute('variant') },
            bubbles: true, // Event akan 'naik' melalui DOM
            composed: true // Event dapat melewati Shadow DOM boundary
        }));
        alert(`Tombol "${this.getAttribute('label')}" (${this.getAttribute('variant')}) diklik!`);
    }

    render() {
        const label = this.getAttribute('label') || 'Button';
        const variant = this.getAttribute('variant') || 'primary';

        // Styling dan markup di dalam Shadow DOM
        this.shadowRoot.innerHTML = `
            <style>
                button {
                    padding: 10px 20px;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    font-size: 16px;
                    transition: background-color 0.3s ease;
                }
                .primary {
                    background-color: #007bff;
                    color: white;
                }
                .primary:hover {
                    background-color: #0056b3;
                }
                .secondary {
                    background-color: #6c757d;
                    color: white;
                }
                .secondary:hover {
                    background-color: #545b62;
                }
            </style>
            <button class="${variant}">${label}</button>
        `;
        this.button = this.shadowRoot.querySelector('button'); // Update referensi button
        this.button.addEventListener('click', this._handleClick.bind(this)); // Re-attach event listener
    }
}

customElements.define('my-button', MyButton); // Mendaftarkan custom element

Dalam contoh ini:

4. Strategi Integrasi Web Components dengan Framework Frontend Populer

Meskipun Web Components adalah standar, setiap framework memiliki cara tersendiri dalam berinteraksi dengan DOM dan properti. Memahami nuansa ini adalah kunci integrasi yang mulus.

4.1. React

React dikenal dengan Virtual DOM-nya. Web Components berinteraksi langsung dengan Real DOM. Ini bisa menimbulkan beberapa keanehan jika tidak ditangani dengan benar.

// React Component (misal: MyReactApp.js)
import React, { useRef, useEffect } from 'react';
import './my-button.js'; // Pastikan Web Component terdaftar

function MyReactApp() {
    const buttonRef = useRef(null);

    useEffect(() => {
        const handleButtonClick = (event) => {
            console.log('Web Component Clicked (from React):', event.detail);
            alert(`React menangkap klik dari WC: ${event.detail.label}`);
        };

        if (buttonRef.current) {
            buttonRef.current.addEventListener('buttonClick', handleButtonClick);
        }

        return () => {
            if (buttonRef.current) {
                buttonRef.current.removeEventListener('buttonClick', handleButtonClick);
            }
        };
    }, []);

    // Untuk properti non-string (misal: objek), Anda perlu set via ref
    useEffect(() => {
        if (buttonRef.current) {
            // Contoh: buttonRef.current.someObjectProp = { id: 1 };
        }
    }, []);

    return (
        <div>
            <h2>Integrasi Web Component di React</h2>
            <my-button
                ref={buttonRef}
                label="Klik dari React"
                variant="primary"
                // Atribut lain akan diteruskan sebagai string
                data-info="react-component"
            ></my-button>
            <my-button label="Batal dari React" variant="secondary"></my-button>
        </div>
    );
}

export default MyReactApp;

📌 Tips React: Jika Anda sering menggunakan Web Components di React, pertimbangkan untuk membungkusnya dalam komponen React tipis yang menangani event listener dan properti kompleks, sehingga komponen React utama Anda tetap bersih.

4.2. Vue

Vue secara default memiliki dukungan yang lebih baik untuk Web Components dibandingkan React. Vue akan mencoba meneruskan properti sebagai atribut jika memungkinkan dan memiliki penanganan event yang lebih fleksibel.

<!-- Vue Component (misal: MyVueApp.vue) -->
<template>
    <div>
        <h2>Integrasi Web Component di Vue</h2>
        <my-button
            label="Klik dari Vue"
            variant="primary"
            @buttonClick="handleButtonClick"
        ></my-button>
        <my-button
            label="Batal dari Vue"
            variant="secondary"
            @buttonClick="handleButtonClick"
        ></my-button>
    </div>
</template>

<script>
// Pastikan Web Component terdaftar di entry point aplikasi Vue Anda
// import './my-button.js'; 

export default {
    name: 'MyVueApp',
    methods: {
        handleButtonClick(event) {
            console.log('Web Component Clicked (from Vue):', event.detail);
            alert(`Vue menangkap klik dari WC: ${event.detail.label}`);
        }
    },
    // Jika Anda menggunakan Vue CLI atau Vite, mungkin perlu konfigurasi untuk mengabaikan Web Components
    // Contoh di vue.config.js:
    // module.exports = {
    //   chainWebpack: config => {
    //     config.module
    //       .rule('vue')
    //       .use('vue-loader')
    //       .tap(options => {
    //         options.compilerOptions = {
    //           ...options.compilerOptions,
    //           isCustomElement: tag => tag.startsWith('my-')
    //         };
    //         return options;
    //       });
    //   }
    // };
};
</script>

📌 Tips Vue: Pastikan Vue dikonfigurasi untuk mengenali custom element Anda (misal, dengan isCustomElement di compilerOptions jika menggunakan build tool). Ini mencegah Vue mencoba merender komponen tersebut sebagai komponen Vue sendiri.

4.3. Angular

Angular juga memiliki dukungan yang baik untuk Web Components, terutama melalui skema kustom.

// Angular Component (misal: app.component.ts)
import { Component, OnInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

// Pastikan Web Component terdaftar di main.ts atau app.module.ts
// import './my-button.js';

@Component({
  selector: 'app-root',
  template: `
    <h2>Integrasi Web Component di Angular</h2>
    <my-button
      label="Klik dari Angular"
      variant="primary"
      (buttonClick)="handleButtonClick($event)"
    ></my-button>
    <my-button
      label="Batal dari Angular"
      variant="secondary"
      (buttonClick)="handleButtonClick($event)"
    ></my-button>
  `,
  // Penting: tambahkan CUSTOM_ELEMENTS_SCHEMA
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppComponent implements OnInit {
  title = 'angular-web-components-demo';

  ngOnInit() {
    // Anda bisa memuat Web Component secara dinamis di sini jika perlu
  }

  handleButtonClick(event: CustomEvent) {
    console.log('Web Component Clicked (from Angular):', event.detail);
    alert(`Angular menangkap klik dari WC: ${event.detail.label}`);
  }
}

📌 Tips Angular: Kunci untuk integrasi Angular adalah menambahkan CUSTOM_ELEMENTS_SCHEMA ke modul Anda. Ini memberi tahu Angular untuk tidak mencoba mengkompilasi tag yang tidak dikenalnya sebagai komponen Angular, melainkan menganggapnya sebagai custom element.

5. Tantangan dan Best Practices dalam Mengelola Design System dengan Web Components

Meskipun Web Components menawarkan banyak keunggulan, ada beberapa pertimbangan dan praktik terbaik yang perlu diperhatikan:

5.1. Server-Side Rendering (SSR) dan Static Site Generation (SSG)

Web Components pada dasarnya beroperasi di sisi klien. Untuk aplikasi yang mengandalkan SSR/SSG untuk performa awal dan SEO, ini bisa menjadi tantangan.

5.2. Styling dan Theming

Meskipun Shadow DOM menyediakan enkapsulasi, Anda mungkin perlu cara untuk mengkustomisasi gaya komponen dari luar (theming).

5.3. Tooling dan Build Process

Membangun Web Components yang siap produksi memerlukan tooling yang tepat, terutama untuk:

Library seperti Lit atau Stencil sangat membantu dalam hal ini dengan menyediakan CLI dan toolchain yang sudah terkonfigurasi.

5.4. Komunikasi Data yang Kompleks

Untuk komunikasi data yang lebih kompleks antara Web Components dan framework induk, atau antar Web Components itu sendiri, pertimbangkan:

6. Contoh Nyata: Membangun Design System dengan Web Components

Bayangkan Anda bekerja di perusahaan dengan beberapa produk:

Dengan Design System yang dibangun menggunakan Web Components, Anda bisa memiliki:

  1. Repo design-system-web-components: Berisi semua komponen inti (Button, Modal, Card, Input, dll.) yang ditulis sebagai Web Components (mungkin menggunakan Lit).
  2. Publikasi: Komponen-komponen ini dipublikasikan ke npm sebagai package (@my-company/ui-components).
  3. Integrasi:
    • Produk A (jQuery): Cukup impor JavaScript Web Components, lalu gunakan tag <my-button> di HTML.
    • Produk B (React): Impor package, bungkus Web Components dalam komponen React tipis untuk DX yang lebih baik jika perlu, lalu gunakan.
    • Produk C (Vue): Impor package, konfigurasikan Vue untuk mengenali custom element, lalu gunakan.

Hasilnya? 🎯 Konsistensi visual dan perilaku di seluruh produk, waktu pengembangan lebih cepat, dan tim developer lebih bahagia karena tidak perlu menulis ulang komponen yang sama berulang kali. Ini adalah kekuatan sejati dari Web Components.

Kesimpulan

Web Components adalah standar web yang kuat dan sering diremehkan, terutama dalam konteks membangun Design System yang framework-agnostic. Mereka menawarkan solusi yang elegan untuk masalah konsistensi UI dan reusabilitas komponen di seluruh ekosistem frontend yang beragam.

Meskipun ada beberapa tantangan dalam hal integrasi dengan framework dan penanganan SSR, manfaat jangka panjang dari komponen yang tahan banting di masa depan dan dapat digunakan di mana saja jauh lebih besar. Dengan pemahaman yang tepat tentang bagaimana Web Components bekerja dan strategi integrasi yang cerdas, Anda dapat membangun fondasi UI yang kokoh, fleksibel, dan siap menghadapi evolusi teknologi web di masa depan. Mari mulai membangun dengan “LEGO bricks” standar web ini!

🔗 Baca Juga