Custom Components Loader in Vanilla JS
๐Ÿ“„

Custom Components Loader in Vanilla JS

Created
Jan 26, 2021 11:10 AM
Tags
js
web components

Introduce

ES6 ๋ช…์„ธ์— ๋ณด๋ฉด Web Components๋ผ๋Š” ๊ฒƒ์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.
์–˜๋Š” ์ผ์ข…์˜ ๋ถ„๋ฆฌ๋œ DOM ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š”๋ฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <my-element>Hello</my-element> <!-- === <h1>Hello</h1> --> <script> class MyElement extends HTMLElement { constructor() { super(); const template = document.createElement('template'); template.innerHTML = '<h1><slot></slot></h1>'; this.attachShadow({ mode: 'closed' }) .append(template.content.cloneNode(true)); } } window.customElements.define( 'my-element', MyElement, ); </script> </body> </html>
ย 
๊ทธ๋Ÿฐ๋ฐ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ์œ„ ์ฝ”๋“œ๋Š” innerHTML ์„ ์ด์šฉํ•ด HTML ์ฝ”๋“œ๋ฅผ ์ธ๋ผ์ธ์œผ๋กœ ๋„ฃ์–ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—...
ย 
๋‹ค์Œ๊ณผ ๊ฐ™์ด xhr ์„ ์ด์šฉํ•ด ๋ถ„๋ฆฌํ•ด์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.
ย 
class MyElement extends HTMLElement { constructor() { super(); _connect(); } async _connect() { const rawHtml = await (await fetch(/* html src */)).text(); const template = document.createElement('template'); template.innerHTML = rawHtml; this.attachShadow({ mode: 'closed' }) .append(template.content.cloneNode(true)); } } window.customElements.define( 'my-element', MyElement, );
ย 
๋‹ค๋งŒ ES6์˜ Class Constructor๋Š” async ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—
์–ด์ฉ” ์ˆ˜ ์—†์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค.
ย 
๋ฌผ๋ก  ๋‹ค์Œ๊ณผ ๊ฐ™์ด then ์„ ์ด์šฉํ•ด์„œ๋„ ๊ฐ€๋Šฅ์€ ํ•œ๋ฐ
ย 
class MyElement extends HTMLElement { constructor() { super(); fetch(/* html src */) .then(async (resp) => await resp.text()) .then((rawHtml) => { const template = document.createElement('template'); template.innerHTML = rawHtml; this.attachShadow({ mode: 'closed' }) .append(template.content.cloneNode(true)); }); } } window.customElements.define( 'my-element', MyElement, );
ย 
์ด๊ฑด ๊ทธ๋ƒฅ ๊ฐœ์ธ์ ์œผ๋กœ ๋ง˜์— ๋“ค์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ์—†๋‚˜ ์ƒ๊ฐํ•ด ๋ณด์•˜๊ณ ...
ES6์˜ import ๊ตฌ๋ฌธ์ด ๋– ์˜ฌ๋ž๋‹ค.
ย 
import rawHtml from './my-element.html'; // ERROR class MyElement extends HTMLElement { constructor() { super(); const template = document.createElement('template'); template.innerHTML = rawHtml; this.attachShadow({ mode: 'closed' }) .append(template.content.cloneNode(true)); } }
ย 
๋‚˜๋Š” ์†”์งํžˆ ๋  ์ค„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ ์–ด๋„ ํ˜„์žฌ๋กœ์„œ๋Š” ์˜ค๋กœ์ง€ js ํŒŒ์ผ๋งŒ์„ import ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.
ย 
๊ทธ๋ž˜์„œ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•œ ๋ฐฉ๋ฒ•์€?
์–ด์ฐจํ”ผ ES6์˜ class ๋„ ์‹ค์ œ๋กœ๋Š” ES5์˜ function Class Declaration์„ ์ถ•์•ฝํ•œ ๊ฒƒ์ผ ๋ฟ์ด๋‹ˆ๊นŒ...
ย 
class Foo { } console.log(typeof Foo); // "function"
ย 
๊ทธ๋ž˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ๋“ˆ์„ ํ•˜๋‚˜ ๊ตฌํ˜„ํ–ˆ๋‹ค.
ย 
/** * raw html data loader * * @param {String} src source link */ const rawHtmlLoader = async (src) => await (await fetch(src)).text(); export default async (src) => { // create a template lement const template = document.createElement('template'); // set contents template.innerHTML = await rawHtmlLoader(src); // define custom element class (es5 version for async extends) function CustomComponent() { const customComponent = Reflect.construct(HTMLElement, [], CustomComponent); // attach shadow customComponent.attachShadow({ mode: 'closed' }) .append(template.content.cloneNode(true)); // return reflected constructor return customComponent; } // set prototype CustomComponent.prototype = Object.create(HTMLElement.prototype); // return custom component element return CustomComponent; };
ย 
์œ„ ์ฝ”๋“œ๋Š” ๊ทธ๋ƒฅ ES5 ๋ฌธ๋ฒ•์œผ๋กœ Class๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๊ณ ,
์—ฌ๊ธฐ์— Reflection์„ ์ด์šฉํ•ด HTMLElement ๋ฅผ extends ํ•œ ์ฝ”๋“œ์ด๋‹ค.
ย 
์™œ ์ด๋ ‡๊ฒŒ ํ–ˆ๋ƒ? ๋ฐ”๋กœ async ๋ฅผ ์ด์šฉํ•œ extends ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ด๋Ÿฐ ์ง“์„ ํ•œ ๊ฒƒ์ด๋‹ค.
ย 
์•„๋ฌดํŠผ, ์ด ๋ชจ๋“ˆ์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด html ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง„ template ์„ ๋ฐ”๋กœ Loadํ•  ์ˆ˜ ์žˆ๋‹ค.
๋ฌผ๋ก  ์›นํŒฉ์ด๋‚˜ ๋ญ ๊ทธ๋Ÿฐ๊ฑฐ ์—†์ด Vanilla JS์—์„œ ๋ง์ด๋‹ค.
ย 
import loader from './loader.js'; window.addEventListener('load', async () => { window.customElements.define( 'my-element', await loader('/components/my-element.html'); ); });
<!-- /components/my-element.html --> <h1> <slot></slot> </h1> <!-- ๋ญ ์ด๋Ÿฐ ๊ฒƒ๋„ ๋‹น์—ฐํžˆ ์‚ฌ์šฉ ๊ฐ€๋Šฅ --> <style scoped> h1 { font-weight: 100; } </style>
ย 
๊ฐœ์ธ์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์ด ๋งˆ์Œ์— ๋“ค์—ˆ๋‹ค. ์กฐ๊ธˆ ๋” ๊น”๋”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
๊ทผ๋ฐ ๋˜ ๋ณด๋ฉด ๋ฉ€๋ฆฌ ๋Œ์•„์˜จ ๊ฒƒ ๊ฐ™๊ธฐ๋„ ํ•˜๊ณ ... ์•„๋ฌดํŠผ ๋ญ ์š•์‹ฌ์ด ์ƒ๊ฒจ ๊ตฌํ˜„ํ•ด ๋ณธ ๋ชจ๋“ˆ.
ย 
GitHub์— ์ฝ”๋“œ๋„ ์˜ฌ๋ ค๋‘์—ˆ๋‹ค.
์ฐธ๊ณ ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด๋„ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
ย 
import componentLoader from 'https://raw.githack.com/Gumball12/vanilla-component-loader/main/index.js'; window.addEventListener('load', async () => { customElements.define( 'my-element', await componentLoader('/components/my-element.html'), ); });
ย 
ย 

+

๋‹น์—ฐํžˆ ์ข€ ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ–ˆ๋‹ค..
Vanilla JS๋กœ MVVM ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋ฉฐ ์•Œ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ,
๊ทธ๋ƒฅ ์ƒˆ๋กœ์ด class๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜์—ˆ๋˜ ๊ฒƒ...
ย