{"id":14990235,"url":"https://github.com/yamoo9/webcomponent","last_synced_at":"2025-04-12T02:06:04.562Z","repository":{"id":69709166,"uuid":"111244754","full_name":"yamoo9/WebComponent","owner":"yamoo9","description":"HTML 웹 컴포넌트","archived":false,"fork":false,"pushed_at":"2017-11-18T22:39:50.000Z","size":61,"stargazers_count":16,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-12T02:05:59.671Z","etag":null,"topics":["korean","translation","web-component","webcomponent"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yamoo9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-18T22:33:32.000Z","updated_at":"2024-07-03T09:17:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"755e24d3-7617-47af-8d9b-ce2cd3788850","html_url":"https://github.com/yamoo9/WebComponent","commit_stats":{"total_commits":3,"total_committers":1,"mean_commits":3.0,"dds":0.0,"last_synced_commit":"eb63e9a76594ed273d5de14beca60e5b6feeb23b"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yamoo9%2FWebComponent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yamoo9%2FWebComponent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yamoo9%2FWebComponent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yamoo9%2FWebComponent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yamoo9","download_url":"https://codeload.github.com/yamoo9/WebComponent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248505862,"owners_count":21115354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["korean","translation","web-component","webcomponent"],"created_at":"2024-09-24T14:19:45.487Z","updated_at":"2025-04-12T02:06:04.532Z","avatar_url":"https://github.com/yamoo9.png","language":"JavaScript","readme":"###### HTML Web Component\n\n# HTML 웹 컴포넌트\n\n웹 컴포넌트에 대한 필요성과 기술 표준에 대한 이야기는 몇 년에 걸쳐 거론되어 왔지만 Chrome, Opera를 제외한 주요 브라우저에서는 아직까지 일부만 지원하고 있는 실정입니다.\n하지만 [웹 컴포넌트 폴리필](https://www.webcomponents.org/polyfills)을 사용하면 바로 적용해 사용할 수 있습니다.\n\n## 목차\n\n- [소개](#소개)\n- [웹 컴포넌트를 구성하는 4가지 요소](#웹-컴포넌트를-구성하는-4가지-요소)\n- [커스텀 HTML 요소 만들기](#커스텀-html-요소-만들기)\n- [바인딩 할 데이터 API](#바인딩-할-데이터-api)\n- [HTML 템플릿 구성하기](#html-템플릿-구성하기)\n- [컴포넌트 스타일링](#컴포넌트-스타일링)\n- [라이프 사이클 메서드](#라이프-사이클-메서드)\n  - [connectedCallback](#connectedcallback)\n    - [Shadow DOM이란?](#shadow-dom이란)\n- [데이터 렌더링](#데이터-렌더링)\n    - [크로스 브라우징](#크로스-브라우징)\n- [테스트 서버](#테스트-서버)\n      - [테스트 서버 개발 모듈](#테스트-서버-개발-모듈)\n- [웹 컴포넌트 \u0026 Shadow DOM](#웹-컴포넌트--shadow-dom)\n- [웹 컴포넌트를 사용할 때 알아두어야 할 점\u003ci\u003e!\u003c/i\u003e](#웹-컴포넌트를-사용할-때-알아두어야-할-점ii)\n  - [컴포넌트 이름 작성 규칙](#컴포넌트-이름-작성-규칙)\n  - [컴포넌트 확장](#컴포넌트-확장)\n  - [커스텀 요소는 컴포넌트 클래스 인스턴스](#커스텀-요소는-컴포넌트-클래스-인스턴스)\n  - [비공개(Private) 메서드](#비공개private-메서드)\n  - [컴포넌트 클래스 프리징(Freezing)](#컴포넌트-클래스-프리징freezing)\n- [결론](#결론)\n- [최신 정보](#최신-정보)\n- [참고](#참고)\n\n## 소개\n\n웹 컴포넌트는 웹 애플리케이션 제작 시에 사용 가능한 부품(Component)을 말하며, 재사용을 목적으로 캡슐화 된 커스텀 HTML 요소를 만들 수 있는 웹 플랫폼 API 세트입니다.\n표준 HTML, DOM 기술 사양에 추가된 웹 컴포넌트 기술을 사용하면 HTML/CSS/JavaScript를 사용하여 재사용 가능한 컴포넌트를 제작할 수 있습니다.\n\n예를 들어, ID로 식별 가능한 데이터를 서버에서 가져와 데이터 바인딩하는 컴포넌트를 아래와 같은 HTML 코드로 사용할 수 있습니다.\n\n```html\n\u003cy9-card data-id=\"1\"\u003e\u003c/y9-card\u003e\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 웹 컴포넌트를 구성하는 4가지 요소\n\nHTML, DOM 표준 기술 사양은 웹 컴포넌트를 구성하기 위한 4가지 API를 제공하고 있습니다.\n\n1. __[customElements](https://www.w3.org/TR/custom-elements/)__\u003cbr\u003e\n커스텀 엘리먼트를 사용하면 새로운 HTML 요소를 만들거나 기존 HTML 요소를 확장할 수 있습니다.\n\n1. __[HTML Template](https://www.html5rocks.com/ko/tutorials/webcomponents/template/#toc-using)__\u003cbr\u003e\n클라이언트 측 템플릿을 위한 표준 DOM 기반 접근 방식으로 새로운 요소를 정의하기 위해 `\u003ctemplate\u003e` 요소를 사용할 수 있습니다.\n\n1. __[HTML Import](https://www.html5rocks.com/ko/tutorials/webcomponents/imports/)__\u003cbr\u003e\nHTML 템플릿(`\u003ctemplate\u003e`)을 사용하면 새 템플릿을 만들 수 있지만 HTML 임포트(`\u003clink rel=\"import\" href=\"...\"\u003e`)를 사용하면 다른 HTML 파일에서 이러한 템플릿으로 가져올 수 있습니다.\n이를 통해 컴포넌트와 HTML 템플릿 파일을 분리 관리할 수 있습니다.\n\n1. __[Shadow DOM](https://dom.spec.whatwg.org/#shadow-trees)__\u003cbr\u003e\nShadow DOM은 컴포넌트 기반 애플리케이션을 작성하기 위한 도구로 설계 되었습니다.\u003cbr\u003e\n컴포넌트 스코프(Scope)를 DOM에서 분리하고 CSS 등을 단순화 할 수 있습니다.\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 커스텀 HTML 요소 만들기\n\n컴포넌트를 구성할 디렉토리(Directory)를 만들고, 디렉토리 안에 커스텀 요소를 정의할 파일을 생성합니다.\n\n```sh\n.\n└── Y9Card\n    └── customElement.js ⬅︎\n```\n\n커스텀 HTML 요소를 생성하려면 먼저 요소를 정의하는 \u003cstrong\u003e클래스(Class)를 선언\u003c/strong\u003e해야 합니다.\n새롭게 정의하는 클래스는 기존 HTML 요소의 능력을 그대로 물려 받아 사용하기 위해 브라우저 네이티브 클래스 \u003cstrong\u003eHTMLElement 클래스를 상속\u003c/strong\u003e 합니다.\n\n클래스를 정의하고 상속하는 구문은 다음과 같습니다.\n\n```js\n/* Y9Card 컴포넌트 클래스 정의 */\nclass Y9Card extends HTMLElement {\n  constructor() {\n    // 상위 클래스로부터 상속된 후 생성자를 정의할 경우, 반드시 super() 호출이 요구됨.\n    super();\n    // \u003cy9-card\u003e 요소 click 이벤트 바인딩: toggleCard() 메서드 실행\n    this.addEventListener('click', e =\u003e this.toggleCard());\n  }\n  // 메서드\n  toggleCard() { console.log('카드 토글(Toggle)'); }\n}\n```\n\n이어서 선언한 클래스를 커스텀 HTML 요소로 사용할 수 있도록 등록합니다.\n이 과정을 마치면 HTML 문서에서 `\u003cy9-card\u003e` 요소를 사용할 수 있게 됩니다.\n\n```js\n/* 커스텀 HTML 요소 \u003cy9-card\u003e 등록 */\nwindow.customElements.define('y9-card', Y9Card);\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 바인딩 할 데이터 API\n\n[JSONPlaceholder](https://jsonplaceholder.typicode.com/) API를 사용해 등록된 커스텀 요소에 데이터를 바인딩 할 것입니다. 바인딩 될 데이터 구조는 다음과 같습니다.\n\n```js\n{\n  id: 1,\n  name: \"Leanne Graham\",\n  username: \"Bret\",\n  email: \"Sincere@april.biz\",\n  address: {\n    street: \"Kulas Light\",\n    suite: \"Apt. 556\",\n    city: \"Gwenborough\",\n    zipcode: \"92998-3874\",\n    geo: {\n      lat: \"-37.3159\",\n      lng: \"81.1496\"\n    }\n  },\n  phone: \"1-770-736-8031 x56442\",\n  website: \"hildegard.org\"\n}\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## HTML 템플릿 구성하기\n\nAPI 데이터를 바인딩 할 템플릿을 만들어 봅시다. 템플릿으로 사용할 HTML 파일을 디렉토리 안에 만듭니다.\n\n```sh\n.\n└── Y9Card\n    ├── customElement.js\n    └── template.html    ⬅︎\n```\n\n카드 컴포넌트 템플릿은 다음과 같이 HTML 코드로 구성합니다. 템플릿을 식별하기 위한 `id` 및 컴포넌트를 구성하는 각 파트를 식별하기 위한 `class` 속성을 설정할 때는\n고유하게 식별 가능한 접두사(예제에서는 `y9-card`)를 사용해 기존 HTML 요소와 충돌나지 않도록 구성해야 합니다.\n\n```html\n\u003ctemplate id=\"y9-card__template\"\u003e\n\n  \u003cdiv class=\"y9-card__container\"\u003e\n    \u003ch2 class=\"y9-card__header\"\u003e\u003c/h2\u003e\n    \u003cp\u003e웹사이트: \u003ca href=\"\" class=\"y9-card__website\"\u003e\u003c/a\u003e\u003c/p\u003e\n    \u003cdiv class=\"y9-card__content\"\u003e\n      \u003cp class=\"y9-card__address\"\u003e\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cbutton type=\"button\" class=\"y9-card__detail-button\"\u003e자세히 보기\u003c/button\u003e\n  \u003c/div\u003e\n\n\u003c/template\u003e\n```\n\n마무리로 템플릿 정의 코드 아래 컴포넌트 커스텀 요소를 정의한 스크립트 파일을 호출합니다.\n\n```html\n\u003cscript src=\"/components/Y9Card/customElement.js\"\u003e\u003c/script\u003e\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 컴포넌트 스타일링\n\n컴포넌트를 스타일링할 CSS 파일을 컴포넌트 디렉토리 내에 생성합니다.\n\n```sh\n.\n└── Y9Card\n    ├── customElement.js\n    ├── template.html\n    └── style.css       ⬅︎\n```\n\n생성된 CSS 파일에 카드 스타일링 코드를 작성합니다.\n\n```css\n.y9-card__template {\n  display: inline-block;\n  width: 30%;\n  margin: 3px;\n  border: 1px solid grey;\n  font-family: Helvetica;\n  text-align: center;\n  border-radius: 5px;\n}\n\n.y9-card__:hover {\n  box-shadow: 3px 3px 3px;\n}\n\n.y9-card__content {\n  display: none;\n}\n\n.y9-card__details-button {\n  margin-bottom: 8px;\n  padding: 6px;\n  background-color: #dedede;\n}\n```\n\n이어서 템플릿 `\u003ctemplate\u003e` 시작 부분에 컴포넌트 스타일 파일을 호출하는 구문을 추가합니다.\n\n```html\n\u003ctemplate id=\"y9-card__template\"\u003e\n\n  \u003clink rel=\"stylesheet\" href=\"/components/Y9Card/style.css\"\u003e\n  ...\n\n\u003c/template\u003e\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 라이프 사이클 메서드\n\n커스텀 요소의 라이프 사이클(Life Cycle) 메소드는 이벤트에 따라 콜백(callback)하여 사용할 수 있습니다.\n\n메서드 이름 | 설명\n--- | ---\nconnectedCallback | 커스텀 요소가 __DOM에 삽입 될 때마다 호출__ 됩니다.\ndisconnectedCallback | 커스텀 요소가 __DOM에서 제거 될 때마다 호출__ 됩니다.\nattributeChangedCallback | 커스텀 요소의 __속성이 추가, 제거, 업데이트 또는 교체 될 때마다 호출__ 됩니다.\n\n자 그럼 이어서 `Y9Card/customElement.js` 파일에 라이프 사이클 메서드를 추가해봅시다.\n\n### connectedCallback\n\n우리가 정의한 커스텀 요소를 DOM에 삽입할 때 호출될 일련의 과정을 정의하려면 `connectedCallback` 메서드를 사용합니다.\n\n__참고__\n\n\u003e `constructor`와 `connectedCallback` 메소드는 비슷해보이지만 용도가 다릅니다.\n\u003e\n\u003e `constructor`는 커스텀 요소 인스턴스가 생성되었을 때 실행되는 반면, `connectedCallback` 메서드는 DOM에 커스텀 요소가 삽입될 때마다 실행됩니다.\n\u003e\n\u003e __`connectedCallback` 메서드는 임포트(`import`)하거나 렌더링(`rendering`) 같은 설정 코드를 실행할 때 유용__ 합니다.\n\n\u003cbr\u003e\n\n먼저 `Y9Card/customElement.js` 파일에 `currentDocument` 변수를 생성해야 합니다. 해당 변수에는 커스텀 컴포넌트 스크립트 파일을 호출한 HTML 파일의 DOM에 접근하기 위함입니다.\n\n```js\nconst currentDocument = window.document.currentScript.ownerDocument;\n```\n\n이어서 `connectedCallback` 메서드를 클래스 구문 내에 정의합니다.\n\n```js\n/* 라이프사이클 메서드 */\n// DOM에 커스텀 요소가 삽입될 때 호출\nconnectedCallback() {\n\n  // #1. Shadow DOM\n  const shadowRoot = this.attachShadow({mode: 'open'});\n\n  // #2. 템플릿을 선택하고 복제합니다.\n  const template   = currentDocument.querySelector('#y9-card__template');\n  const instance   = tempalte.content.cloneNode(true);\n\n  // #3. 복제된 노드를 ShadowRoot에 삽입합니다.\n  shadowRoot.appendChild(instance);\n\n  // #4. 커스텀 HTML 요소로 부터 data-id 속성 값을 가져옵니다.\n  // e.g) \u003cy9-card data-id=\"7\"\u003e\u003c/y9-card\u003e\n  const id = this.getAttribute('data-id');\n\n  // #5. API에서 해당 사용자 ID에 대한 데이터를 가져와 인스턴스 렌더링 메서드를 호출합니다.\n  fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n    .then( response =\u003e response.text() )\n    // #6. JSON 데이터 ➜ 객체 변경 후, render() 메서드에 전달하여 실행합니다.\n    .then( responseText =\u003e this.render(JSON.parse(responseText)) )\n    .catch( error =\u003e console.error(error) );\n\n}\n```\n\n1. `.attachShadow()` 메서드를 사용해 [Shadow DOM]()을 커스텀 요소에 연결합니다.\n1. `currentDocument` 변수를 통해 템플릿 식별자를 쿼리하여 템플릿을 참조한 후, 템플릿 노드를 복제합니다.\n1. 복제된 템플릿 노드를 `shadowRoot` 변수에 참조된 객체의 `.appendChild()` 메서드를 통해 삽입합니다.\n1. 커스텀 요소의 `data-id` 속성 값을 가져와 변수 `id`에 복사합니다.\n1. `fetch()` 함수를 사용해 API 데이터를 서버로부터 호출합니다.\n1. 응답 받은 JSON 텍스트 데이터를 객체화 하고 커스텀 요소의 `render()` 메서드에 전달하여 실행합니다.\n\n#### Shadow DOM이란?\n\n[Shadow DOM](https://developer.mozilla.org/ko/docs/Web/Web_Components/Shadow_DOM)은 웹 컴포넌트 내에 존재하는\n템플릿, 스타일, 스크립트 코드를 캡슐화 하는 기능을 가지고 있어 HTML 문서의 DOM에서 분리할 수 있습니다.\n웹 컴포넌트에 연결하여 사용하는 방법이 아니더라도 Shadow DOM 만 따로 사용하는 것도 가능합니다.\n\n\u003cimg src=\"./_/shadowDOM.png\" alt=\"Shadow DOM\" width=\"500\"\u003e\u003cbr\u003e\n\u003e shadow host는 요소, Shadow DOM Subtrees는 Shadow DOM을 말합니다.\n\nShadow DOM은 반드시 이미 존재하는 요소(HTML 파일 내에 사용된 요소)에 추가해야 합니다.\n요소는 `\u003cdiv\u003e`와 같은 네이티브 요소일 수도 있고, `\u003cy9-card\u003e`와 같은 커스텀 요소일 수도 있습니다.\n\n위 예제에서 사용했던 [`.attachShadow()`](https://developer.mozilla.org/ko/docs/Web/API/Element/attachShadow) 메서드를 사용해\nShadow DOM을 요소에 추가할 수 있습니다. 구문은 다음과 같습니다.\n\n```js\nelementNode.attachShadow(shadowRootInit);\n```\n\n`shadowRootInit`은 옵션 객체로 `mode` 속성을 설정할 수 있습니다.\n\n`mode` 속성은 Shadow DOM Tree의 캡슐화 모드를 설정합니다.\n\n모드 값 | 설명\n--- | ---\nopen | shadowRoot를 추가한 요소들이 `.shadowRoot` 속성을 사용해 HTML DOM으로부터 접근 가능하게 설정합니다.\nclose | shadowRoot를 추가한 요소들이 `.shadowRoot` 속성을 사용해 HTML DOM으로부터 접근 불가능하게 설정합니다.\n\n```js\nelement.attachShadow({\n  mode: 'open' // 'open' | 'close'\n});\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 데이터 렌더링\n\n서버로부터 전달 받아 객체화 한 데이터는 `render()` 메서드의 인자로 전달됩니다.\n전달 받은 데이터의 각 속성을 템플릿에 바인딩하는 코드를 `render()` 메서드 내에 작성합니다.\n\n```js\n/* 렌더링 메서드 */\nrender(data) {\n\n  // #1. ShadowRoot를 참조합니다.\n  const shadowRoot = this.shadowRoot;\n\n  // #2. 데이터 바인딩 할 템플릿 내부 요소를 참조합니다.\n  let header  = shadowRoot.querySelector('.y9-card__header');\n  let website = shadowRoot.querySelector('.y9-card__website');\n  let address = shadowRoot.querySelector('.y9-card__address');\n\n  // #3. 참조한 템플릿 요소마다 각각 데이터를 바인딩 합니다.\n  header.innerHTML  = data.username;\n  website.innerHTML = data.website;\n\n  let data_address = data.address;\n  address.innerHTML = `\n    \u003ch4\u003e주소\u003c/h4\u003e\n    ${data_address.suite},\u003cbr\u003e\n    ${data_address.street},\u003cbr\u003e\n    ${data_address.city},\u003cbr\u003e\n    우편번호: ${data_address.zipcode}\n  `;\n\n}\n```\n\n1. ShadowRoot를 참조합니다.\n1. 데이터 바인딩 할 템플릿 내부 요소(헤더, 웹사이트, 주소)를 참조합니다.\n1. 참조한 템플릿 요소마다 각각 데이터를 바인딩 합니다.\n\n\u003cbr\u003e\n\n마무리로 `toggle()` 메서드에 다음 코드를 추가합니다.\n\n```js\ntoggle() {\n\n  // #1. ShadowRoot를 참조합니다.\n  const shadowRoot = this.shadowRoot;\n\n  // #2. ShadowRoot를 통해 요소(콘텐츠, 버튼)를 참조합니다.\n  let content = shadowRoot.querySelector('.y9-card__content');\n  let button  = shadowRoot.querySelector('.y9-card__detail-button');\n\n  // #3. 콘텐츠의 display 상태가 화면에 표시되는지 여부를 is_content_visible 변수에 복사합니다.\n  let is_content_visible = content.style.display === 'none';\n\n  // #4. 참조한 버튼 텍스트 값을 is_content_visible 조건 값에 따라 변경(토글)합니다.\n  button.innerHTML = is_content_visible ? '자세한 정보 보기' : '펼쳐진 정보 감추기';\n  // #5. 참조한 콘텐츠의 display 상태 값을 is_content_visible 조건 값에 따라 변경(토글)합니다.\n  content.style.display  = is_content_visible ? 'block' : 'none';\n\n}\n```\n\n1. ShadowRoot를 참조합니다.\n1. ShadowRoot를 통해 요소(콘텐츠, 버튼)를 참조합니다.\n1. 콘텐츠의 `display` 상태가 화면에 표시되는지 여부를 `is_content_visible` 변수에 복사합니다.\n1. 참조한 버튼 텍스트 값을 `is_content_visible` 조건 값에 따라 변경(토글)합니다.\n1. 참조한 콘텐츠의 `display` 상태 값을 `is_content_visible` 조건 값에 따라 변경(토글)합니다.\n\n\u003cbr\u003e\n\n커스텀 컴포넌트를 생성하고 사용하기 위한 준비 과정이 마무리 되었으니 프로젝트에 해당 컴포넌트를 사용할 수 있습니다.\n`index.html` 파일을 생성한 후 컴포넌트를 사용해봅시다.\n\n```sh\n.\n├── components\n│   └── Y9Card\n│       ├── customElement.js\n│       ├── style.css\n│       └── template.html\n└── index.html ⬅︎\n```\n\n아래는 `index.html` 전문입니다.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"ko-KR\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n    \u003ctitle\u003e웹 컴포넌트 DEMO\u003c/title\u003e\n\n    \u003c!-- #1. 커스텀 컴포넌트 템플릿 파일 로드 --\u003e\n    \u003clink rel=\"import\" href=\"./components/Y9Card/template.html\"\u003e\n\n  \u003c/head\u003e\n  \u003cbody\u003e\n\n    \u003c!-- #2. 커스텀 컴포넌트 요소 사용 --\u003e\n    \u003cy9-card data-id=\"9\"\u003e\u003c/y9-card\u003e\n\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n1. `\u003clink rel=\"import\"\u003e` 구문을 추가한 다음 `href=\"./components/Y9Card/template.html\"' 속성을 추가합니다.\n1. `\u003cy9-card data-id=\"9\"\u003e\u003c/y9-card\u003e` 커스텀 요소를 body 요소 내부에 추가합니다.\n\n#### 크로스 브라우징\n\n모든 브라우저가 웹 컴포넌트를 지원하는 것은 아니기 때문에 브라우저 호환성을 고려하려면 [webcomponents.js](https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.2/webcomponents-lite.js) 파일을 문서에 추가해야 합니다.\n`\u003c/body\u003e` 바로 앞에 아래 구문을 추가하여 웹 컴포넌트를 지원하지 않는 브라우저에서 webcomponents.js를 로드할 수 있도록 조건 처리합니다.\n\n```html\n...\n\n\u003cscript\u003e\n    (function (global) {\n      'use strict';\n\n      var document = global.document;\n      var body     = document.body;\n\n      // 웹 컴포넌트를 사용할 수 있는지 검토\n      var is_support_register = 'registerElement' in document;\n      var is_support_import   = 'import'          in document.createElement('link');\n      var is_support_template = 'content'         in document.createElement('template');\n\n      // 웹 컴포넌트를 사용할 수 없다면 폴리필 스크립트 로드\n      if ( !is_support_register || !is_support_import || !is_support_template) {\n        var polyfill = document.createElement('script');\n        polyfill.src = 'https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.2/webcomponents-lite.js';\n        body.appendChild(polyfill);\n      }\n\n    })(window);\n  \u003c/script\u003e\n\n\u003c/body\u003e\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 테스트 서버\n\n웹 컴포넌트를 테스트하려면 서버를 구동해야 합니다. 테스트 서버 개발 모듈을 설치하여 사용해봅니다.\n\n##### 테스트 서버 개발 모듈\n\n- [http-server](https://www.npmjs.com/package/http-server)\n- [static-server](https://www.npmjs.com/package/static-server)\n- [json-server](https://github.com/typicode/json-server)\n- [live-server](https://www.npmjs.com/package/live-server)\n\n이 예제에서는 `http-server` 모듈을 설치하여 사용해봅니다.\n`package.json` 파일을 생성하는 명령을 실행한 후, NPM 인스톨 명령을 사용하여 `http-server` 개발 모듈을 개발 의존성(`-d`, `--save-dev`) 용도로 설치합니다.\n\n```sh\n$ echo {} \u003e package.json\n$ npm i -D http-server\n```\n\n`package.json` 파일은 프로젝트 디렉토리 루트(Root)에 생성되어야 합니다.\n\n```sh\n.\n├── components/\n│   └── Y9Card/\n│       ├── customElement.js\n│       ├── style.css\n│       └── template.html\n├── index.html\n├── node_modules/\n└── package.json  ⬅\n```\n\n`package.json` 파일을 열어 NPM 스크립트 `test-server`를 추가합니다. 아래 코드를 참고하세요.\n\n```json\n{\n  \"scripts\": {\n    \"test-server\": \"http-server\"\n  },\n  \"devDependencies\": {\n    \"http-server\": \"^0.10.0\"\n  }\n}\n```\n\n`test-server` 스크립트 명령을 실행한 후 웹 컴포넌트가 올바르게 작동하는지 확인합니다.\n\n```sh\n$ npm run test-server\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 웹 컴포넌트 \u0026 Shadow DOM\n\n테스트 서버의 `index.html` 파일을 브라우저에서 테스트 하면 커스텀 요소가 올바르게 작동하는 것을 확인할 수 있습니다. (아래 그림 참고)\n\n\u003cimg src=\"../../ASSETS/webComponent-shadowDOM.jpg\" alt=\"\" width=\"550\"\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n\n[⇪ 목차로 이동](#목차)\n\n---\n\n## 웹 컴포넌트를 사용할 때 알아두어야 할 점\u003ci\u003e!\u003c/i\u003e\n\n### 컴포넌트 이름 작성 규칙\n\n- 커스텀 요소의 이름 작성 시, 음절의 구분은 하이픈(`-`)을 사용해야 합니다. (e.g `\u003cy9-card\u003e`)\u003cbr\u003e\n※ `\u003ccard\u003e`, `\u003cy9_card\u003e`, `\u003cy9Card\u003e` 처럼 사용하는 것은 올바른 방법이 아닙니다.\n\n- 동일한 커스텀 요소를 중복해서 정의하면 안됩니다.\u003cbr\u003e\n즉, `class Y9Card extends HTMLElement {}` 구문이 2번 사용되서는 안됩니다.\n\n- 네이티브 HTML 요소는 텅빈 요소(empty element, e.g `\u003clink /\u003e`)를 허용하지만,\u003cbr\u003e\n커스텀 요소는 반드시 닫는 태그(e.g `\u003c/y9-card\u003e`)가 필요합니다.\n\n[⇪ 목차로 이동](#목차)\n\n### 컴포넌트 확장\n\n새로운 컴포넌트를 등록할 때 다른 컴포넌트를 상속 할 수 있습니다.\n예를 들어 `Y9Card` 컴포넌트를 상속하는 `Y9FlipCard`를 만들고자 한다면 다음과 같은 구문을 사용할 수 있습니다.\n컴포넌트를 확장하는 자세한 튜토리얼은 [재사용 가능한 웹 구성 요소: 요소 확장](https://developers.google.com/web/fundamentals/web-components/customelements#extend)을 참고하세요.\n\n```js\n// Y9Card 클래스를 상속한 Y9FlipCard 클래스 정의\nclass Y9FlipCard extend Y9Card {\n  constructor() {\n    // 상속 후, constructor()를 사용할 경우, 반드시 super()를 실행해야 함.\n    super();\n  }\n\n  // 메서드 확장\n  flipX() { ... }\n  flipY() { ... }\n  flipToggle() { ... }\n}\n\nwindow.customElements.define('y9-flip-card', Y9FlipCard);\n```\n\n[⇪ 목차로 이동](#목차)\n\n### 커스텀 요소는 컴포넌트 클래스 인스턴스\n\n사용자가 HTML 문서에 추가하는 커스텀 HTML 요소는 컴포넌트 클래스의 인스턴스입니다.\n\n```html\n\u003cy9-card data-id=\"23\"\u003e\u003c/y9-card\u003e\n```\n\n컴포넌트 클래스에 인스턴스(공용) 메서드 추가하면 다른 커스텀 요소 또는 스크립트에서 해당 커스텀 요소를 손쉽게 제어할 수 있습니다.\n\n```js\n// 카드 참조\nvar card_23 = document.querySelector('y9-card[data-id=\"23\"]');\n\n// 카드 토글\ncard_23.toggle();\n```\n\n[⇪ 목차로 이동](#목차)\n\n### 비공개(Private) 메서드\n\nIIFE 패턴을 사용해 비공개 메서드를 정의할 수도 있습니다.\n예를 들어, 컴포넌트 클래스 내부에서 매우 복잡한 작동을 요구하는 컴포넌트를 만들어야 할 경우 사용하면 유용합니다.\n\n```js\n(function() {\n  'use strict';\n\n  // 비공개 메서드 정의\n  // ※ 외부 컨텍스트에서 접근이 불가능합니다.\n  function _privateMethod(self, otherArgs) { ... }\n\n  class Y9Card extends HTMLElement {\n    ...\n\n    // 클래스에서 공개된 메서드를 통해 감춰진 비공개 메서드 사용\n    // ※ 외부 컨텍스트에서 접근이 가능합니다.\n    publicMethod() {\n      ...\n      // 비공개 메서드 호출\n      _privateMethod(this, args);\n    }\n    ...\n  }\n\n  customElements.define('y9-card', Y9Card);\n\n})();\n```\n\n[⇪ 목차로 이동](#목차)\n\n### 컴포넌트 클래스 프리징(Freezing)\n\n만약 컴포넌트 클래스를 누군가에 의해 수정할 수 없도록 프리징하고자 한다면 `Object.freeze()`를 사용합니다.\n\n```js\nclass Y9Card extends HTMLElement { ... }\n\nconst Frozen_Y9Card = Object.freeze(Y9Card); // 프리징\nwindow.customElements.define('y9-card', Frozen_Y9Card);\n```\n\n\u003e 개발 과정에서 컴포넌트 클래스를 프리징 할 경우 디버깅의 어려움이 있습니다. 배포할 때 프리징하길 권장합니다.\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 결론\n\n웹 컴포넌트 API는 현재 개발 중인 표준 기술 사양으로 실무에 바로 사용하기는 무리가 있습니다.\n\n하지만 [VueJS](https://vuejs.org/), [React](https://reactjs.org/), [Angular](https://angular.io/)와 같은 프레임워크는 컴포넌트 기반 개발을 지향하고 있고 웹 컴포넌트를 당장 실무에 사용할 수 있는 방법을 제공하고 있습니다.\n실무에 컴포넌트 기반 개발을 도입할 생각이라면?! 웹 컴포넌트 표준 API가 개발 완성되고 브라우저 호환에 문제가 없을 때까지 프레임워크를 사용하시길 바랍니다.\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 최신 정보\n\n[HTML 임포트(`import`) 방법에 대해 부정적인 의견과 대안책 논의](https://github.com/w3c/webcomponents/issues/645)를 살펴보면 HTML 임포트(`import`)를 사용하는 대신 __JavaScript 모듈 임포트로 대체하자__ 는 움직임이 있습니다.\n\n만약 이와 같은 의견을 같이 하고자 한다면 다음 과정을 따라 위에서 다룬 예제를 수정합니다.\n\n1. `index.html` 파일에서 `\u003clink rel=\"import\" href=\"/components/Y9Card/template.html\"\u003e` 코드를 제거합니다.\n1. `component/Y9Card/customElement.js` 파일을 아래와 같이 수정합니다.\n\n```js\n// Async Await 활용\n(async () =\u003e {\n  'use strict';\n\n  // components/Y9Card/template.html 파일을 fetch() 한 후, templateHTML 변수에 참조합니다.\n  const templateHTML = await fetch('/components/Y9Card/template.html');\n  // textHTMLTemplate 변수에 textHTML 참조 값을 텍스트로 변환한 값을 대입합니다.\n  const textHTMLTemplate = await templateHTML.text();\n  // new DOMParser()를 사용해 textHTMLTemplate를 구문 분석한 후, \u003ctemplate\u003e을 찾아 HTMLTemplate 변수에 참조합니다.\n  const HTMLTemplate = new DOMParser().parseFromString(textHTMLTemplate, 'text/html').querySelector('template');\n\n  /* Y9Card 클래스 정의 */\n  class Y9Card extends HTMLElement {\n\n    // 생성자\n    constructor() { ... }\n\n    // 라이프 사이클 메서드\n    connectedCallback() {\n\n      // Shadow DOM을 컴포넌트 요소에 추가합니다. (외부 접근 허용)\n      const shadowRoot = this.attachShadow({ mode: 'open' });\n\n      // HTMLTemplate 콘텐츠를 복제한 후 instance 변수에 참조합니다.\n      const instance = HTMLTemplate.content.cloneNode(true);\n      // Shadow DOM에 instance를 추가합니다.\n      shadowRoot.appendChild(instance);\n\n      ...\n    }\n\n    // 인스턴스 메서드\n    render(data) { ... }\n    toggle() { ... }\n\n  }\n\n  /* 커스텀 요소 등록 */\n  customElements.define('y9-card', Y9Card);\n\n})();\n```\n\n[⇪ 목차로 이동](#목차)\n\n\u003cbr\u003e\n\n## 참고\n\n- [W3C, 웹 컴포넌트 기술 사양 표준화 진행상황](https://www.w3.org/standards/techs/components)\n- [HTML Web Component using Vanilla JavaScript](https://ayushgp.github.io/html-web-components-using-vanilla-js/)\n- [웹 컴포넌트, NAVER D2](http://d2.naver.com/helloworld/188655)\n- [웹 컴포넌트, MDN](https://developer.mozilla.org/ko/docs/Web/Web_Components)\n\n[⇪ 목차로 이동](#목차)","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyamoo9%2Fwebcomponent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyamoo9%2Fwebcomponent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyamoo9%2Fwebcomponent/lists"}