SiamCafe.net Blog
Technology

Web Components Scaling Strategy วิธี Scale

web components scaling strategy วธ scale
Web Components Scaling Strategy วิธี Scale | SiamCafe Blog
2026-03-13· อ. บอม — SiamCafe.net· 1,437 คำ

Web Components Scaling Strategy วิธี Scale

Web Components เป็น web standard ที่ช่วยสร้าง reusable custom HTML elements ด้วย Shadow DOM, Custom Elements, HTML Templates และ ES Modules ทำให้สร้าง UI components ที่ encapsulated ใช้ได้กับทุก framework (React, Vue, Angular) การ scale Web Components สำหรับ design systems ขนาดใหญ่ต้องวางแผน architecture, versioning, testing และ distribution อย่างเป็นระบบ บทความนี้อธิบาย scaling strategies ตั้งแต่ monorepo setup จนถึง CDN distribution พร้อม code examples

Web Components Fundamentals

// basic_component.js — Basic Web Component
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  static get observedAttributes() {
    return ['variant', 'size', 'disabled'];
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (oldVal !== newVal) this.render();
  }

  get variant() { return this.getAttribute('variant') || 'primary'; }
  get size() { return this.getAttribute('size') || 'medium'; }

  render() {
    const styles = {
      primary: 'background: #3b82f6; color: white;',
      secondary: 'background: #6b7280; color: white;',
      outline: 'background: transparent; border: 2px solid #3b82f6; color: #3b82f6;',
    };
    const sizes = {
      small: 'padding: 4px 12px; font-size: 12px;',
      medium: 'padding: 8px 16px; font-size: 14px;',
      large: 'padding: 12px 24px; font-size: 16px;',
    };

    this.shadowRoot.innerHTML = `
      <style>
        button {
          
          
          border-radius: 6px;
          cursor: pointer;
          font-family: inherit;
          transition: opacity 0.2s;
        }
        button:hover { opacity: 0.9; }
        button:disabled { opacity: 0.5; cursor: not-allowed; }
      </style>
      <button >
        <slot></slot>
      </button>
    `;
  }
}

customElements.define('my-button', MyButton);
// Usage: <my-button variant="primary" size="large">Click Me</my-button>

Monorepo Architecture

# monorepo.py — Monorepo structure for Web Components
import json

class MonorepoStructure:
    STRUCTURE = """
# Design System Monorepo Structure
design-system/
├── packages/
│   ├── core/                   # Core utilities, tokens, mixins
│   │   ├── src/
│   │   │   ├── tokens.js       # Design tokens (colors, spacing)
│   │   │   ├── mixins.js       # Shared CSS mixins
│   │   │   └── utils.js        # Shared utilities
│   │   └── package.json
│   ├── button/                 # Individual component package
│   │   ├── src/
│   │   │   ├── button.js
│   │   │   ├── button.css
│   │   │   └── button.test.js
│   │   ├── stories/
│   │   │   └── button.stories.js
│   │   └── package.json
│   ├── input/
│   ├── modal/
│   ├── table/
│   └── ...
├── apps/
│   ├── storybook/              # Component documentation
│   ├── playground/             # Testing playground
│   └── docs/                   # Documentation site
├── tools/
│   ├── build/                  # Build scripts
│   ├── generators/             # Component generators
│   └── testing/                # Test utilities
├── lerna.json / pnpm-workspace.yaml
├── turbo.json                  # Turborepo config
└── package.json
"""

    TOOLS = {
        "pnpm": {"name": "pnpm Workspaces", "use": "Package management — fast, disk-efficient"},
        "turborepo": {"name": "Turborepo", "use": "Build orchestration — caching, parallel builds"},
        "changeset": {"name": "Changesets", "use": "Versioning + changelog — per-package semver"},
        "storybook": {"name": "Storybook", "use": "Component documentation + visual testing"},
    }

    def show_structure(self):
        print("=== Monorepo Structure ===")
        print(self.STRUCTURE[:600])

    def show_tools(self):
        print(f"\n=== Recommended Tools ===")
        for key, tool in self.TOOLS.items():
            print(f"  [{tool['name']}] {tool['use']}")

mono = MonorepoStructure()
mono.show_structure()
mono.show_tools()

Scaling Strategies

# scaling.py — Scaling strategies for Web Components
import json

