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 วัฒนธรรม: "ทำไมระบบยอมให้สิ่งนี้เกิด?" ไม่ใช่ "ใครทำ?"
