SiamCafe.net Blog
Technology

Stencil.js Internal Developer Platform

stenciljs internal developer platform
Stencil.js Internal Developer Platform | SiamCafe Blog
2025-09-23· อ. บอม — SiamCafe.net· 1,598 คำ

Stencil.js Internal Developer Platform คืออะไร

Stencil.js เป็น compiler สำหรับสร้าง Web Components ที่ทำงานได้ทุก framework (React, Angular, Vue, vanilla JS) พัฒนาโดยทีม Ionic Framework Internal Developer Platform (IDP) คือแพลตฟอร์มภายในองค์กรที่ช่วยให้นักพัฒนาสร้าง deploy และจัดการ applications ได้ด้วยตนเอง การรวม Stencil.js กับ IDP ช่วยสร้าง shared component library ที่ทุกทีมใช้ร่วมกัน ลด duplication เพิ่ม consistency และเร่งความเร็วการพัฒนา

Stencil.js สำหรับ Design System

# stencil_design.py — Stencil.js for design systems
import json

class StencilDesignSystem:
    BENEFITS = {
        "framework_agnostic": {
            "name": "Framework Agnostic",
            "description": "Web Components ทำงานกับทุก framework — React, Angular, Vue, Svelte",
            "impact": "ทีมต่างๆ ใช้ framework ต่างกันได้ แต่ใช้ components เดียวกัน",
        },
        "compiler": {
            "name": "Build-time Compiler",
            "description": "Stencil compile เป็น native Web Components — ไม่มี runtime overhead",
            "impact": "Bundle size เล็ก, performance ดี, lazy loading built-in",
        },
        "typescript": {
            "name": "TypeScript + JSX",
            "description": "เขียน components ด้วย TypeScript + JSX — type-safe, familiar syntax",
            "impact": "Developer experience ดี, IDE support, auto-complete",
        },
        "testing": {
            "name": "Built-in Testing",
            "description": "Unit tests + E2E tests built-in — Jest + Puppeteer",
            "impact": "ทดสอบ components ง่าย ไม่ต้อง setup เพิ่ม",
        },
    }

    COMPONENT_EXAMPLE = """
// my-button.tsx — Stencil component example
import { Component, Prop, Event, EventEmitter, h } from '@stencil/core';

@Component({
  tag: 'my-button',
  styleUrl: 'my-button.css',
  shadow: true,
})
export class MyButton {
  @Prop() variant: 'primary' | 'secondary' | 'danger' = 'primary';
  @Prop() size: 'sm' | 'md' | 'lg' = 'md';
  @Prop() disabled: boolean = false;
  @Prop() loading: boolean = false;

  @Event() buttonClick: EventEmitter;

  private handleClick = () => {
    if (!this.disabled && !this.loading) {
      this.buttonClick.emit();
    }
  };

  render() {
    return (
      
    );
  }
}
"""

    def show_benefits(self):
        print("=== Stencil.js Benefits ===\n")
        for key, b in self.BENEFITS.items():
            print(f"[{b['name']}]")
            print(f"  {b['description']}")
            print()

    def show_component(self):
        print("=== Component Example ===")
        print(self.COMPONENT_EXAMPLE[:500])

design = StencilDesignSystem()
design.show_benefits()
design.show_component()

IDP Architecture

# idp_arch.py — Internal Developer Platform architecture
import json

class IDPArchitecture:
    LAYERS = {
        "developer_portal": {
            "name": "Developer Portal (Backstage)",
            "description": "UI สำหรับ developers — service catalog, docs, templates",
            "tools": "Backstage, Port, Cortex",
        },
        "component_library": {
            "name": "Shared Component Library (Stencil.js)",
            "description": "Web Components ที่ทุกทีมใช้ร่วมกัน — design system",
            "tools": "Stencil.js, Storybook, Chromatic",
        },
        "ci_cd": {
            "name": "CI/CD Pipeline",
            "description": "Build, test, publish components อัตโนมัติ",
            "tools": "GitHub Actions, npm registry, Changesets",
        },
        "infrastructure": {
            "name": "Infrastructure Platform",
            "description": "Deploy applications ด้วย golden paths",
            "tools": "Kubernetes, Terraform, ArgoCD",
        },
    }

    GOLDEN_PATHS = {
        "new_app": {
            "name": "Create New App",
            "steps": "Template → Scaffold → CI/CD → Deploy → Monitor",
            "includes": "Design system components pre-configured",
        },
        "new_component": {
            "name": "Create New Component",
            "steps": "Template → Develop → Test → Review → Publish to npm",
            "includes": "Storybook, unit tests, accessibility checks",
        },
        "deploy": {
            "name": "Deploy to Production",
            "steps": "PR → Review → Staging → Canary → Production",
            "includes": "Automated testing, component visual regression",
        },
    }

    def show_layers(self):
        print("=== IDP Architecture ===\n")
        for key, layer in self.LAYERS.items():
            print(f"[{layer['name']}]")
            print(f"  {layer['description']}")
            print(f"  Tools: {layer['tools']}")
            print()

    def show_golden_paths(self):
        print("=== Golden Paths ===")
        for key, path in self.GOLDEN_PATHS.items():
            print(f"\n[{path['name']}]")
            print(f"  Steps: {path['steps']}")

