ข้ามไปยังเนื้อหา

การเผยแพร่และการรองรับบน browser

web component ก็เป็นเพียง JavaScript module ตัวหนึ่ง เผยแพร่ด้วยวิธีเดียวกับ ES module package ทั่วไป ตัวอย่าง package.json แบบขั้นต่ำ:

{
"name": "my-counter",
"version": "1.0.0",
"type": "module",
"main": "./dist/my-counter.js",
"module": "./dist/my-counter.js",
"exports": {
".": "./dist/my-counter.js"
},
"files": ["dist"],
"keywords": ["web-components", "custom-element"]
}

ผู้ใช้ติดตั้งด้วย npm install my-counter แล้ว import เพียงครั้งเดียว (การ import แบบ side-effect ก็เพียงพอที่จะลงทะเบียน tag):

import 'my-counter';
// <my-counter> is now usable anywhere in the document

Custom Elements Manifest (custom-elements.json) เป็นไฟล์ JSON ที่เครื่องอ่านได้ ซึ่งทำเอกสารกำกับ custom element ทุกตัวในแพ็กเกจ ไม่ว่าจะเป็น tag name, properties, attributes, events และ CSS custom properties เครื่องมืออย่าง Storybook, IDEs และแดชบอร์ดของ design system ต่างนำไฟล์นี้ไปใช้ทำ auto-complete และเอกสารประกอบ

สร้างไฟล์นี้ได้ด้วย CLI @custom-elements-manifest/analyzer:

Terminal window
npm install --save-dev @custom-elements-manifest/analyzer
npx cem analyze --globs "src/**/*.js"

ตัวอย่างส่วนหนึ่งของผลลัพธ์ที่ได้:

{
"schemaVersion": "1.0.0",
"modules": [{
"kind": "javascript-module",
"path": "src/my-counter.js",
"declarations": [{
"kind": "class",
"name": "MyCounter",
"customElement": true,
"tagName": "my-counter",
"attributes": [
{ "name": "label", "type": { "text": "string" } }
],
"members": [
{ "kind": "field", "name": "count", "type": { "text": "number" } }
]
}]
}]
}

ปฏิบัติตาม semantic versioning การเปลี่ยนแปลงที่ถือว่าเป็น breaking changes ใน custom element ได้แก่:

  • การลบหรือเปลี่ยนชื่อ attribute, property หรือ event
  • การเปลี่ยน tag name ของ element
  • การเปลี่ยนรูปร่างของอ็อบเจกต์ detail ใน custom event

ส่วนการเปลี่ยนแปลงที่ไม่ใช่ breaking ได้แก่ การเพิ่ม attribute ที่เป็น optional ตัวใหม่ การเพิ่ม CSS custom properties ใหม่ หรือการเพิ่ม public methods ใหม่

ณ ปี 2025 Custom Elements v1, Shadow DOM v1 และ HTML Templates ได้รับการรองรับใน browser หลักทุกตัว ทั้ง Chrome, Firefox, Safari และ Edge (ทุกเวอร์ชันปัจจุบันและเวอร์ชันก่อนหน้าส่วนใหญ่) ป้าย “Baseline Widely Available” ครอบคลุมทั้งสามเสาหลักนี้

ข้อยกเว้นหนึ่งเดียว: customized built-ins (element ที่ extend built-in อย่าง HTMLButtonElement ด้วย is="...") ไม่ รองรับใน Safari ควรเลือกใช้ autonomous custom elements (extends HTMLElement) เพื่อความเข้ากันได้สูงสุด

ตัว Lit เองรองรับ Chrome 104+, Firefox 121+, Safari 16.4+ สำหรับแอปที่ใช้งานจริงซึ่งต้องรองรับ browser เก่า แพ็กเกจ @webcomponents/polyfills จะช่วยอุดช่องว่างเหล่านั้นได้

เดโมด้านล่างแสดง badge element ที่เผยแพร่ได้แบบง่าย ๆ ซึ่งสะท้อน “ความรู้สึกของแพ็กเกจที่เผยแพร่แล้ว” คือสิ่งที่ผู้ใช้จะเห็นพอดีหลังติดตั้ง component ของคุณจาก npm

ตัวเลือกBenefitCost
เผยแพร่เป็น unbundled ES module พร้อมประกาศ Lit เป็น peerDependenciesBundler ฝั่ง consumer สามารถ dedupe และ tree-shake Lit ได้เต็มที่ ลดโอกาสโหลดซ้ำต้องพึ่งพาว่า consumer มี bundler ที่ resolve ESM ได้ถูกต้อง
Bundle/minify Lit เข้าไปในไฟล์ package ของตัวเองใช้งานได้ทันทีแม้ consumer ไม่มี build stepเสี่ยงโหลด Lit runtime ซ้ำหลายชุดเมื่อหน้าเดียวมี component จากหลาย vendor ทำให้ bundle บวม
  • Bundle Lit runtime เข้าไปในทุก component ที่เผยแพร่ แทนที่จะประกาศเป็น shared dependency — ถ้าหน้าเว็บใช้ component จากหลาย vendor ผู้ใช้จะโหลด Lit ซ้ำหลายชุดโดยไม่รู้ตัว ทำให้ bundle บวมเกินจำเป็น
  • เผยแพร่เฉพาะไฟล์ที่ minify/bundle แล้วเท่านั้น (ไม่มี unbundled ESM ให้เลือก) — ทำลายความสามารถ tree-shaking และ code-splitting ของ bundler ฝั่ง consumer
  • ลืมระบุ browser support หรือ polyfill requirement ไว้ใน README — ผู้ใช้ที่ต้อง support browser เก่าจะไม่รู้ว่าต้องติดตั้ง @webcomponents/polyfills เพิ่มเอง

💡 ตัวอย่างจากของจริง

Home Assistant — เผยแพร่และดูแล custom elements จำนวนมากที่ใช้ Lit เป็น shared dependency ตัวเดียวทั่วทั้งแอป แทนที่จะ bundle Lit แยกในแต่ละ element

Google Material Web — เผยแพร่แต่ละ component เป็น unbundled ES module แยกตัว ทำให้ consumer เลือก import และ tree-shake เฉพาะส่วนที่ใช้ได้

จุดประสงค์ของไฟล์ Custom Elements Manifest คืออะไร?
custom element ประเภทใดที่ไม่รองรับใน Safari?
การ import 'my-counter' (การ import แบบ side-effect) ทำอะไร?