재사용 가능한 구성 요소의 성배: 사용자 지정 요소, Shadow DOM 및 NPM
게시 됨: 2022-03-10가장 단순한 구성 요소의 경우에도 인력 노동 비용이 상당했을 수 있습니다. UX 팀은 사용성 테스트를 수행합니다. 일련의 이해 관계자가 설계를 승인해야 합니다.
개발자는 AB 테스트, 접근성 감사, 단위 테스트 및 브라우저 간 검사를 수행합니다. 문제를 해결한 후에는 그 노력을 반복하고 싶지 않습니다 . 재사용 가능한 구성 요소 라이브러리를 구축함으로써(처음부터 모든 것을 구축하는 대신) 과거의 노력을 지속적으로 활용하고 이미 해결된 설계 및 개발 과제를 다시 방문하지 않아도 됩니다.

구성 요소의 무기고를 구축하는 것은 공통 브랜드를 공유하는 상당한 웹사이트 포트폴리오를 소유한 Google과 같은 회사에 특히 유용합니다. UI를 구성 가능한 위젯으로 코드화함으로써 대기업은 개발 시간을 단축하고 프로젝트 전반에 걸쳐 시각적 및 사용자 상호 작용 디자인의 일관성을 달성할 수 있습니다. 지난 몇 년 동안 스타일 가이드와 패턴 라이브러리에 대한 관심이 높아졌습니다. 여러 개발자와 디자이너가 여러 팀에 분산되어 있다는 점을 감안할 때 대기업은 일관성을 유지하려고 합니다. 우리는 단순한 색상 견본보다 더 잘 할 수 있습니다. 우리에게 필요한 것은 쉽게 배포할 수 있는 코드 입니다.
코드 공유 및 재사용
코드를 수동으로 복사하여 붙여넣는 것은 어렵지 않습니다. 그러나 해당 코드를 최신 상태로 유지하는 것은 유지 관리의 악몽입니다. 따라서 많은 개발자가 패키지 관리자에 의존하여 프로젝트 전체에서 코드를 재사용합니다. 그 이름에도 불구하고 Node Package Manager는 프론트엔드 패키지 관리를 위한 타의 추종을 불허하는 플랫폼이 되었습니다. 현재 NPM 레지스트리에는 700,000개 이상의 패키지가 있으며 매달 수십억 개의 패키지가 다운로드됩니다. package.json 파일이 있는 모든 폴더는 공유 가능한 패키지로 NPM에 업로드할 수 있습니다. NPM은 주로 JavaScript와 연결되지만 패키지에는 CSS와 마크업이 포함될 수 있습니다. NPM을 사용하면 코드를 쉽게 재사용하고 업데이트 할 수 있습니다. 여기저기서 코드를 수정할 필요 없이 패키지에서만 코드를 변경하면 됩니다.
마크업 문제
Sass와 Javascript는 import 문을 사용하여 쉽게 이식할 수 있습니다. 템플릿 언어는 HTML에 동일한 기능을 제공합니다. 템플릿은 HTML의 다른 조각을 부분 형식으로 가져올 수 있습니다. 예를 들어 바닥글에 대한 마크업을 한 번만 작성한 다음 다른 템플릿에 포함할 수 있습니다. 템플릿 언어의 다양성이 존재한다고 말하는 것은 절제된 표현일 것입니다. 한 가지에만 자신을 묶는 것은 코드의 잠재적인 재사용성을 심각하게 제한합니다. 대안은 마크업을 복사하여 붙여넣고 스타일과 자바스크립트에만 NPM을 사용하는 것입니다.
이것은 Financial Times가 Origami 구성 요소 라이브러리를 사용하여 취한 접근 방식입니다. 그녀의 연설에서 "부트스트랩처럼 만들 수는 없나요?" Alice Bartlett은 "사람들이 프로젝트에 템플릿을 포함하게 하는 좋은 방법은 없습니다"라고 결론지었습니다. Lonely Planet에서 구성 요소 라이브러리를 유지 관리한 경험에 대해 Ian Feather는 이 접근 방식의 문제를 반복해서 말했습니다.
“한 번 해당 코드를 복사하면 기본적으로 무기한 유지 관리해야 하는 버전이 절단됩니다. 작업 구성 요소에 대한 마크업을 복사할 때 해당 시점의 CSS 스냅샷에 대한 암시적 링크가 있었습니다. 그런 다음 템플릿을 업데이트하거나 CSS를 리팩터링하면 사이트에 흩어져 있는 모든 버전의 템플릿을 업데이트해야 합니다.”
솔루션: 웹 구성 요소
웹 구성 요소는 JavaScript에서 마크업을 정의하여 이 문제를 해결합니다. 구성 요소 작성자는 마크업, CSS 및 Javascript를 자유롭게 변경할 수 있습니다. 구성 요소의 소비자는 손으로 코드를 변경하는 프로젝트를 탐색할 필요 없이 이러한 업그레이드의 이점을 누릴 수 있습니다. 터미널을 통한 간결한 npm update
로 프로젝트 전체의 최신 변경 사항과 동기화할 수 있습니다. 구성 요소의 이름과 해당 API만 일관성을 유지하면 됩니다.
웹 구성 요소를 설치하는 것은 터미널에 npm install component-name
을 입력하는 것만큼 간단합니다. Javascript는 import 문에 포함될 수 있습니다.
<script type="module"> import './node_modules/component-name/index.js'; </script>
그런 다음 마크업의 아무 곳에서나 구성 요소를 사용할 수 있습니다. 다음은 텍스트를 클립보드에 복사하는 간단한 예제 구성 요소입니다.
CodePen에서 CSS GRID(@cssgrid)의 Pen Simple 웹 구성 요소 데모를 참조하십시오.
프론트 엔드 개발에 대한 구성 요소 중심 접근 방식은 Facebook의 React 프레임워크에 의해 도입되면서 유비쿼터스되었습니다. 현대 프론트 엔드 워크플로에서 프레임워크가 널리 퍼져 있음을 감안할 때 필연적으로 많은 회사에서 선택한 프레임워크를 사용하여 구성 요소 라이브러리를 구축했습니다. 이러한 구성 요소는 해당 특정 프레임워크 내에서만 재사용할 수 있습니다.

