วิธีที่ Nordhealth ใช้พร็อพเพอร์ตี้ที่กำหนดเองในคอมโพเนนต์เว็บ

ประโยชน์ของการใช้พร็อพเพอร์ตี้ที่กำหนดเองในระบบการออกแบบและคลังคอมโพเนนต์

David Darnes
David Darnes

ผมชื่อ Dave และเป็นนักพัฒนาฟรอนท์เอนด์อาวุโสที่ Nordhealth เราทำงานด้านการออกแบบและพัฒนาระบบการออกแบบ Nord ซึ่งรวมถึงการสร้างคอมโพเนนต์เว็บสำหรับคลังคอมโพเนนต์ เราอยากจะแชร์วิธีแก้ปัญหาเกี่ยวกับการจัดรูปแบบ Web Components โดยใช้พร็อพเพอร์ตี้ CSS ที่กำหนดเอง รวมถึงประโยชน์อื่นๆ บางส่วนของการใช้พร็อพเพอร์ตี้ที่กำหนดเองในระบบการออกแบบและไลบรารีคอมโพเนนต์

วิธีสร้างคอมโพเนนต์เว็บ

เราใช้ Lit ซึ่งเป็นไลบรารีที่มีโค้ดบอยเลอร์เพลตจำนวนมาก เช่น สถานะ สไตล์ที่กำหนดขอบเขต การสร้างเทมเพลต และอื่นๆ เพื่อสร้างคอมโพเนนต์ของเว็บ Lit ไม่ได้มีขนาดเล็กเท่านั้น แต่ยังสร้างขึ้นจาก JavaScript API ดั้งเดิมด้วย ซึ่งหมายความว่าเราสามารถส่งชุดโค้ดที่มีขนาดเล็กซึ่งใช้ประโยชน์จากฟีเจอร์ที่เบราว์เซอร์มีอยู่แล้วได้


import {html, css, LitElement} from 'lit';

export class SimpleGreeting extends LitElement {
  static styles = css`:host { color: blue; font-family: sans-serif; }`;

  static properties = {
    name: {type: String},
  };

  constructor() {
    super();
    this.name = 'there';
  }

  render() {
    return html`

Hey ${this.name}, welcome to Web Components!

`
; } } customElements.define('simple-greeting', SimpleGreeting);
คอมโพเนนต์ของเว็บที่เขียนด้วย Lit

แต่สิ่งที่น่าสนใจที่สุดเกี่ยวกับ Web Components คือการทำงานร่วมกับเฟรมเวิร์ก JavaScript ที่มีอยู่เกือบทุกเฟรมเวิร์ก หรือแม้แต่ไม่มีเฟรมเวิร์กเลยก็ได้ เมื่อมีการอ้างอิงแพ็กเกจ JavaScript หลักในหน้าเว็บแล้ว การใช้ Web Component ก็จะคล้ายกับการใช้องค์ประกอบ HTML ดั้งเดิมมาก สัญญาณที่บ่งบอกว่าไม่ใช่องค์ประกอบ HTML ดั้งเดิมคือเครื่องหมายขีดกลางที่สอดคล้องกันภายในแท็ก ซึ่งเป็นมาตรฐานที่ใช้เพื่อระบุให้เบราว์เซอร์ทราบว่านี่คือ Web Component

การห่อหุ้มสไตล์ Shadow DOM

Web Components มี Shadow DOM เช่นเดียวกับองค์ประกอบ HTML ที่มีอยู่แต่เดิม Shadow DOM คือแผนผังโหนดที่ซ่อนอยู่ภายในองค์ประกอบ วิธีที่ดีที่สุดในการแสดงภาพนี้คือการเปิดเครื่องมือตรวจสอบเว็บและเปิดตัวเลือก "แสดงโครงสร้าง Shadow DOM" เมื่อทำเสร็จแล้ว ให้ลองดูองค์ประกอบอินพุตดั้งเดิมในเครื่องมือตรวจสอบ คุณจะมีตัวเลือกในการเปิดอินพุตนั้นและดูองค์ประกอบทั้งหมดภายใน คุณยังลองใช้ฟีเจอร์นี้กับคอมโพเนนต์เว็บของเราได้ด้วย ลองตรวจสอบคอมโพเนนต์อินพุตที่กำหนดเองเพื่อดู Shadow DOM

