Custom Components Loader in Vanilla JS
๐Ÿ“„

Custom Components Loader in Vanilla JS

Created
Jan 26, 2021 11:10 AM
Tags
js
web components
ย 
ES6์˜ Web Components, Shadow DOM, import ๊ทธ๋ฆฌ๊ณ  Reflect ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— ์ž‘์„ฑํ•˜๋Š” ๊ธ€์ž…๋‹ˆ๋‹ค.
์‚ฌ์‹ค ์—ฌ๊ธฐ์„œ ์œ ์‹ฌํ•˜๊ฒŒ ๋ด์•ผํ•  ๊ฑด ์—†์–ด์š”. ๊ทธ๋ƒฅ ์ œ ์š•์‹ฌ์— ์ž‘์„ฑํ•˜๋Š” ๊ธ€์ž…๋‹ˆ๋‹ค.
ย 
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๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜์—ˆ๋˜ ๊ฒƒ...
ย 

Loading Comments...