SiamCafe.net Blog
Technology

Stencil.js Post-mortem Analysis

stenciljs post mortem analysis
Stencil.js Post-mortem Analysis | SiamCafe Blog
2025-10-29· อ. บอม — SiamCafe.net· 1,477 คำ

Stencil.js Post-mortem Analysis คืออะไร

Stencil.js เป็น compiler สำหรับสร้าง Web Components จาก Ionic team ช่วยให้เขียน components ด้วย TypeScript + JSX แล้ว compile เป็น standard Web Components ที่ใช้ได้กับทุก framework Post-mortem Analysis คือกระบวนการวิเคราะห์หลังเกิดเหตุการณ์ (incident) เพื่อหาสาเหตุ เรียนรู้ และป้องกันไม่ให้เกิดซ้ำ การรวมสองแนวคิดนี้ช่วยทีม engineering วิเคราะห์ปัญหาที่เกิดกับ Stencil.js projects อย่างเป็นระบบ ทั้ง build failures, performance issues และ production incidents

Stencil.js Overview

// stencil_overview.tsx — Stencil.js component example
import { Component, Prop, State, Event, EventEmitter, h } from '@stencil/core';

@Component({
  tag: 'my-counter',
  styleUrl: 'my-counter.css',
  shadow: true,
})
export class MyCounter {
  @Prop() initialCount: number = 0;
  @Prop() step: number = 1;
  @State() count: number;
  @Event() countChanged: EventEmitter<number>;

  componentWillLoad() {
    this.count = this.initialCount;
  }

  private increment = () => {
    this.count += this.step;
    this.countChanged.emit(this.count);
  };

  private decrement = () => {
    this.count -= this.step;
    this.countChanged.emit(this.count);
  };