Shadow DOM ที่ตรวจสอบในเครื่องมือสำหรับนักพัฒนาเว็บ
ตัวอย่างของ Shadow DOM ในองค์ประกอบอินพุตข้อความปกติและในคอมโพเนนต์เว็บอินพุต Nord ของเรา

ข้อดีอย่างหนึ่ง (หรือข้อเสีย ขึ้นอยู่กับมุมมองของคุณ) ของ Shadow DOM คือการห่อหุ้มสไตล์ หากคุณเขียน CSS ภายใน Web Component สไตล์เหล่านั้นจะรั่วไหลและส่งผลต่อหน้าหลักหรือองค์ประกอบอื่นๆ ไม่ได้ เนื่องจากจะอยู่ในคอมโพเนนต์อย่างสมบูรณ์ นอกจากนี้ CSS ที่เขียนสําหรับหน้าหลักหรือ Web Component หลักจะรั่วไหลไปยัง Web Component ของคุณไม่ได้

การห่อหุ้มสไตล์นี้เป็นประโยชน์ในไลบรารีคอมโพเนนต์ของเรา ซึ่งช่วยให้เรามั่นใจได้มากขึ้นว่าเมื่อมีผู้ใช้คอมโพเนนต์ของเรา คอมโพเนนต์นั้นจะแสดงผลตามที่เราตั้งใจไว้ ไม่ว่าหน้าหลักจะใช้สไตล์ใดก็ตาม และเพื่อความมั่นใจยิ่งขึ้น เราจึงเพิ่ม all: unset; ไปยังรูทหรือ "โฮสต์" ของ Web Component ทั้งหมด


:host {
  all: unset;
  display: block;
  box-sizing: border-box;
  text-align: start;
  /* ... */
}
มีการใช้โค้ดบอยเลอร์เพลตของคอมโพเนนต์บางรายการกับ Shadow Root หรือตัวเลือกโฮสต์

แต่จะเกิดอะไรขึ้นหากผู้ที่ใช้คอมโพเนนต์เว็บของคุณมีเหตุผลที่สมควรในการเปลี่ยนรูปแบบบางอย่าง อาจมีข้อความที่ต้องมีความคมชัดมากขึ้นเนื่องจากบริบท หรือเส้นขอบที่ต้องหนาขึ้น หากไม่มีสไตล์ใดที่ใช้กับคอมโพเนนต์ได้ คุณจะปลดล็อกตัวเลือกการจัดรูปแบบเหล่านั้นได้อย่างไร

ตรงนี้เองที่พร็อพเพอร์ตี้ที่กำหนดเองของ CSS จะเข้ามามีบทบาท

พร็อพเพอร์ตี้ที่กำหนดเองของ CSS

พร็อพเพอร์ตี้ที่กำหนดเองมีชื่อที่เหมาะสมมาก ซึ่งเป็นพร็อพเพอร์ตี้ CSS ที่คุณตั้งชื่อเองทั้งหมดและใช้ค่าที่ต้องการได้ ข้อกำหนดอย่างเดียวคือคุณต้องนำหน้าด้วยขีดกลาง 2 ขีด เมื่อประกาศพร็อพเพอร์ตี้ที่กำหนดเองแล้ว คุณจะใช้ค่าใน CSS ได้โดยใช้ฟังก์ชัน var()


:root {
  --n-color-accent: rgb(53, 89, 199);
  /* ... */
}