arch = IDPArchitecture()
arch.show_layers()
arch.show_golden_paths()

Component Library Pipeline

# pipeline.py — Component library CI/CD pipeline
import json

class ComponentPipeline:
    GITHUB_ACTIONS = """
# .github/workflows/component-library.yml
name: Component Library
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      
      - run: npm ci
      
      - name: Lint
        run: npm run lint
      
      - name: Unit Tests
        run: npm run test -- --ci
      
      - name: Build
        run: npm run build
      
      - name: E2E Tests
        run: npm run test.e2e
      
      - name: Visual Regression
        run: npx chromatic --project-token=}
      
      - name: Accessibility Check
        run: npm run test.a11y

  publish:
    needs: build-and-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'
      
      - run: npm ci
      - run: npm run build
      
      - name: Publish to npm
        run: npx changeset publish
        env:
          NODE_AUTH_TOKEN: }
      
      - name: Deploy Storybook
        run: npm run deploy-storybook
"""

    STORYBOOK = """
// Button.stories.tsx — Storybook story
export default {
  title: 'Components/Button',
  component: 'my-button',
  argTypes: {
    variant: { control: 'select', options: ['primary', 'secondary', 'danger'] },
    size: { control: 'select', options: ['sm', 'md', 'lg'] },
    disabled: { control: 'boolean' },
    loading: { control: 'boolean' },
  },
};

const Template = (args) => `
  Click Me
`;

export const Primary = Template.bind({});
Primary.args = { variant: 'primary', size: 'md' };

export const Loading = Template.bind({});
Loading.args = { variant: 'primary', loading: true };
"""

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

    def show_storybook(self):
        print("\n=== Storybook Stories ===")
        print(self.STORYBOOK[:400])

pipeline = ComponentPipeline()
pipeline.show_pipeline()
pipeline.show_storybook()

Python Automation Tools

# automation.py — Python tools for component library management
import json

class ComponentAutomation:
    CODE = """
# component_manager.py — Manage Stencil component library
import json
import subprocess
from pathlib import Path
from datetime import datetime

class ComponentLibraryManager:
    def __init__(self, lib_path="./component-library"):
        self.lib_path = Path(lib_path)
    
    def list_components(self):
        '''List all components'''
        components_dir = self.lib_path / "src" / "components"
        components = []
        
        for comp_dir in components_dir.iterdir():
            if comp_dir.is_dir():
                tsx_files = list(comp_dir.glob("*.tsx"))
                test_files = list(comp_dir.glob("*.spec.ts"))
                css_files = list(comp_dir.glob("*.css"))
                
                components.append({
                    'name': comp_dir.name,
                    'has_tests': len(test_files) > 0,
                    'has_styles': len(css_files) > 0,
                    'files': len(list(comp_dir.iterdir())),
                })
        
        return components
    
    def component_health(self):
        '''Check component library health'''
        components = self.list_components()
        
        tested = sum(1 for c in components if c['has_tests'])
        styled = sum(1 for c in components if c['has_styles'])
        
        return {
            'total_components': len(components),
            'with_tests': tested,
            'test_coverage_pct': round(tested / max(len(components), 1) * 100, 1),
            'with_styles': styled,
            'components': components,
        }
    
    def scaffold_component(self, name, props=None):
        '''Scaffold a new component'''
        comp_dir = self.lib_path / "src" / "components" / name
        comp_dir.mkdir(parents=True, exist_ok=True)
        
        # Generate TSX
        tsx_content = self._generate_tsx(name, props or [])
        (comp_dir / f"{name}.tsx").write_text(tsx_content)
        
        # Generate CSS
        css_content = f":host {{ display: block; }}"
        (comp_dir / f"{name}.css").write_text(css_content)
        
        # Generate test
        test_content = self._generate_test(name)
        (comp_dir / f"{name}.spec.ts").write_text(test_content)
        
        return {'name': name, 'path': str(comp_dir)}
    
    def _generate_tsx(self, name, props):
        class_name = ''.join(w.capitalize() for w in name.split('-'))
        prop_decorators = '\\n'.join(
            f"  @Prop() {p['name']}: {p['type']} = {p['default']};"
            for p in props
        )
        
        return f'''
import {{ Component, Prop, h }} from '@stencil/core';

@Component({{
  tag: '{name}',
  styleUrl: '{name}.css',
  shadow: true,
}})
export class {class_name} {{
{prop_decorators}

  render() {{
    return (
      
); }} }} ''' def _generate_test(self, name): return f''' import {{ newSpecPage }} from '@stencil/core/testing'; import {{ {name.replace('-', '')} }} from './{name}'; describe('{name}', () => {{ it('renders', async () => {{ const page = await newSpecPage({{ components: [{name.replace('-', '')}], html: '<{name}>', }}); expect(page.root).toBeTruthy(); }}); }}); ''' # mgr = ComponentLibraryManager() # health = mgr.component_health() # mgr.scaffold_component("my-card", [ # {"name": "title", "type": "string", "default": "''"}, # {"name": "elevated", "type": "boolean", "default": "false"}, # ]) """ def show_code(self): print("=== Component Automation ===") print(self.CODE[:600]) auto = ComponentAutomation() auto.show_code()