규모가 큰 회사에서 프론트엔드가 균일하고 프레임워크 간에 재구성하는 것은 드문 일이 아닙니다. 프레임워크는 왔다가 사라집니다. 프로젝트 전체에서 최대한의 잠재적 재사용을 가능하게 하려면 프레임워크에 구애받지 않는 구성 요소가 필요합니다.


“저는 Dojo, Mootools, Prototype, jQuery, Backbone, Thorax 및 React를 사용하여 웹 애플리케이션을 구축했습니다. 저는 저와 함께 노예였던 그 킬러 Dojo 구성요소를 제 React에 가져올 수 있었으면 좋겠습니다. 오늘의 앱."
— Dion Almaer, Google 엔지니어링 이사
웹 구성 요소에 대해 이야기할 때 사용자 정의 요소와 shadow DOM의 조합에 대해 이야기합니다. 사용자 정의 요소와 그림자 DOM은 W3C DOM 사양과 WHATWG DOM 표준의 일부입니다. 즉, 웹 구성 요소가 웹 표준 입니다. 커스텀 요소와 섀도우 DOM은 마침내 올해 크로스 브라우저 지원을 달성하도록 설정되었습니다. 기본 웹 플랫폼의 표준 부분을 사용하여 구성 요소가 프런트 엔드 구조 조정 및 아키텍처 재고의 빠르게 움직이는 주기에서 살아남을 수 있도록 합니다. 웹 구성 요소는 모든 템플릿 언어 및 모든 프런트 엔드 프레임워크와 함께 사용할 수 있습니다. 이들은 진정으로 상호 호환되고 상호 운용 가능합니다. Wordpress 블로그에서 단일 페이지 응용 프로그램에 이르기까지 모든 곳에서 사용할 수 있습니다.

웹 구성 요소 만들기
사용자 정의 요소 정의
태그 이름을 만들고 해당 콘텐츠를 페이지에 표시하는 것은 항상 가능했습니다.
<made-up-tag>Hello World!</made-up-tag>
HTML은 내결함성을 갖도록 설계되었습니다. 유효한 HTML 요소가 아니더라도 위의 내용이 렌더링됩니다. 이렇게 하는 데 합당한 이유는 없었습니다. 표준화된 태그에서 벗어나는 것은 전통적으로 나쁜 습관이었습니다. 그러나 사용자 정의 요소 API를 사용하여 새 태그를 정의함으로써 내장 기능이 있는 재사용 가능한 요소로 HTML을 보강할 수 있습니다. 사용자 정의 요소를 만드는 것은 React에서 구성 요소를 만드는 것과 매우 비슷하지만 여기서는 HTMLElement
를 확장했습니다.