class ScalingStrategies:
    STRATEGIES = {
        "lazy_loading": {
            "name": "1. Lazy Loading Components",
            "description": "โหลด component เมื่อต้องใช้จริง — ลด initial bundle size",
            "code": """
// lazy-loader.js
const lazyDefine = (tagName, importFn) => {
  if (customElements.get(tagName)) return;
  
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(async (entry) => {
      if (entry.isIntersecting) {
        const module = await importFn();
        if (!customElements.get(tagName)) {
          customElements.define(tagName, module.default);
        }
        observer.unobserve(entry.target);
      }
    });
  });
  
  document.querySelectorAll(tagName).forEach(el => observer.observe(el));
};

// Usage
lazyDefine('heavy-chart', () => import('./components/chart.js'));
lazyDefine('data-table', () => import('./components/table.js'));
""",
        },
        "tree_shaking": {
            "name": "2. Tree Shaking & Code Splitting",
            "description": "Import เฉพาะ components ที่ใช้ — Rollup/Webpack tree-shake ส่วนที่ไม่ใช้ออก",
            "code": "import { MyButton } from '@design-system/button'; // Only button code",
        },
        "design_tokens": {
            "name": "3. Design Tokens",
            "description": "แยก design values (colors, spacing) เป็น tokens — เปลี่ยน theme ง่าย",
            "code": "CSS Custom Properties: --ds-color-primary: #3b82f6;",
        },
        "versioning": {
            "name": "4. Independent Versioning",
            "description": "แต่ละ component มี version แยก — update component เดียวไม่กระทบอื่น",
            "code": "@design-system/button@2.1.0, @design-system/input@1.5.0",
        },
        "cdn_distribution": {
            "name": "5. CDN Distribution",
            "description": "Publish ไป CDN — ใช้ได้ทันทีด้วย script tag ไม่ต้อง build",
            "code": '',
        },
    }

    def show_strategies(self):
        print("=== Scaling Strategies ===\n")
        for key, strat in self.STRATEGIES.items():
            print(f"[{strat['name']}]")
            print(f"  {strat['description']}")
            print()

    def bundle_comparison(self):
        print("=== Bundle Size Comparison ===")
        print(f"  All components (no optimization): ~250KB")
        print(f"  Tree-shaken (import what you use):  ~30KB")
        print(f"  Lazy-loaded (on demand):             ~5KB initial")
        print(f"  CDN + HTTP/2:                        ~2KB per component (cached)")

scale = ScalingStrategies()
scale.show_strategies()
scale.bundle_comparison()

Testing Strategy

# testing.py — Testing strategy for Web Components
import json

class TestingStrategy:
    LAYERS = {
        "unit": {
            "name": "Unit Tests",
            "tool": "Web Test Runner + @open-wc/testing",
            "what": "Test component logic, attributes, events",
            "coverage": "80%+ per component",
        },
        "visual": {
            "name": "Visual Regression Tests",
            "tool": "Storybook + Chromatic / Percy",
            "what": "Screenshot comparison — catch visual changes",
            "coverage": "ทุก component + variants",
        },
        "a11y": {
            "name": "Accessibility Tests",
            "tool": "axe-core + Storybook a11y addon",
            "what": "WCAG compliance, keyboard navigation, screen reader",
            "coverage": "ทุก interactive component",
        },
        "integration": {
            "name": "Integration Tests",
            "tool": "Playwright / Cypress",
            "what": "Test components ในแต่ละ framework (React, Vue, Angular)",
            "coverage": "Critical components + framework wrappers",
        },
    }

    UNIT_TEST = """
// button.test.js — Unit test with @open-wc/testing
import { html, fixture, expect } from '@open-wc/testing';
import '../src/my-button.js';

describe('MyButton', () => {
  it('renders with default props', async () => {
    const el = await fixture(html`Click`);
    expect(el.variant).to.equal('primary');
    expect(el.size).to.equal('medium');
  });

  it('reflects variant attribute', async () => {
    const el = await fixture(html`Click`);
    const button = el.shadowRoot.querySelector('button');
    expect(button).to.exist;
    expect(el.variant).to.equal('secondary');
  });

  it('dispatches click event', async () => {
    const el = await fixture(html`Click`);
    let clicked = false;
    el.addEventListener('click', () => { clicked = true; });
    el.shadowRoot.querySelector('button').click();
    expect(clicked).to.be.true;
  });

  it('is accessible', async () => {
    const el = await fixture(html`Click Me`);
    await expect(el).to.be.accessible();
  });
});
"""

    def show_layers(self):
        print("=== Testing Layers ===\n")
        for key, layer in self.LAYERS.items():
            print(f"[{layer['name']}] Tool: {layer['tool']}")
            print(f"  What: {layer['what']}")
            print()

    def show_unit_test(self):
        print("=== Unit Test Example ===")
        print(self.UNIT_TEST[:500])