.n-color-accent-text {
  color: var(--n-color-accent);
}
ตัวอย่างจากเฟรมเวิร์ก CSS ของเราเกี่ยวกับโทเค็นการออกแบบเป็นพร็อพเพอร์ตี้ที่กำหนดเองและการใช้งานในคลาสตัวช่วย

เมื่อพูดถึงการรับค่า คุณสมบัติที่กำหนดเองทั้งหมดจะได้รับการรับค่า ซึ่งเป็นไปตามลักษณะการทำงานปกติของพร็อพเพอร์ตี้และค่า CSS ปกติ พร็อพเพอร์ตี้ที่กำหนดเองซึ่งใช้กับองค์ประกอบระดับบนหรือองค์ประกอบนั้นๆ สามารถใช้เป็นค่าในพร็อพเพอร์ตี้อื่นๆ ได้ เราใช้พร็อพเพอร์ตี้ที่กำหนดเองอย่างมากสำหรับโทเค็นการออกแบบโดยการนำไปใช้กับองค์ประกอบรูทผ่านเฟรมเวิร์ก CSS ซึ่งหมายความว่าองค์ประกอบทั้งหมดในหน้าเว็บสามารถใช้ค่าโทเค็นเหล่านี้ได้ ไม่ว่าจะเป็นคอมโพเนนต์เว็บ คลาสตัวช่วย CSS หรือนักพัฒนาซอฟต์แวร์ที่ต้องการดึงค่าจากรายการโทเค็นของเรา

ความสามารถในการรับค่าพร็อพเพอร์ตี้ที่กำหนดเองด้วยการใช้ฟังก์ชัน var() นี้ช่วยให้เราเจาะผ่าน Shadow DOM ของคอมโพเนนต์เว็บและช่วยให้นักพัฒนาแอปควบคุมการจัดรูปแบบคอมโพเนนต์ได้ละเอียดยิ่งขึ้น

พร็อพเพอร์ตี้ที่กำหนดเองในคอมโพเนนต์เว็บ Nord

ทุกครั้งที่เราพัฒนาคอมโพเนนต์สำหรับระบบการออกแบบ เราจะใช้แนวทางที่รอบคอบกับ CSS ของคอมโพเนนต์นั้น โดยเราต้องการให้โค้ดมีขนาดเล็กแต่ดูแลรักษาได้ง่าย โทเค็นการออกแบบที่เรามีจะกำหนดเป็นพร็อพเพอร์ตี้ที่กำหนดเองภายในเฟรมเวิร์ก CSS หลักในองค์ประกอบรูท


:root {
  --n-space-m: 16px;
  --n-space-l: 24px;
  /* ... */
  --n-color-background: rgb(255, 255, 255);
  --n-color-border: rgb(216, 222, 228);
  /* ... */
}
พร็อพเพอร์ตี้ CSS ที่กำหนดเองซึ่งกำหนดไว้ในตัวเลือกรูท

จากนั้นค่าโทเค็นเหล่านี้จะได้รับการอ้างอิงภายในคอมโพเนนต์ของเรา ในบางกรณี เราจะใช้ค่ากับพร็อพเพอร์ตี้ CSS โดยตรง แต่ในกรณีอื่นๆ เราจะกำหนดพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทใหม่และใช้ค่ากับพร็อพเพอร์ตี้นั้น


:host {
  --n-tab-group-padding: 0;
  --n-tab-list-background: var(--n-color-background);
  --n-tab-list-border: inset 0 -1px 0 0 var(--n-color-border);
  /* ... */
}

.n-tab-group-list {
  box-shadow: var(--n-tab-list-border);
  background-color: var(--n-tab-list-background);
  gap: var(--n-space-s);
  /* ... */
}
พร็อพเพอร์ตี้ที่กำหนดเองที่กำหนดไว้ใน Shadow Root ของคอมโพเนนต์ แล้วนำไปใช้ในรูปแบบคอมโพเนนต์ นอกจากนี้ยังมีการใช้พร็อพเพอร์ตี้ที่กำหนดเองจากรายการโทเค็นการออกแบบด้วย

