CustomElement ใน Svelte3 ง่ายๆ แค่ปลายนิ้ว
Tags: svelte, customelement, webcomponents, sapper
Published: 2021-02-07 11:40:07

## Web components คืออะไร

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

#### ส่วนประกอบของ web component จะประกอบไปด้วย 4 ประการ หลักๆ
* custom Elements specification
* shadow DOM specification
* HTML Template specification
* ES Module specification

web component meta-specification [ศึกษาข้อมูลเพิ่มเติมได้ที่](https://www.webcomponents.org/specs)

#### custom HTML tags
```javascript
class AutonomousButton extends HTMLElement {
  ...
}
customElements.define("autonomous-button", AutonomousButton);
```
#### การเรียกใช้งาน
```html
<autonomous-button>Click Me :)</autonomous-button>
```
เราสามารถ extends HTMLElements อื่นๆได้ เช่น HTMLButtonElement
```javascript
class CustomizedButton extends HTMLButtonElement {
  ...
}
customElements.define("customized-button", CustomizedButton, { extends: "button" });
```
#### การเรียกใช้งาน
```html
<button is="customized-button">Click Me :)</button>
```

#### Shadow DOM
Shadow DOM เป็นการกำหนดวิธีใช้สไตล์ที่ห่อหุ้มและมาร์กอัปในส่วนประกอบของเว็บ ความสามารถในการซ่อนโครงสร้างมาร์กอัปสไตล์และพฤติกรรมและแยกออกจากโค้ดอื่น ๆ บนเพจเพื่อไม่ให้ส่วนต่างๆขัดแย้งกัน เช่นว่า ถ้าชื่อ styles ภายในและภายนอก เป็นชื่อเดียวกัน ถ้าอยู่ภายใต้ Shadow DOM จะมองเป็นคนละชื่อ และภายใน Shadow DOM จะไม่มี side effect ไปหาภายนอกเช่นกัน

```html
<!--- HTML element --->
<div class="element"></div>
```

```javascript
/** attachted shadow DOM */
const header = document.createElement('element');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
```

ใช้ `const shadowRoot = header.attachShadow({mode: 'open'});` เพื่อ attachShadow เข้าไปใน element

```html
<div class="element">
   #shadow-root (open)
   <h1>Hello Shadow DOM</h1>
</div>
```

#### ES Module
ESM ย่อมาจาก ES Modules เป็นข้อเสนอของ Javascript ในการใช้ระบบโมดูลมาตรฐาน ซึ่งในอนาคต Library ของ Javascript ต้องปรับให้เข้ากับมาตรฐาน ESM ทั้งหมด วิธีสังเกตุการใช้งานจะมี `type="module"` ใน tag script
หรือสามารถ  import ด้วย นามสกุล .mjs หรือเราสามารถกหนด ใน package.json โดยระบุ `type="module"` ก็ได้

```javascript
/** import syntax */
import "https://example.com/module.mjs";
```

```json
/** package.json */
{
  ...
  "type": "module"
}
```

```html 
<!-- import module script -->
<script type="module" src="modules/module.js"></script>

<script type="module">
  import {awesomeExplosion} from '@awesome-things/awesome-explosion';
</script>
```
ทีมา [integration-with-the-javascript-module-system](https://html.spec.whatwg.org/multipage/webappapis.html#integration-with-the-javascript-module-system)

เราพอรู้จัก webcomponent กันแล้วใช่ไหมครับ คราวนี้เรามาดูกันว่า ด้วยความเรียบง่ายของ svelte ที่เขียน code ได้สั้นและกระชับ จะเอามาสร้าง webcomponent ได้อย่างไร รับรองว่าแค่กระดิกนิ้ว ก็เสร็จได้โดยง่าย

#### การทำ CustomElement ใน Svelte

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4asdbf4mjxyewqiujsmn.png)

สมมุติเราสร้าง structure ตามภาพนะครับ

📦src
 ┣ 📂web_components
 ┃ ┣ 📂Button
 ┃ ┃ ┣ 📜Button.svelte
 ┃ ┃ ┗ 📜index.js
 ┣ 📜App.svelte
 ┣ 📜main.js
 ┗ 📜styles.svelte

โค้ดใน component เราจะประมาณนี้
```javascript
/** Button.svelte */
<svelte:options tag="sv-button" />

<script>
  import { onMount } from "svelte";
  onMount(() => {
    console.log("button web component mounted");
  });
  const alertClick = () => {
    alert("alert Button");
  };
</script>

<button on:click={alertClick}>Button</button>

```
ตัวที่บอกว่า component เราคือ web_component
`<svelte:options tag="sv-button" />` ชื่อที่เป็น customElement เราคือ sv-button คำสั่ง svelte:options เป็นคำสั่งคอยบอก compiler ของ svelte ว่า `tag="sv-button"` ทำให้เป็น customElement ให้หน่อย

ที่มา [svelte-options](https://svelte.dev/tutorial/svelte-options)

ยังไม่จบแค่นี้ เรายังต้องมา config rollup.config.js อีกเล็กน้อย
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/29jv6oe77l3w5ip22d78.png)

```javascript
/** rollup.config.js */

import svelte from "rollup-plugin-svelte";
import path from "path";
let basePath = path.join(__dirname, "./src/web_components");

export default {
       input: `${basePath}/Button/Button.svelte`,
       output: {
	 format: "es",
	 dir: "public/build",
       },
       ...
       plugins:[
          svelte({
	    compilerOptions: {
		customElement: true
            }
	  }),
       ]
}
```
จากภาพด้านบนเราจะเห็นว่า เรามีการ รับ input `input: ${basePath}/Button/Button.svelte` แล้วมีการกำหนด output เป็น ESM format ซึ่งทำให้ไฟล์ webcomponent ของเราเป็นไปมาตรฐานของ. web component spec กำหนดปลายทางไปที่ `dir: "public/build"`

จากนั้นเราลอง build ดูจะได้โครงสร้างประมาณนี้ ตัวที่บอกให้ build component ธรรมดาเป็น web component คือ `customElement: true` แต่ว่าต้องไปกำหนด svelte:options ด้วยนะ ใน component ที่เราต้องการ

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a699jwm3jva9aqig4wjx.png)
จะเห็นว่าโครงสร้างมันอยู่ในระดับเดียวกันหมด ถ้ามา component หลายตัวคงวุ่นวายน่าดู งั้นเรามาจะระเบียบโครงสร้างหลัง Build กันใหม่

