1. Pendahuluan
Di dunia pengembangan web yang dinamis, kita sering dihadapkan pada pilihan teknologi yang beragam. Di satu sisi, ada framework frontend modern seperti React, Vue, dan Angular yang menawarkan ekosistem kaya dan pola pengembangan yang efisien. Di sisi lain, ada Web Components, standar web native yang memungkinkan kita membangun komponen UI yang reusable dan framework-agnostic.
Pertanyaannya, bisakah kita memanfaatkan kedua dunia ini secara bersamaan? Jawabannya adalah ya! Mengintegrasikan Web Components dengan framework frontend modern bukan hanya mungkin, tapi juga menawarkan segudang manfaat:
- Fleksibilitas dan Reusabilitas: Bangun komponen inti sekali saja dengan Web Components, lalu gunakan di berbagai proyek, terlepas dari framework yang digunakan. Ini sangat berguna untuk design system atau komponen UI tingkat rendah yang harus konsisten di seluruh produk.
- Migrasi Bertahap: Jika Anda berencana memigrasikan aplikasi lama ke framework baru, atau sebaliknya, Web Components bisa menjadi jembatan yang mulus. Anda bisa mulai membangun komponen baru dengan Web Components dan mengintegrasikannya secara bertahap.
- Menghindari Vendor Lock-in: Dengan Web Components, Anda tidak sepenuhnya terikat pada satu framework. Komponen Anda akan “hidup” lebih lama, bahkan jika Anda memutuskan untuk beralih framework di masa depan.
Namun, seperti halnya setiap integrasi, ada tantangan. Frameworks memiliki cara mereka sendiri dalam mengelola data, event, dan styling. Memahami bagaimana Web Components berinteraksi dengan mekanisme internal framework adalah kunci untuk interoperabilitas yang mulus. Artikel ini akan memandu Anda melalui tips dan trik praktis untuk mengintegrasikan Web Components dengan React, Vue, dan Angular.
2. Memahami Dasar Interoperabilitas
Inti dari Web Components adalah bahwa mereka adalah native HTML elements. Ini berarti browser mengenali mereka seperti <p>, <div>, atau <a>. Framework frontend, pada dasarnya, juga bekerja dengan elemen HTML. Ini adalah fondasi utama interoperabilitas.
📌 Poin Penting:
- Atribut vs. Props: Web Components menerima data melalui attributes pada elemen HTML (misalnya
<my-component data-name="Alice">). Frameworks, di sisi lain, sering menggunakan props yang merupakan JavaScript object (misalnya<MyComponent name="Alice" />). Saat mengintegrasikan, framework biasanya akan meneruskan props sebagai atribut ke Web Component. - Custom Events: Web Components berkomunikasi ke “luar” (ke aplikasi atau framework yang menggunakannya) menggunakan Custom Events (misalnya
this.dispatchEvent(new CustomEvent('my-event', { detail: 'data' }))). Frameworks perlu “mendengarkan” event ini. - Shadow DOM dan Styling: Web Components sering menggunakan Shadow DOM untuk mengisolasi styling dan markup mereka. Ini bisa menjadi tantangan karena gaya global dari framework tidak akan secara otomatis menembus Shadow DOM.
Mari kita lihat bagaimana framework populer menangani hal-hal ini.
3. Integrasi dengan React
React seringkali menjadi framework yang sedikit “rewel” dalam berinteraksi dengan Web Components karena ia memiliki sistem event sintetisnya sendiri dan cenderung mengabaikan atribut non-standar pada elemen DOM.
💡 Tips Praktis:
- Passing Data: Gunakan atribut HTML untuk meneruskan data sederhana. Untuk data kompleks (objek/array), Anda mungkin perlu meneruskannya sebagai prop JavaScript ke komponen pembungkus React, lalu secara manual menetapkannya ke properti Web Component menggunakan
refdanuseEffect. - Menangani Event: React tidak secara otomatis mendengarkan custom events dari Web Components. Anda perlu menambahkan event listener secara manual menggunakan
refdanuseEffect. - Membungkus Web Component: Seringkali lebih baik membungkus Web Component di dalam komponen React untuk mengelola interaksi.
// my-web-component.js (contoh Web Component)
class MyWebComponent extends HTMLElement {
static get observedAttributes() { return ['name']; }
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>span { color: blue; }</style>
<span>Halo, <slot>Dunia</slot>!</span>
<button>Klik Saya</button>
`;
}
connectedCallback() {
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('buttonClick', {
detail: { message: `Tombol diklik oleh ${this.getAttribute('name') || 'anonim'}` },
bubbles: true,
composed: true
}));
});
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'name') {
this.shadowRoot.querySelector('span').textContent = `Halo, ${newValue || 'Dunia'}!`;
}
}
}
customElements.define('my-web-component', MyWebComponent);
// App.js (Integrasi dengan React)
import React, { useRef, useEffect } from 'react';
import './my-web-component'; // Pastikan Web Component terdaftar
function App() {
const webComponentRef = useRef(null);
useEffect(() => {
const handleButtonClick = (event) => {
console.log('Custom event diterima di React:', event.detail.message);
alert(event.detail.message);
};
const currentRef = webComponentRef.current;
if (currentRef) {
// Menambahkan event listener untuk custom event
currentRef.addEventListener('buttonClick', handleButtonClick);
}
return () => {
if (currentRef) {
currentRef.removeEventListener('buttonClick', handleButtonClick);
}
};
}, []);
// Membungkus Web Component dalam komponen React
const MyReactWebComponent = ({ name, onButtonClick }) => {
const ref = useRef(null);
useEffect(() => {
const currentRef = ref.current;
if (currentRef && onButtonClick) {
currentRef.addEventListener('buttonClick', onButtonClick);
}
return () => {
if (currentRef && onButtonClick) {
currentRef.removeEventListener('buttonClick', onButtonClick);
}
};
}, [onButtonClick]);
// React meneruskan prop 'name' sebagai atribut HTML secara otomatis
return <my-web-component ref={ref} name={name}>React User</my-web-component>;
};
const handleGlobalButtonClick = (event) => {
console.log('Event dari komponen pembungkus:', event.detail.message);
};
return (
<div>
<h1>Integrasi Web Component dengan React</h1>
<MyReactWebComponent name="React Developer" onButtonClick={handleGlobalButtonClick} />
{/* Contoh langsung (kurang disarankan untuk event handling) */}
<my-web-component ref={webComponentRef} name="Langsung">
<p>Konten dari React</p>
</my-web-component>
</div>
);
}
export default App;
Dalam contoh React, kita perlu secara eksplisit menambahkan dan menghapus event listener menggunakan useEffect dan useRef untuk custom event. Untuk data, React meneruskan string props sebagai atribut secara default.
4. Integrasi dengan Vue
Vue memiliki dukungan yang lebih baik untuk Web Components secara out-of-the-box dibandingkan React, berkat sistem event dan prop-nya yang lebih fleksibel.
💡 Tips Praktis:
- Passing Data: Vue secara otomatis akan meneruskan props yang Anda deklarasikan di komponen Vue sebagai atribut HTML ke Web Component.
- Menangani Event: Vue secara otomatis akan mendengarkan custom events yang dipancarkan oleh Web Components. Anda bisa menggunakan
v-onseperti biasa. - Slot Content: Vue bisa meneruskan konten ke slot Web Component dengan mudah.
<!-- my-web-component.js (sama seperti di atas) -->
<!-- App.vue (Integrasi dengan Vue) -->
<template>
<div>
<h1>Integrasi Web Component dengan Vue</h1>
<!-- Vue secara otomatis meneruskan 'name' sebagai atribut dan mendengarkan 'buttonClick' -->
<my-web-component :name="vueUserName" @buttonClick="handleButtonClick">
<p>Konten dari Vue</p>
</my-web-component>
</div>
</template>
<script>
import './my-web-component'; // Pastikan Web Component terdaftar
export default {
data() {
return {
vueUserName: 'Vue Developer'
};
},
methods: {
handleButtonClick(event) {
console.log('Custom event diterima di Vue:', event.detail.message);
alert(event.detail.message);
}
}
};
</script>
Vue secara intuitif menangani atribut dan custom event, membuat integrasi terasa lebih natural.
5. Integrasi dengan Angular
Angular juga memiliki dukungan yang cukup baik untuk Web Components, terutama dengan adanya CUSTOM_ELEMENTS_SCHEMA yang memberitahu Angular untuk tidak mengeluh tentang elemen HTML yang tidak dikenal.
💡 Tips Praktis:
- Passing Data: Anda bisa menggunakan input properties Angular (
[property]="value") untuk meneruskan data ke Web Component. Angular akan meneruskannya sebagai atribut. - Menangani Event: Gunakan output properties Angular (
(event)="handler()") untuk mendengarkan custom events dari Web Component. - Content Projection: Angular mendukung content projection ke slot Web Component.
- Schema Configuration: Tambahkan
CUSTOM_ELEMENTS_SCHEMAkeNgModuleAnda.
// my-web-component.js (sama seperti di atas)
// app.module.ts (Konfigurasi Angular)
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
// Pastikan Web Component terdaftar sebelum Angular bootstrap
import './my-web-component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA] // Ini penting!
})
export class AppModule { }
// app.component.ts (Integrasi dengan Angular)
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>Integrasi Web Component dengan Angular</h1>
<!-- Angular meneruskan input 'name' sebagai atribut dan mendengarkan event 'buttonClick' -->
<my-web-component [name]="angularUserName" (buttonClick)="handleButtonClick($event)">
<p>Konten dari Angular</p>
</my-web-component>
`,
styles: []
})
export class AppComponent {
angularUserName = 'Angular Developer';
handleButtonClick(event: CustomEvent) {
console.log('Custom event diterima di Angular:', event.detail.message);
alert(event.detail.message);
}
}
CUSTOM_ELEMENTS_SCHEMA adalah kunci untuk menginformasikan Angular agar tidak memperlakukan Web Components sebagai elemen yang tidak dikenal, memungkinkan integrasi yang mulus untuk data dan event.
6. Tantangan Umum dan Solusi
Meskipun integrasi dasar sudah kita bahas, ada beberapa tantangan umum yang mungkin Anda temui:
🎨 Styling
- Masalah: Gaya global dari framework tidak akan secara otomatis menembus Shadow DOM Web Component. Sebaliknya, gaya Web Component juga tidak akan bocor keluar.
- Solusi:
- CSS Custom Properties (Variables): Desain Web Component Anda untuk menerima CSS Custom Properties (misalnya
--button-color: blue;). Framework kemudian dapat mengatur variabel ini dari luar Shadow DOM. ::partdan::slotted: Jika Anda perlu mengekspos bagian internal Web Component untuk styling, gunakanpartatribut di dalam Shadow DOM dan::part()pseudo-element dari luar. Untuk konten yang dislot, gunakan::slotted().- No Shadow DOM: Opsi terakhir adalah tidak menggunakan Shadow DOM (mode:
openatauclosed) dan hanya menggunakan Custom Elements. Namun, ini mengorbankan isolasi gaya.
- CSS Custom Properties (Variables): Desain Web Component Anda untuk menerima CSS Custom Properties (misalnya
🔄 State Management
- Masalah: Bagaimana Web Component dan framework berbagi state yang kompleks atau global?
- Solusi:
- Event-Driven: Web Components memancarkan event, framework mendengarkan dan memperbarui state globalnya. Framework kemudian meneruskan data kembali ke Web Component melalui atribut/props.
- Global State (misalnya Redux, Vuex, NgRx): Web Components bisa memancarkan event yang memicu action di store global. Framework akan memperbarui store, dan komponen framework yang membungkus Web Component akan meneruskan state yang diperbarui sebagai prop.
- Context API (React) / Provide/Inject (Vue): Anda bisa membungkus Web Component dalam komponen framework yang menyediakan context atau data. Web Component kemudian bisa mengakses data ini melalui prop atau bahkan dengan API native yang sesuai jika didesain demikian (lebih kompleks).
⚡ Server-Side Rendering (SSR)
- Masalah: Web Components, terutama dengan Shadow DOM, seringkali tidak dirender di server secara default, menyebabkan flash of unstyled content (FOUC) atau masalah hidrasi.
- Solusi: Gunakan [