นอกจากนี้ เราจะดึงค่าบางอย่างที่เฉพาะเจาะจงกับคอมโพเนนต์แต่ไม่ได้อยู่ในโทเค็นของเรา และเปลี่ยนค่าเหล่านั้นให้เป็นพร็อพเพอร์ตี้ที่กำหนดเองตามบริบท พร็อพเพอร์ตี้ที่กำหนดเองซึ่งเกี่ยวข้องกับคอมโพเนนต์จะให้ประโยชน์ที่สำคัญ 2 ประการแก่เรา ประการแรกคือเราสามารถใช้ CSS ได้อย่าง "กระชับ" มากขึ้นเนื่องจากค่าดังกล่าวสามารถใช้กับพร็อพเพอร์ตี้หลายรายการภายในคอมโพเนนต์ได้


.n-tab-group-list::before {
  /* ... */
  padding-inline-start: var(--n-tab-group-padding);
}

.n-tab-group-list::after {
  /* ... */
  padding-inline-end: var(--n-tab-group-padding);
}
พร็อพเพอร์ตี้ที่กำหนดเองตามบริบทของระยะขอบของกลุ่มแท็บที่ใช้ในหลายที่ภายในโค้ดคอมโพเนนต์

และประการที่ 2 การเปลี่ยนสถานะและรูปแบบของคอมโพเนนต์จะดูสะอาดตามาก เพราะมีเพียงพร็อพเพอร์ตี้ที่กำหนดเองเท่านั้นที่ต้องเปลี่ยนเพื่ออัปเดตพร็อพเพอร์ตี้ทั้งหมดเมื่อคุณจัดรูปแบบสถานะเมื่อวางเมาส์หรือสถานะที่ใช้งานอยู่ หรือในกรณีนี้คือรูปแบบ


:host([padding="l"]) {
  --n-tab-group-padding: var(--n-space-l);
}
รูปแบบของคอมโพเนนต์แท็บที่เปลี่ยนระยะห่างโดยใช้การอัปเดตพร็อพเพอร์ตี้ที่กำหนดเองเพียงครั้งเดียวแทนการอัปเดตหลายครั้ง

แต่ประโยชน์ที่ทรงพลังที่สุดคือเมื่อเรากำหนดพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเหล่านี้ในคอมโพเนนต์ เราจะสร้าง API ของ CSS ที่กำหนดเองสำหรับคอมโพเนนต์แต่ละรายการ ซึ่งผู้ใช้คอมโพเนนต์นั้นๆ สามารถใช้ได้


<nord-tab-group label="T>itl<e"
  >!<-- ... --
/nord>-t<ab-gr>oup

style
  nord-tab-group {
    --n-tab-group-padding: var(--n-space<-xl);
>  }
/style
ใช้คอมโพเนนต์กลุ่มแท็บบนหน้าเว็บและอัปเดตพร็อพเพอร์ตี้ที่กำหนดเองของการเว้นวรรคให้มีขนาดใหญ่ขึ้น

ตัวอย่างก่อนหน้าแสดงคอมโพเนนต์ของเว็บรายการหนึ่งของเราที่มีพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทซึ่งเปลี่ยนผ่านตัวเลือก ผลลัพธ์ของแนวทางทั้งหมดนี้คือคอมโพเนนต์ที่มีความยืดหยุ่นในการจัดรูปแบบเพียงพอสำหรับผู้ใช้ ในขณะที่ยังคงรักษาการจัดรูปแบบจริงส่วนใหญ่ไว้ นอกจากนี้ ในฐานะนักพัฒนาคอมโพเนนต์ เรายังมีความสามารถในการสกัดกั้นสไตล์ที่ผู้ใช้ใช้ได้อีกด้วย หากต้องการปรับหรือขยายพร็อพเพอร์ตี้ใดพร็อพเพอร์ตี้หนึ่ง เราก็ทำได้โดยที่ผู้ใช้ไม่ต้องเปลี่ยนโค้ด