```javascript
import multiInput from "rollup-plugin-multi-input";

export default {
   ...
   plugins: [
      ...,
      multiInput({
	relative: "src/",
	transformOutputPath: (output, input) => {
	   return output;
        },
      }),
  ],
}
```
เมื่อเพิ่ม plugin นี้เข้าไปมันจะ build โครงสร้างให้เป็นไปตาม src ที่เราได้ทำเอาไว้ มาลอง build กันดูอีกรอบ
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l324yywgt6apu46vfkgg.png)

พอเรามาแกะโครงสร้าง web component ที่ผ่านการ build มาแล้ว
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jqowtlml6pkvc1fnjyft.png)
จะเห็นมีการ ทำ customElement ตาม spec ของ web component
แถมยังมีการ export component ออกมาตาม ESM syntax ด้วย ถ้ายังนั้นก็หมายความเราสามารถเอาไป import ให้ใช้งานได้ เพราะ modern browser รู้จัก syntax แบบนี้อยู่แล้ว

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qp5uycxnq8ydkdokaioi.png)
เวลาใช้ก็ import web component ที่เราได้ build ไว้นะครับ
`<script type="module" defer src='/build/web_components/Button/Button.js'></script>`
ที่สำคัญอย่าลืม `type="module"` นะครับ เดี่ยวมันจะด่าเรา
ส่วนตอนจะเรียกใช้ก้แค่ ใส่ชื่อ cutomElement เขาไปได้เลยครับ
```html
<sv-button></sv-button>
```
พอได้ตามที่ต้องการแล้ว ถึงเวลารัน `yarn start/ npm run start`
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vbab4l9stf8q196tsmem.png)

จากนั้น ลองกดไปที่ปุ่ม 1 ครั้งครับมีการ alert เป็นไปตามที่เราได้เขียนไว้เลยครับ
```javascript
const alertClick = () => {
    alert("alert Button");
};

```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vc7sysd3xbuw3fugwl1w.png)

เพียงเท่านี้เราก้ได้ webcomponent ที่เขียนจาก svelte มาใช้แล้วครับ
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pwuall5u6aem02sqbd4j.gif)

จะเห็นว่า การสร้าง component ธรรมดา จนกลายมาเป็น web component ที่พร้อมใช้งาน ผ่านการ config น้อยมาก ง่ายมากด้วย นี่ยังไม่ได้พูดถึงการ build webcomponent แบบหลายไฟล์รวมกัน ซึ่ง javascript framework ตัวอื่นทำได้ยากมากกว่าจะได้ web component มาใช้งานอันนึง บางทีอาจจะต้องเอา component มา wrap อีก นั่นเป็นเพราะว่าด้วยข้อจำกัดของ javascript ด้วย
svelte จึงเป็นทางเลือกอีกทางหนึ่ง ที่เหมาะสำหรับนักพัฒนาที่จะเอามาทำเป็น webcomponent แจกจ่าย หรือใช้งานภายในองค์กร เพราะเขียนโค้ดน้อยมาก config ก็ไม่ยากเลย

ขอบคุณที่ท่านที่อ่านมาจนจบ ฝากกด Subscribe กด Share link ด้วยนะครับ

สำหรับใครที่อ่านแล้วยังไม่เข้าใจ แล้วอยากเรียนรู้ svelte. ตั้งแต่เริ่ม แบบ step by step สามารถศึกษาเพิ่มเติมได้ที่

[Svelte: Framework Not Framework — (ตอนที่ 1) แรกรู้จัก](https://medium.com/@tanangular/svelte-framework-not-framework-%E0%B8%95%E0%B8%AD%E0%B8%99%E0%B8%97%E0%B8%B5%E0%B9%88-1-%E0%B9%81%E0%B8%A3%E0%B8%81%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-434fde7ff97f)