Web Components Stream Processing คืออะไร
Web Components เป็น web standard สำหรับสร้าง reusable custom HTML elements ด้วย Shadow DOM, Custom Elements และ HTML Templates ทำให้สร้าง UI components ที่ encapsulated และใช้ได้กับทุก framework Stream Processing คือการประมวลผลข้อมูลแบบ real-time ต่อเนื่อง การรวมสองแนวคิดนี้ช่วยสร้าง real-time dashboard components, live data visualizations และ streaming data widgets ที่ reusable ข้าม projects โดยใช้ Web Standards ไม่ผูกกับ framework ใดๆ
Web Components พื้นฐาน
# web_components.py — Web Components fundamentals
import json
class WebComponentsBasics:
STANDARDS = {
"custom_elements": {
"name": "Custom Elements",
"description": "สร้าง HTML elements ใหม่ด้วย JavaScript class",
"api": "customElements.define('my-element', MyElement)",
"lifecycle": ["connectedCallback", "disconnectedCallback", "attributeChangedCallback"],
},
"shadow_dom": {
"name": "Shadow DOM",
"description": "Encapsulated DOM tree — styles และ markup แยกจาก main document",
"api": "this.attachShadow({ mode: 'open' })",
"benefit": "CSS isolation, DOM encapsulation",
},
"html_templates": {
"name": "HTML Templates",
"description": "Reusable HTML fragments ที่ไม่ render จนกว่าจะ clone",
"api": " + document.importNode()",
"benefit": "Performance — ไม่ parse/render จนกว่าจะใช้",
},
}
COMPONENT_EXAMPLE = """
// stream-counter.js — Basic Web Component
class StreamCounter extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.count = 0;
this.shadowRoot.innerHTML = `
0
`;
}
connectedCallback() {
this.counterEl = this.shadowRoot.querySelector('.counter');
}
update(value) {
this.count = value;
if (this.counterEl) {
this.counterEl.textContent = this.count.toLocaleString();
}
}
increment(amount = 1) {
this.update(this.count + amount);
}
}
customElements.define('stream-counter', StreamCounter);
// Usage:
"""
def show_standards(self):
print("=== Web Component Standards ===\n")
for key, std in self.STANDARDS.items():
print(f"[{std['name']}]")
print(f" {std['description']}")
print(f" API: {std['api']}")
print()
def show_example(self):
print("=== Component Example ===")
print(self.COMPONENT_EXAMPLE[:500])
wc = WebComponentsBasics()
wc.show_standards()
wc.show_example()
Real-Time Stream Components
# stream_components.py — Real-time stream processing components
import json
class StreamComponents:
LIVE_CHART = """
// live-chart.js — Real-time streaming chart component
class LiveChart extends HTMLElement {
static get observedAttributes() {
return ['max-points', 'title', 'color'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.data = [];
this.maxPoints = parseInt(this.getAttribute('max-points')) || 100;
this.render();
}
render() {
this.shadowRoot.innerHTML = `
`;
this.canvas = this.shadowRoot.querySelector('canvas');
this.ctx = this.canvas.getContext('2d');
this.statsEl = this.shadowRoot.querySelector('.stats');
}
addPoint(value, timestamp = Date.now()) {
this.data.push({ value, timestamp });
if (this.data.length > this.maxPoints) {
this.data.shift();
}
this.draw();
this.updateStats();
}
draw() {
const { canvas, ctx, data } = this;
const w = canvas.width = canvas.offsetWidth;
const h = canvas.height = canvas.offsetHeight;
const color = this.getAttribute('color') || '#2563eb';
ctx.clearRect(0, 0, w, h);
if (data.length < 2) return;
const max = Math.max(...data.map(d => d.value));
const min = Math.min(...data.map(d => d.value));
const range = max - min || 1;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 2;
data.forEach((d, i) => {
const x = (i / (data.length - 1)) * w;
const y = h - ((d.value - min) / range) * h * 0.9 - h * 0.05;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
updateStats() {
const values = this.data.map(d => d.value);
const avg = values.reduce((a, b) => a + b, 0) / values.length;
const max = Math.max(...values);
const min = Math.min(...values);
this.statsEl.textContent =
`Avg: | Max: | Min: `;
}
}
customElements.define('live-chart', LiveChart);
"""
EVENT_LOG = """
// event-log.js — Real-time event log component
class EventLog extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.maxEntries = parseInt(this.getAttribute('max-entries')) || 50;
this.shadowRoot.innerHTML = `
`;
this.logEl = this.shadowRoot.querySelector('.log');
}
addEntry(message, level = 'info') {
const entry = document.createElement('div');
entry.className = `entry `;
const time = new Date().toLocaleTimeString();
entry.innerHTML = ``;
this.logEl.prepend(entry);
while (this.logEl.children.length > this.maxEntries) {
this.logEl.lastChild.remove();
}
}
}
customElements.define('event-log', EventLog);
"""
def show_chart(self):
print("=== Live Chart Component ===")
print(self.LIVE_CHART[:600])
def show_log(self):
print(f"\n=== Event Log Component ===")
print(self.EVENT_LOG[:500])
sc = StreamComponents()
sc.show_chart()
sc.show_log()
WebSocket & SSE Integration
# websocket.py — WebSocket and SSE stream integration
import json
class StreamIntegration:
WEBSOCKET = """
// stream-connector.js — WebSocket stream connector
class StreamConnector extends HTMLElement {
static get observedAttributes() {
return ['url', 'auto-connect'];
}
constructor() {
super();
this.ws = null;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
}
connectedCallback() {
if (this.getAttribute('auto-connect') !== 'false') {
this.connect();
}
}
disconnectedCallback() {
this.disconnect();
}
connect() {
const url = this.getAttribute('url');
if (!url) return;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
this.reconnectDelay = 1000;
this.dispatchEvent(new CustomEvent('stream-connected'));
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.dispatchEvent(new CustomEvent('stream-data', {
detail: data, bubbles: true, composed: true
}));
};
this.ws.onclose = () => {
this.dispatchEvent(new CustomEvent('stream-disconnected'));
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(
this.reconnectDelay * 2, this.maxReconnectDelay
);
};
}
disconnect() {
if (this.ws) this.ws.close();
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
customElements.define('stream-connector', StreamConnector);
// Usage
//
// connector.addEventListener('stream-data', (e) => chart.addPoint(e.detail.value));
"""
SSE = """
// sse-connector.js — Server-Sent Events connector
class SSEConnector extends HTMLElement {
connectedCallback() {
const url = this.getAttribute('url');
this.source = new EventSource(url);
this.source.onmessage = (event) => {
const data = JSON.parse(event.data);
this.dispatchEvent(new CustomEvent('stream-data', {
detail: data, bubbles: true, composed: true
}));
};
this.source.onerror = () => {
this.dispatchEvent(new CustomEvent('stream-error'));
};
}
disconnectedCallback() {
if (this.source) this.source.close();
}
}
customElements.define('sse-connector', SSEConnector);
"""
def show_websocket(self):
print("=== WebSocket Connector ===")
print(self.WEBSOCKET[:600])
def show_sse(self):
print(f"\n=== SSE Connector ===")
print(self.SSE[:400])
stream = StreamIntegration()
stream.show_websocket()
stream.show_sse()
Dashboard Assembly
# dashboard.py — Assembling stream dashboard
import json
class StreamDashboard:
HTML = """
<!-- stream-dashboard.html -->
<html>
<head>
<script src="stream-connector.js" type="module"></script>
<script src="live-chart.js" type="module"></script>
<script src="stream-counter.js" type="module"></script>
<script src="event-log.js" type="module"></script>
</head>
<body>
<stream-connector id="conn" url="wss://api.example.com/stream"></stream-connector>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;">
<stream-counter id="rps" label="Requests/sec"></stream-counter>
<stream-counter id="errors" label="Errors"></stream-counter>
<stream-counter id="latency" label="Latency (ms)"></stream-counter>
</div>
<live-chart id="chart" title="Throughput" color="#2563eb" max-points="200"></live-chart>
<event-log id="log" max-entries="100"></event-log>
<script>
const conn = document.getElementById('conn');
const chart = document.getElementById('chart');
const log = document.getElementById('log');
conn.addEventListener('stream-data', (e) => {
const { type, value, message } = e.detail;
if (type === 'metric') chart.addPoint(value);
if (type === 'event') log.addEntry(message, e.detail.level);
if (type === 'counter') document.getElementById(e.detail.id)?.update(value);
});
</script>
</body>
</html>
"""
PYTHON_BACKEND = """
# backend.py — WebSocket backend for stream dashboard
import asyncio
import json
import random
from datetime import datetime
import websockets
async def stream_handler(websocket):
print(f"Client connected")
try:
while True:
data = {
"type": random.choice(["metric", "event", "counter"]),
"timestamp": datetime.now().isoformat(),
}
if data["type"] == "metric":
data["value"] = random.uniform(100, 1000)
elif data["type"] == "event":
data["message"] = f"Request processed in {random.randint(10, 200)}ms"
data["level"] = random.choice(["info", "info", "warn", "error"])
elif data["type"] == "counter":
data["id"] = random.choice(["rps", "errors", "latency"])
data["value"] = random.randint(50, 500)
await websocket.send(json.dumps(data))
await asyncio.sleep(0.1)
except websockets.ConnectionClosed:
print("Client disconnected")
async def main():
async with websockets.serve(stream_handler, "0.0.0.0", 8765):
await asyncio.Future()
asyncio.run(main())
"""
def show_html(self):
print("=== Dashboard HTML ===")
print(self.HTML[:500])
def show_backend(self):
print(f"\n=== Python Backend ===")
print(self.PYTHON_BACKEND[:500])
dash = StreamDashboard()
dash.show_html()
dash.show_backend()
Testing & Publishing
# testing.py — Web component testing and publishing
import json
class ComponentTesting:
TESTING = {
"unit": {
"name": "Unit Tests (Web Test Runner)",
"framework": "@web/test-runner + @open-wc/testing",
"test": """
import { fixture, html, expect } from '@open-wc/testing';
import '../src/stream-counter.js';
describe('StreamCounter', () => {
it('renders with default label', async () => {
const el = await fixture(html` `);
expect(el.shadowRoot.querySelector('.label').textContent).to.equal('Events');
});
it('updates count', async () => {
const el = await fixture(html` `);
el.update(42);
expect(el.shadowRoot.querySelector('.counter').textContent).to.equal('42');
});
it('increments', async () => {
const el = await fixture(html` `);
el.increment(10);
el.increment(5);
expect(el.count).to.equal(15);
});
});
""",
},
"publish": {
"name": "Publish to npm",
"steps": [
"npm init (package.json)",
"npm publish --access public",
"CDN: unpkg.com/my-stream-components",
],
},
}
def show_testing(self):
print("=== Component Testing ===")
print(self.TESTING["unit"]["test"][:400])
def show_publish(self):
print(f"\n=== Publishing ===")
for step in self.TESTING["publish"]["steps"]:
print(f" • {step}")
test = ComponentTesting()
test.show_testing()
test.show_publish()
FAQ - คำถามที่พบบ่อย
Q: Web Components กับ React Components อันไหนดี?
A: Web Components: framework-agnostic, browser-native, ใช้ได้ทุกที่ React Components: ecosystem ใหญ่, developer experience ดีกว่า, state management ใช้ Web Components: shared components ข้าม frameworks, design system, embeddable widgets ใช้ React: full application, complex state, team ใช้ React อยู่แล้ว
Q: WebSocket กับ SSE อันไหนดีสำหรับ streaming?
A: WebSocket: bi-directional, ส่งข้อมูลได้ทั้ง 2 ทาง, binary support SSE: server → client only, auto-reconnect built-in, simpler ใช้ WebSocket: interactive (chat, gaming, bi-directional control) ใช้ SSE: dashboard, notifications, server push only
Q: Shadow DOM จำเป็นไหม?
A: แนะนำสำหรับ: shared components, design system (CSS isolation สำคัญ) ไม่จำเป็นสำหรับ: internal components ที่ต้อง style จาก parent ข้อเสีย Shadow DOM: debugging ยากขึ้น, global styles ไม่เข้า ทางเลือก: ใช้ Custom Elements ไม่มี Shadow DOM (light DOM)
Q: Performance ของ Web Components ดีไหม?
A: ดีมาก Browser-native = ไม่มี framework overhead Shadow DOM = efficient rendering (isolated repaints) เร็วกว่า React/Vue สำหรับ simple components ช้ากว่าสำหรับ complex state management ที่ framework optimize ดี สำหรับ stream dashboard: Web Components เหมาะมาก (lightweight, fast updates)