เราพบว่าแนวทางนี้มีประสิทธิภาพอย่างยิ่ง ไม่เพียงแต่สำหรับเราในฐานะผู้สร้างคอมโพเนนต์ของระบบการออกแบบเท่านั้น แต่ยังรวมถึงทีมพัฒนาเมื่อใช้คอมโพเนนต์เหล่านี้ในผลิตภัณฑ์ของเราด้วย

การใช้คุณสมบัติที่กำหนดเองในขั้นสูง

ในขณะที่เขียน เราไม่ได้เปิดเผยพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเหล่านี้ในเอกสารประกอบ แต่เราวางแผนที่จะทำเพื่อให้ทีมพัฒนาที่กว้างขึ้นของเราเข้าใจและใช้ประโยชน์จากพร็อพเพอร์ตี้เหล่านี้ได้ เราแพ็กเกจคอมโพเนนต์ใน npm ด้วยไฟล์ Manifest ซึ่งมีข้อมูลทุกอย่างที่ควรรู้เกี่ยวกับคอมโพเนนต์ จากนั้นเราจะใช้ไฟล์ Manifest เป็นข้อมูลเมื่อมีการติดตั้งใช้งานเว็บไซต์เอกสารประกอบ ซึ่งทำได้โดยใช้ Eleventy และฟีเจอร์ข้อมูลส่วนกลาง เราวางแผนที่จะรวมพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเหล่านี้ไว้ในไฟล์ข้อมูล Manifest นี้

อีกด้านหนึ่งที่เราต้องการปรับปรุงคือวิธีที่พร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเหล่านี้รับค่า ปัจจุบัน เช่น หากต้องการปรับสีของคอมโพเนนต์ตัวคั่น 2 รายการ คุณจะต้องกำหนดเป้าหมายคอมโพเนนต์ทั้ง 2 รายการนั้นโดยเฉพาะด้วยตัวเลือก หรือใช้พร็อพเพอร์ตี้ที่กำหนดเองโดยตรงกับองค์ประกอบที่มีแอตทริบิวต์สไตล์ ซึ่งอาจดูเหมือนไม่มีปัญหา แต่จะดีกว่านี้หากนักพัฒนาซอฟต์แวร์กำหนดรูปแบบเหล่านั้นในองค์ประกอบที่ประกอบด้วยหรือแม้แต่ในระดับรูทได้


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
  }
  
  section nord-divider {
    --n-divider-color: var(--n-color-status-success);
  }
</style>
อินสแตนซ์ 2 รายการของคอมโพเนนต์ตัวคั่นซึ่งต้องมีการกำหนดสีที่แตกต่างกัน 2 แบบ โดยมีเส้นคั่นหนึ่งเส้นซ้อนอยู่ภายในส่วน ซึ่งเราสามารถใช้สำหรับตัวเลือกที่เฉพาะเจาะจงมากขึ้นได้ แต่เราต้องกำหนดเป้าหมายเส้นคั่นโดยเฉพาะ

สาเหตุที่คุณต้องตั้งค่าพร็อพเพอร์ตี้ที่กำหนดเองในคอมโพเนนต์โดยตรงก็เนื่องจากเรากําลังกําหนดค่าเหล่านั้นในองค์ประกอบเดียวกันผ่านตัวเลือกโฮสต์คอมโพเนนต์ โทเค็นการออกแบบส่วนกลางที่เราใช้ในคอมโพเนนต์โดยตรงจะส่งผ่านโดยไม่มีผลกระทบจากปัญหานี้ และยังสามารถดักจับในองค์ประกอบระดับบนได้ด้วย เราจะใช้ประโยชน์จากทั้ง 2 อย่างนี้ได้อย่างไร

พร็อพเพอร์ตี้ที่กำหนดเองแบบส่วนตัวและแบบสาธารณะ