  render() {
    return (
      <div class="counter">
        <button onClick={this.decrement}>-</button>
        <span class="count">{this.count}</span>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

// stencil.config.ts
import { Config } from '@stencil/core';
export const config: Config = {
  namespace: 'my-design-system',
  outputTargets: [
    { type: 'dist', esmLoaderPath: '../loader' },
    { type: 'dist-custom-elements' },
    { type: 'docs-readme' },
    { type: 'www', serviceWorker: null },
  ],
};

Post-mortem Analysis Framework

# postmortem.py — Post-mortem analysis framework
import json
from datetime import datetime

class PostMortemTemplate:
    TEMPLATE = {
        "title": "",
        "date": "",
        "severity": "",  # P1-P4
        "duration": "",
        "impact": "",
        "summary": "",
        "timeline": [],
        "root_cause": "",
        "contributing_factors": [],
        "resolution": "",
        "action_items": [],
        "lessons_learned": [],
    }

    SEVERITY_LEVELS = {
        "P1": {"name": "Critical", "description": "Service down, all users affected", "response": "< 15 min"},
        "P2": {"name": "Major", "description": "Service degraded, many users affected", "response": "< 30 min"},
        "P3": {"name": "Minor", "description": "Partial impact, some users affected", "response": "< 2 hours"},
        "P4": {"name": "Low", "description": "Cosmetic/minor, few users affected", "response": "< 1 day"},
    }

    FIVE_WHYS = """
    5 Whys Technique:
    
    Problem: Component library build failed in production
    
    Why 1: Stencil build errored on TypeScript compilation
    Why 2: A new component used a TypeScript 5.x feature
    Why 3: CI/CD pipeline used TypeScript 4.9 (pinned version)
    Why 4: No automated TypeScript version check in CI
    Why 5: Missing version compatibility matrix in docs
    
    Root Cause: TypeScript version mismatch between dev and CI
    Fix: Pin TypeScript version in package.json + add CI version check
    """

    def show_template(self):
        print("=== Post-mortem Template ===\n")
        for key in self.TEMPLATE:
            print(f"  {key}: ...")

    def show_severity(self):
        print(f"\n=== Severity Levels ===")
        for level, info in self.SEVERITY_LEVELS.items():
            print(f"  [{level}] {info['name']} — {info['description']} (Response: {info['response']})")

    def show_five_whys(self):
        print(f"\n=== 5 Whys Example ===")
        print(self.FIVE_WHYS[:500])

pm = PostMortemTemplate()
pm.show_template()
pm.show_severity()
pm.show_five_whys()

Common Stencil.js Incidents

# incidents.py — Common Stencil.js incidents
import json

class StencilIncidents:
    INCIDENTS = {
        "build_failure": {
            "title": "Build Failure: TypeScript/Stencil Version Mismatch",
            "severity": "P2",
            "root_cause": "TypeScript version ใน dev ≠ CI → compiler errors",
            "resolution": "Pin exact TypeScript version, add engine check",
            "prevention": [
                "Lock TypeScript version ใน package.json (exact, ไม่ใช้ ^)",
                "Add engines field ใน package.json",
                "CI ใช้ same Node.js + npm versions as dev",
                "Renovate/Dependabot สำหรับ version updates",
            ],
        },
        "hydration_mismatch": {
            "title": "SSR Hydration Mismatch",
            "severity": "P2",
            "root_cause": "Server-rendered HTML ≠ client-rendered → visual glitch",
            "resolution": "Fix conditional rendering, use componentDidLoad instead of connectedCallback",
            "prevention": [
                "ใช้ componentDidLoad สำหรับ browser-only code",
                "Test SSR output vs client render",
                "Avoid window/document in server context",
                "Add hydration tests to CI",
            ],
        },
        "bundle_size_regression": {
            "title": "Bundle Size Regression (+200KB)",
            "severity": "P3",
            "root_cause": "New dependency imported at top-level → not tree-shaken",
            "resolution": "Dynamic import for heavy dependency",
            "prevention": [
                "Add bundle size check to CI (bundlesize, size-limit)",
                "Review imports in PR — avoid top-level heavy imports",
                "Use Stencil's lazy-loading output target",
                "Monitor bundle size trend dashboard",
            ],
        },
        "memory_leak": {
            "title": "Memory Leak in Component",
            "severity": "P2",
            "root_cause": "Event listener not removed in disconnectedCallback",
            "resolution": "Add cleanup in disconnectedCallback",
            "prevention": [
                "Linting rule: require disconnectedCallback cleanup",
                "Code review checklist: event listener cleanup",
                "Memory profiling in CI (Playwright + Chrome DevTools)",
                "Add leak detection tests",
            ],
        },
        "style_bleeding": {
            "title": "CSS Style Bleeding Between Components",
            "severity": "P3",
            "root_cause": "Component ใช้ shadow: false → global CSS leak",
            "resolution": "Enable Shadow DOM หรือ scope CSS manually",
            "prevention": [
                "Default shadow: true สำหรับทุก components",
                "CSS review: avoid global selectors",
                "Visual regression tests (Chromatic)",
            ],
        },
    }

    def show_incidents(self):
        print("=== Common Stencil.js Incidents ===\n")
        for key, inc in self.INCIDENTS.items():
            print(f"[{inc['severity']}] {inc['title']}")
            print(f"  Root cause: {inc['root_cause']}")
            print(f"  Resolution: {inc['resolution']}")
            print(f"  Prevention:")
            for p in inc["prevention"][:2]:
                print(f"    • {p}")
            print()

incidents = StencilIncidents()
incidents.show_incidents()

Automated Post-mortem Tools

# tools.py — Automated post-mortem tools
import json
import random
from datetime import datetime, timedelta

class PostMortemTools:
    CODE = """
# auto_postmortem.py — Automated post-mortem generator
import json
from datetime import datetime

class AutoPostMortem:
    def __init__(self):
        self.incidents = []
    
    def create_incident(self, title, severity, description):
        incident = {
            "id": f"INC-{len(self.incidents)+1:04d}",
            "title": title,
            "severity": severity,
            "status": "investigating",
            "created_at": datetime.utcnow().isoformat(),
            "timeline": [
                {"time": datetime.utcnow().isoformat(), "event": "Incident created"},
            ],
            "description": description,
        }
        self.incidents.append(incident)
        return incident
    
    def add_timeline(self, incident_id, event):
        for inc in self.incidents:
            if inc["id"] == incident_id:
                inc["timeline"].append({
                    "time": datetime.utcnow().isoformat(),
                    "event": event,
                })
                return True
        return False
    
    def resolve(self, incident_id, root_cause, resolution, action_items):
        for inc in self.incidents:
            if inc["id"] == incident_id:
                inc["status"] = "resolved"
                inc["resolved_at"] = datetime.utcnow().isoformat()
                inc["root_cause"] = root_cause
                inc["resolution"] = resolution
                inc["action_items"] = action_items
                return inc
        return None
    
    def generate_report(self, incident_id):
        for inc in self.incidents:
            if inc["id"] == incident_id:
                report = f"# Post-mortem: {inc['title']}\\n"
                report += f"Severity: {inc['severity']}\\n"
                report += f"Status: {inc['status']}\\n"
                report += f"\\n## Timeline\\n"
                for t in inc["timeline"]:
                    report += f"- {t['time']}: {t['event']}\\n"
                if inc.get("root_cause"):
                    report += f"\\n## Root Cause\\n{inc['root_cause']}\\n"
                if inc.get("action_items"):
                    report += f"\\n## Action Items\\n"
                    for ai in inc["action_items"]:
                        report += f"- [ ] {ai}\\n"
                return report
        return None

pm = AutoPostMortem()
inc = pm.create_incident("Stencil Build Failure", "P2", "Production build failed")
pm.add_timeline(inc["id"], "Root cause identified: TS version mismatch")
pm.resolve(inc["id"], "TypeScript 5.x feature used, CI has TS 4.9",
           "Pinned TS version to 5.3.3",
           ["Add TS version check to CI", "Update docs with compatibility matrix"])
print(pm.generate_report(inc["id"]))
"""

    def show_code(self):
        print("=== Auto Post-mortem Tool ===")
        print(self.CODE[:600])

    def metrics_dashboard(self):
        print(f"\n=== Incident Metrics (Last 90 days) ===")
        print(f"  Total incidents: {random.randint(5, 15)}")
        print(f"  P1: {random.randint(0, 2)} | P2: {random.randint(1, 5)} | P3: {random.randint(2, 8)} | P4: {random.randint(0, 3)}")
        print(f"  MTTR (Mean Time to Resolve): {random.randint(30, 180)} minutes")
        print(f"  MTTD (Mean Time to Detect): {random.randint(5, 30)} minutes")
        print(f"  Repeat incidents: {random.randint(0, 3)}")
        print(f"  Action items completed: {random.randint(70, 95)}%")

tools = PostMortemTools()
tools.show_code()
tools.metrics_dashboard()

Prevention & Best Practices

# prevention.py — Prevention best practices
import json

class Prevention:
    CI_CHECKS = {
        "build": "Stencil build ต้อง pass ทุก PR",
        "test": "Unit tests + visual regression tests",
        "bundle_size": "Bundle size limit check (size-limit)",
        "a11y": "Accessibility audit (axe-core)",
        "type_check": "TypeScript strict mode check",
        "lint": "ESLint + Stylelint",
        "version_check": "Node.js + TypeScript version compatibility",
    }

    MONITORING = {
        "error_tracking": "Sentry — track runtime errors ใน Web Components",
        "performance": "Web Vitals — LCP, FID, CLS ของ pages ที่ใช้ components",
        "bundle_trend": "Bundle size trend — track regression over time",
        "usage_analytics": "Component usage analytics — ใครใช้ component ไหน",
    }

    BLAMELESS_CULTURE = [
        "Focus on systems ไม่ใช่ individuals",
        "ทุกู้คืนทำผิดได้ — ระบบต้องป้องกัน",
        "Post-mortem = learning opportunity ไม่ใช่ blame session",
        "Share post-mortems openly — ทั้งองค์กรเรียนรู้",
        "Action items ต้องมี owner + deadline",
        "Review action items ทุกสัปดาห์ — ไม่ปล่อยค้าง",
    ]

    def show_ci(self):
        print("=== CI/CD Checks ===\n")
        for check, desc in self.CI_CHECKS.items():
            print(f"  [{check}] {desc}")

    def show_monitoring(self):
        print(f"\n=== Monitoring ===")
        for key, desc in self.MONITORING.items():
            print(f"  [{key}] {desc}")

    def show_culture(self):
        print(f"\n=== Blameless Culture ===")
        for principle in self.BLAMELESS_CULTURE[:4]:
            print(f"  • {principle}")

prev = Prevention()
prev.show_ci()
prev.show_monitoring()
prev.show_culture()

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

Q: Stencil.js กับ Lit อันไหนดี?

A: Stencil: compiler-based, TypeScript + JSX, lazy-loading built-in, Ionic ecosystem Lit: runtime library (~5KB), simpler, Google-backed, larger community ใช้ Stencil: Ionic projects, ต้องการ TypeScript + JSX, lazy-loading ใช้ Lit: lightweight, ไม่ต้องการ build step, simpler API ทั้งคู่ output standard Web Components — เลือกตาม team preference

Q: Post-mortem ต้องทำทุก incident ไหม?

A: P1-P2: ต้องทำทุกครั้ง (within 48 hours) P3: ทำเมื่อมี lessons สำคัญ หรือ repeat incident P4: ไม่จำเป็น — track ใน issue tracker Format: สั้น กระชับ — ไม่ต้องยาวเป็นหน้าๆ สำคัญ: action items ต้องมี owner + deadline + follow-up

Q: 5 Whys ใช้ยังไง?

A: ถาม "ทำไม" ซ้ำๆ จนถึง root cause (ปกติ 3-5 ครั้ง) เริ่มจาก symptom → drill down จนถึงสิ่งที่ fix ได้ อย่าหยุดที่ "human error" — ถามต่อว่าทำไมระบบไม่ป้องกัน ตัวอย่าง: Build failed → TS version mismatch → ไม่มี version pin → ไม่มี policy → สร้าง policy + CI check

Q: Blameless post-mortem สำคัญอย่างไร?

A: ถ้า blame คน → คนซ่อนปัญหา → ปัญหาไม่ถูกแก้ → เกิดซ้ำ ถ้า blameless → คนกล้าพูด → เรียนรู้จากปัญหา → ป้องกันได้ Focus: ระบบ + process ต้องดีพอที่จะป้องกัน human error วัฒนธรรม: "ทำไมระบบยอมให้สิ่งนี้เกิด?" ไม่ใช่ "ใครทำ?"

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

Fivetran Connector Post-mortem Analysisอ่านบทความ → Apache Kafka Streams Post-mortem Analysisอ่านบทความ → Whisper Speech Post-mortem Analysisอ่านบทความ → Strapi CMS Post-mortem Analysisอ่านบทความ → AlmaLinux Setup Post-mortem Analysisอ่านบทความ →

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