Backstage Integration

# backstage.py — Backstage developer portal integration
import json

class BackstageIntegration:
    CATALOG = """
# catalog-info.yaml — Backstage component registration
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: design-system
  description: Shared Stencil.js Component Library
  annotations:
    github.com/project-slug: org/design-system
    backstage.io/techdocs-ref: dir:.
  tags:
    - stencil
    - web-components
    - design-system
  links:
    - url: https://storybook.example.com
      title: Storybook
    - url: https://www.npmjs.com/package/@org/design-system
      title: npm Package
spec:
  type: library
  lifecycle: production
  owner: platform-team
"""

    TEMPLATE = """
# scaffolder-template.yaml — New App with Design System
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: react-app-with-design-system
  title: React App with Design System
spec:
  owner: platform-team
  type: website
  parameters:
    - title: App Details
      properties:
        name:
          title: App Name
          type: string
        description:
          title: Description
          type: string
  steps:
    - id: scaffold
      name: Scaffold
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: }
    - id: publish
      name: Publish
      action: publish:github
      input:
        repoUrl: github.com?owner=org&repo=}
"""

    def show_catalog(self):
        print("=== Backstage Catalog ===")
        print(self.CATALOG[:400])

    def show_template(self):
        print("\n=== Scaffolder Template ===")
        print(self.TEMPLATE[:400])

backstage = BackstageIntegration()
backstage.show_catalog()
backstage.show_template()

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

Q: Stencil.js กับ Lit อันไหนดีกว่าสำหรับ design system?

A: Stencil: compiler-based, lazy loading, framework wrappers (React/Angular/Vue), testing built-in Lit: runtime library, เล็กกว่า (~5KB), simple API, Google-backed เลือก Stencil: ถ้าต้องการ framework wrappers + testing + lazy loading เลือก Lit: ถ้าต้องการ simplicity + smaller bundle + Google ecosystem

Q: IDP จำเป็นต้องมีไหม?

A: ขึ้นกับขนาดองค์กร: < 10 developers: ไม่จำเป็น — ใช้ README + templates พอ 10-50 developers: แนะนำ — ช่วย standardize + ลด onboarding time > 50 developers: จำเป็น — ไม่มี IDP = chaos, duplicated work, inconsistency ROI: ลดเวลา onboarding 50%, ลด duplicated components 70%, เพิ่ม developer satisfaction

Q: Component library ต้องมีกี่ components ถึงจะคุ้ม?

A: เริ่มจาก 5-10 components พื้นฐาน: Button, Input, Card, Modal, Toast, Table, Tabs คุ้มเมื่อ: ใช้ซ้ำ > 3 ทีม หรือ > 5 projects เพิ่มเติม: Form components, Navigation, Data display, Feedback components สำคัญ: คุณภาพ > จำนวน — 10 components ที่ดีดีกว่า 50 components ที่แย่

Q: Shadow DOM ดีหรือไม่ดี?

A: ดี: Encapsulation — styles ไม่รั่วเข้าออก, ใช้ได้ทุก framework ไม่ดี: Style customization ยากกว่า, ต้องใช้ CSS custom properties (--var) ทางเลือก: Stencil รองรับ shadow: false — ใช้ scoped CSS แทน แนะนำ: ใช้ Shadow DOM + CSS custom properties สำหรับ theming

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

Elasticsearch OpenSearch Internal Developer Platformอ่านบทความ → OSPF Area Design Internal Developer Platformอ่านบทความ → React Server Components Internal Developer Platformอ่านบทความ → PHP Filament Internal Developer Platformอ่านบทความ → WordPress WooCommerce Internal Developer Platformอ่านบทความ →

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