พร็อพเพอร์ตี้ที่กำหนดเองแบบส่วนตัวคือสิ่งที่ Lea Verou ได้รวบรวมไว้ ซึ่งเป็นพร็อพเพอร์ตี้ที่กำหนดเองแบบ "ส่วนตัว" ตามบริบทในคอมโพเนนต์เอง แต่ตั้งค่าเป็นพร็อพเพอร์ตี้ที่กำหนดเองแบบ "สาธารณะ" พร้อมด้วยการสำรอง



:host {
  --_n-divider-color: var(--n-divider-color, var(--n-color-border));
  --_n-divider-size: var(--n-divider-size, 1px);
}

.n-divider {
  border-block-start: solid var(--_n-divider-size) var(--_n-divider-color);
  /* ... */
}
CSS ของคอมโพเนนต์เว็บตัวคั่นที่มีการปรับพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทเพื่อให้ CSS ภายในอาศัยพร็อพเพอร์ตี้ที่กำหนดเองแบบส่วนตัว ซึ่งตั้งค่าเป็นพร็อพเพอร์ตี้ที่กำหนดเองแบบสาธารณะที่มีการสำรอง

การกำหนดพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทในลักษณะนี้หมายความว่าเรายังคงทำทุกอย่างได้เหมือนเดิม เช่น การรับค่าโทเค็นส่วนกลางและการนำค่ากลับมาใช้ใหม่ในโค้ดคอมโพเนนต์ แต่คอมโพเนนต์จะรับคำจำกัดความใหม่ของพร็อพเพอร์ตี้นั้นในตัวคอมโพเนนต์เองหรือองค์ประกอบระดับบนสุดอย่างราบรื่นด้วย


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
    --n-divider-color: var(--n-color-status-success);
  }
</style>
เส้นแบ่ง 2 เส้นอีกครั้ง แต่คราวนี้คุณเปลี่ยนสีเส้นแบ่งได้โดยเพิ่มพร็อพเพอร์ตี้ที่กำหนดเองตามบริบทของเส้นแบ่งลงในตัวเลือกส่วน โดยเส้นแบ่งจะรับค่าดังกล่าว ทำให้โค้ดสะอาดและยืดหยุ่นมากขึ้น

แม้ว่าอาจมีข้อโต้แย้งว่าวิธีนี้ไม่ได้ "เป็นส่วนตัว" อย่างแท้จริง แต่เรายังคงคิดว่านี่เป็นวิธีแก้ปัญหาที่เรากังวลได้อย่างยอดเยี่ยม เมื่อมีโอกาส เราจะจัดการปัญหานี้ในคอมโพเนนต์เพื่อให้ทีมพัฒนาควบคุมการใช้งานคอมโพเนนต์ได้มากขึ้น พร้อมทั้งยังคงได้รับประโยชน์จากแนวทางที่เรามีอยู่

หวังว่าข้อมูลเชิงลึกเกี่ยวกับวิธีที่เราใช้ Web Components กับพร็อพเพอร์ตี้ที่กำหนดเองของ CSS จะเป็นประโยชน์กับคุณ โปรดแจ้งให้เราทราบว่าคุณคิดอย่างไร และหากตัดสินใจที่จะใช้วิธีใดวิธีหนึ่งเหล่านี้ในงานของคุณเอง คุณสามารถติดต่อฉันได้ทาง Twitter @DavidDarnes นอกจากนี้ คุณยังติดตาม Nordhealth @NordhealthHQ บน Twitter รวมถึงสมาชิกคนอื่นๆ ในทีมของฉันได้ ซึ่งทุกคนได้ทุ่มเทอย่างหนักเพื่อรวบรวมระบบการออกแบบนี้และดำเนินการฟีเจอร์ที่กล่าวถึงในบทความนี้ ได้แก่ @Viljamis, @WickyNilliams และ @eric_habich

รูปภาพหลักโดย Dan Cristian Pădureț