test = TestingStrategy()
test.show_layers()
test.show_unit_test()

CI/CD & Distribution

# cicd.py — CI/CD pipeline for Web Components
import json

class CICD:
    PIPELINE = """
# .github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - run: pnpm install
      - run: pnpm turbo build        # Build all packages
      - run: pnpm turbo test          # Run all tests
      - run: pnpm turbo lint          # Lint all packages

  release:
    needs: build-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install

      # Changesets — version + publish
      - uses: changesets/action@v1
        with:
          publish: pnpm changeset publish
        env:
          NPM_TOKEN: }
          GITHUB_TOKEN: }

  cdn-deploy:
    needs: release
    runs-on: ubuntu-latest
    steps:
      - run: pnpm turbo build --filter=@ds/*
      - run: aws s3 sync dist/ s3://cdn.example.com/ds/ --cache-control "public, max-age=31536000"
      - run: aws cloudfront create-invalidation --distribution-id $CDN_ID --paths "/ds/*"
"""

    DISTRIBUTION = {
        "npm": "npm publish — สำหรับ developers (import ใน build)",
        "cdn": "CDN (S3 + CloudFront / unpkg) — สำหรับ script tag",
        "storybook": "Storybook (Chromatic) — สำหรับ documentation + design review",
        "figma": "Figma plugin sync — สำหรับ designers",
    }

    def show_pipeline(self):
        print("=== CI/CD Pipeline ===")
        print(self.PIPELINE[:500])

    def show_distribution(self):
        print(f"\n=== Distribution Channels ===")
        for channel, desc in self.DISTRIBUTION.items():
            print(f"  [{channel}] {desc}")

cicd = CICD()
cicd.show_pipeline()
cicd.show_distribution()

FAQ - คำถามที่พบบ่อย

Q: Web Components กับ React Components ต่างกัน?

A: Web Components: web standard, ใช้ได้ทุก framework, Shadow DOM encapsulation React Components: React-specific, ใช้ได้เฉพาะ React, virtual DOM ข้อดี Web Components: framework-agnostic, future-proof, native browser support ข้อเสีย: DX ไม่ดีเท่า React, ecosystem เล็กกว่า ใช้ Web Components: design system ที่ต้องใช้ข้าม frameworks

Q: ใช้ library อะไรสร้าง Web Components ดี?

A: Lit (Google): เบา (~5KB), reactive properties, templates — แนะนำมากที่สุด Stencil (Ionic): compiler-based, TypeScript, lazy-loading built-in Fast (Microsoft): enterprise-grade, accessibility built-in Vanilla: ไม่ใช้ library — เหมาะ component ง่ายๆ เริ่มต้น: Lit (ง่ายที่สุด, community ใหญ่, Google สนับสนุน)

Q: Scale ยังไงให้รองรับ 100+ components?

A: 1) Monorepo (pnpm + Turborepo) — จัดการ packages 2) Independent versioning (Changesets) — update ทีละ component 3) Lazy loading — โหลดเมื่อใช้ 4) Tree shaking — bundle เฉพาะที่ import 5) CDN distribution — cache + parallel download 6) Visual regression testing — ป้องกัน breaking changes

Q: Shadow DOM จำเป็นไหม?

A: ขึ้นกับ use case: ใช้ Shadow DOM: ต้องการ style encapsulation ป้องกัน CSS leak ไม่ใช้ Shadow DOM: ต้องการ global CSS, ง่ายกว่า customize แนะนำ: ใช้ Shadow DOM สำหรับ design system — ป้องกัน style conflicts

📖 บทความที่เกี่ยวข้อง

AlmaLinux Setup Scaling Strategy วิธี Scaleอ่านบทความ → CDK Construct Scaling Strategy วิธี Scaleอ่านบทความ → OpenTelemetry SDK Scaling Strategy วิธี Scaleอ่านบทความ → Web Components Shift Left Securityอ่านบทความ → Segment Routing Scaling Strategy วิธี Scaleอ่านบทความ →

📚 ดูบทความทั้งหมด →