class ExpandableBox extends HTMLElement { constructor() { super() } }
super()
에 대한 매개변수 없는 호출은 생성자의 첫 번째 명령문이어야 합니다. 생성자는 초기 상태와 기본값을 설정하고 이벤트 리스너를 설정하는 데 사용해야 합니다. 새 사용자 정의 요소는 HTML 태그의 이름과 해당 클래스에 해당하는 요소로 정의해야 합니다.
customElements.define('expandable-box', ExpandableBox)
클래스 이름을 대문자로 표기하는 규칙입니다. 그러나 HTML 태그의 구문은 규칙 그 이상입니다. 브라우저가 새로운 HTML 요소를 구현하고 확장 가능한 상자라고 부르길 원했다면 어떻게 될까요? 이름 충돌을 방지하기 위해 새로운 표준화된 HTML 태그에는 대시가 포함되지 않습니다. 대조적으로 사용자 정의 요소의 이름에는 대시 가 포함되어야 합니다.
customElements.define('whatever', Whatever) // invalid customElements.define('what-ever', Whatever) // valid
사용자 정의 요소 수명 주기
API는 4가지 사용자 정의 요소 반응 을 제공합니다. 즉, 사용자 정의 요소의 수명 주기에서 특정 이벤트에 대한 응답으로 자동으로 호출되는 클래스 내에서 정의할 수 있는 함수입니다.
연결된 콜백 은 사용자 정의 요소가 DOM에 추가될 때 실행됩니다.
connectedCallback() { console.log("custom element is on the page!") }
여기에는 Javascript로 요소를 추가하는 작업이 포함됩니다.
document.body.appendChild(document.createElement("expandable-box")) //“custom element is on the page”
뿐만 아니라 HTML 태그가 있는 페이지 내에 요소를 포함하기만 하면 됩니다.
<expandable-box></expandable-box> // "custom element is on the page"
리소스 가져오기 또는 렌더링과 관련된 모든 작업이 여기에 있어야 합니다.
disconnectedCallback 은 사용자 정의 요소가 DOM에서 제거될 때 실행됩니다.
disconnectedCallback() { console.log("element has been removed") } document.querySelector("expandable-box").remove() //"element has been removed"
adoptedCallback
은 사용자 정의 요소가 새 문서에 채택될 때 실행됩니다. 이 문제에 대해 너무 자주 걱정할 필요는 없을 것입니다.
attributeChangedCallback
은 속성이 추가, 변경 또는 제거될 때 실행됩니다. disabled 또는 src 와 같은 표준화된 기본 속성과 우리가 구성하는 모든 사용자 정의 속성에 대한 변경 사항을 수신하는 데 사용할 수 있습니다. 이것은 사용자 친화적인 API 생성을 가능하게 하는 커스텀 요소의 가장 강력한 측면 중 하나입니다.
사용자 정의 요소 속성
HTML 속성은 매우 많습니다. 속성이 변경될 때 브라우저 가 attributeChangedCallback
을 호출하는 데 시간을 낭비하지 않도록 수신하려는 속성 변경 목록을 제공해야 합니다. 이 예에서는 하나만 관심이 있습니다.
static get observedAttributes() { return ['expanded'] }
이제 attributeChangedCallback
은 우리가 나열한 유일한 속성이므로 사용자 정의 요소의 확장된 속성 값을 변경할 때만 호출됩니다.
HTML 속성은 상응하는 값( href, src, alt, value 등)을 가질 수 있고 다른 속성은 true 또는 false(예: disabled, selected, required )입니다. 해당 값이 있는 속성의 경우 사용자 정의 요소의 클래스 정의에 다음을 포함합니다.
get yourCustomAttributeName() { return this.getAttribute('yourCustomAttributeName'); } set yourCustomAttributeName(newValue) { this.setAttribute('yourCustomAttributeName', newValue); }
예제 요소의 경우 속성은 true 또는 false이므로 getter와 setter를 정의하는 것이 약간 다릅니다.
get expanded() { return this.hasAttribute('expanded') } // the second argument for setAttribute is mandatory, so we'll use an empty string set expanded(val) { if (val) { this.setAttribute('expanded', ''); } else { this.removeAttribute('expanded') } }
이제 상용구가 처리되었으므로 attributeChangedCallback
을 사용할 수 있습니다.
attributeChangedCallback(name, oldval, newval) { console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`); // do something every time the attribute changes }
전통적으로 Javascript 구성 요소를 구성하려면 init
함수에 인수를 전달해야 했습니다. attributeChangedCallback
을 활용하여 마크업만으로 구성 가능한 사용자 지정 요소를 만들 수 있습니다.
Shadow DOM과 사용자 정의 요소는 별도로 사용할 수 있으며 사용자 정의 요소는 그 자체로 유용할 수 있습니다. Shadow DOM과 달리 폴리필이 가능 합니다. 그러나 두 가지 사양은 함께 잘 작동합니다.
Shadow DOM으로 마크업과 스타일 붙이기
지금까지 사용자 정의 요소의 동작 을 처리했습니다. 그러나 마크업 및 스타일과 관련하여 사용자 정의 요소는 스타일이 지정되지 않은 비어 있는 <span>
과 동일합니다. HTML과 CSS를 컴포넌트의 일부로 캡슐화하려면 shadow DOM을 연결해야 합니다. 생성자 함수 내에서 이 작업을 수행하는 것이 가장 좋습니다.
class FancyComponent extends HTMLElement { constructor() { super() var shadowRoot = this.attachShadow({mode: 'open'}) shadowRoot.innerHTML = `<h2>hello world!</h2>` }
모드가 의미하는 바를 이해하는 것에 대해 걱정하지 마십시오. 해당 상용구를 포함해야 하지만 거의 항상 open
을 원할 것입니다. 이 간단한 예제 구성 요소는 "hello world"라는 텍스트를 렌더링합니다. 대부분의 다른 HTML 요소와 마찬가지로 사용자 정의 요소에는 자식이 있을 수 있지만 기본적으로는 그렇지 않습니다. 지금까지 우리가 정의한 위의 사용자 정의 요소는 화면에 자식을 렌더링하지 않습니다. 태그 사이에 내용을 표시하려면 slot
요소를 사용해야 합니다.
shadowRoot.innerHTML = ` <h2>hello world!</h2> <slot></slot> `
스타일 태그를 사용하여 일부 CSS를 구성 요소에 적용할 수 있습니다.
shadowRoot.innerHTML = `<style> p { color: red; } </style> <h2>hello world!</h2> <slot>some default content</slot>`
이러한 스타일은 구성 요소에만 적용되므로 페이지의 다른 부분에 영향을 주는 스타일 없이 요소 선택기를 자유롭게 사용할 수 있습니다. 이것은 CSS 작성을 단순화하여 BEM과 같은 명명 규칙을 불필요하게 만듭니다.
NPM에 구성 요소 게시
NPM 패키지는 명령줄을 통해 게시됩니다. 터미널 창을 열고 재사용 가능한 패키지로 바꾸고 싶은 디렉토리로 이동합니다. 그런 다음 터미널에 다음 명령을 입력합니다.
- 프로젝트에 아직 package.json이 없으면
npm init
가 생성을 안내합니다. -
npm adduser
는 컴퓨터를 NPM 계정에 연결합니다. 기존 계정이 없으면 새 계정이 생성됩니다. -
npm publish

모든 것이 잘 되었다면 이제 NPM 레지스트리에 구성 요소를 갖게 되었으며 설치 및 사용 준비가 완료되었으며 전 세계 사람들과 공유할 수 있습니다.

웹 구성 요소 API는 완벽하지 않습니다. 사용자 정의 요소는 현재 양식 제출에 데이터를 포함할 수 없습니다. 점진적인 강화 스토리는 좋지 않습니다. 접근성을 다루는 것은 생각만큼 쉽지 않습니다.
원래 2011년에 발표되었지만 브라우저 지원은 여전히 보편적이지 않습니다. Firefox 지원은 올해 말 예정입니다. 그럼에도 불구하고 일부 유명 웹사이트(예: Youtube)에서는 이미 이를 사용하고 있습니다. 현재의 단점에도 불구하고 보편적으로 공유 가능한 구성 요소의 경우 단일 옵션이며 앞으로 제공해야 하는 것에 대한 흥미로운 추가를 기대할 수 있습니다.