มารู้จัก Svelte และ SvelteKit กัน
[ สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
(บทความนี้เป็นบทความประกอบการอธิบายกับ Video นะครับ)
รู้จัก Svelte / Svelte Kit
Ref: https://svelte.dev/
Svelte คือ open-source Frontend component framework ที่ถูกสร้างโดย Rich Harris และ maintain โดย Svelte core team ที่มีจุดประสงค์ในการทำ UI Component ของ Web เพื่ออำนวยความสะดวกในการ reuse component ทั้งส่วน UI / Styling และ Logic ใน Component ได้ โดยจุดเด่นหลักๆของ Svelte คือ
- Component-based = เหมือนๆกับ Web framework ชื่อดังเจ้าอื่นๆ อย่าง React, Vue, Angular สามารถทำ UI Component ที่รวม Style / Logic ไว้ได้
- Reactive = มีความสามารถในการ update UI แบบอัตโนมัติเมื่อ data change (คล้ายๆ Vue)
- Compiler-based = จุดแข็งสำคัญของ Svelte สำหรับการ build project
Svelte จะใช้ compiler (จังหวะที่มีการ build code เพื่อ production ใช้งาน) ทำการ convert เป็น optimized JavaScript ที่สามารถจัดการ DOM โดยตรงได้เลย
จากแต่เดิมที่บาง Framework ต้องอาศัย Virtual DOM ในการช่วย Optimize การ manipulates ของ Javascript (โดยการ pack javascript ส่วนนี้เข้า ไป) ที่ถึงแม้ว่าจะเร็วกว่าการจัดการ DOM โดยตรง แต่การใช้ Virtual DOM ก็เพิ่มกระบวนการประมวลผลและใช้หน่วยความจำมากขึ้น
ฝั่ง Svelte จะทำ step นี้ตั้งแต่ compiler เลย ระบบ compile ของ Svelte จะวิเคราะห์ code แบบคงที่ระหว่างการสร้าง โดยจะส่งผลทำให้ bundle sizes มีขนาดเล็กลง และ Javascript สามารถจัดการได้ไวขึ้น
รวมถึงเทคนิคอย่าง Reactive = Svelte ใช้เทคนิคใหม่ stores เพื่อจัดการกับความ responsive แทน virtual DOM diffing stores จะเชื่อมต่อกับองค์ประกอบ DOM โดยตรง อัปเดตเฉพาะองค์ประกอบที่ได้รับผลจากการเปลี่ยนแปลงสถานะ ลดการทำงานของ DOM และการใช้หน่วยความจำ (ระบบ reactive นี้มีประสิทธิภาพมากกว่า virtual DOM เพราะจะอัปเดตเฉพาะสิ่งที่ต้องการเท่านั้น) เทียบได้กับการที่ code สามารถตัด runtime ออกได้ แล้วเหลือเพียง javascript ไป run ตรงๆได้
-
Small bulider-size = จากคุณสมบัติเรื่อง compiler ส่งผลทำให้ขนาดของ build file svelte มีขนาดเล็กมาก (ราวๆ 2kb) เมื่อเทียบกับเจ้าอื่นๆ
-
Easy to learn = มีเว็บ tutorial ให้เรียนรู้ได้เลย และ code ส่วนใหญ่ (จากเท่าที่ลองมา) code สั้นกว่า framework หลายๆเจ้า เมื่อใช้ไอเดียเดียวกัน
นี่คือ 1 ใน framework ที่ Release ช่วงปี 2016 เทียบกับ React (2013), Angular (2010), Vue (2014) ก็ถือว่าเป็น Framework ที่เกิดขึ้นในยุคหลังๆก็ว่าได้ = ดังนั้น เป็น 1 ใน framework ที่มี lesson learn มาแล้วจาก 3 Framework ชื่อดังก่อนหน้านี้มา
นี่คือเว็บ tutorial ของ svelte https://learn.svelte.dev/tutorial/updating-arrays-and-objects
เพิ่มเติมตัวของ SvelteKit
จุดแข็งอีกอย่างของ Svelte ที่นอกเหนือจากการที่เป็น UI Framework แล้ว ยังมี library ที่ใช้งานคู่กันอย่าง SvelteKit ที่ช่วยอำนวยความสะดวกในการทำ web application ด้วย โ ดย SvelteKit นั้นวางตัวเองเป็น
Svelte is a component framework, SvelteKit is an app framework
SvelteKit เป็น Framework ที่สร้างขึ้นบน Svelte ช่วยให้นักพัฒนาสามารถสร้าง web app เว็บแบบเต็มรูปแบบได้อย่างง่ายดาย
library ของ SvelteKit ครอบคลุมถึง
-
Svelte: คอมไพเลอร์ Svelte ที่แปลงโค้ด Svelte เป็น JavaScript ธรรมดา
-
Vite: เครื่องมือรันไทม์ที่โหลดแอปพลิเคชัน SvelteKit อย่างรวดเร็ว
-
Router: ไลบรารีการจัดการเส้นทางที่ช่วยให้นักพัฒนาสร้างแอปพลิเคชันแบบหลายหน้า
-
Store: ไลบรารีการจัดการสถานะแบบ reactive
-
Form: ไลบรารีการจัดการแบบฟอร์มที่ช่วยให้นักพัฒนาสร้างแบบฟอร์มที่ใช้งานได้ง่าย
-
library ที่ทำการรวม feature พื้นฐานของ Web framework โดย design ให้ใช้ร่วมกับ Svelte Component ได้
-
ถ้าเทียบกับฝั่ง React หรือ Vue
- Svelte = React, Vue
- SvelteKit = Next, Nuxt
Feature มีตั้งแต่
- Routing
- Server-side rendering
- Data fetching
- Service workers
- TypeScript integration
- Prerendering
- Single-page apps
- Library packaging
- Optimised production builds
- Deploying to different hosting providers
Feature support ก็คล้ายๆกับฝั่ง Next, Nuxt เลย แต่ก็จะมีของที่คล้ายๆกันกับของบางอย่างที่ง่ายกว่า (มาดูผ่านตัวอย่างไปพร้อมๆกันได้)
เราจะคุยอะไรกันใน Session นี้บ้าง
ในหัวข้อน ี้เราจะพูด 2 Part แยกออกจากกันคือ
- มารู้จักกับ Svelte Component พื้นฐานของตัว Svelte ก่อน (ผ่าน tutorial ของ Svelte) โดยเราจะหยิบเอาตัวที่เรารู้สึกว่า "ต้องรู้" ของ Svelte มาก่อน
- มาลองทำ project Easy Blog กัน ผ่าน SvelteKit
ก่อนเราจะมาทำ project จริงกัน เราจะพาทัวร์ feature ของ Component ของ Svelte เพื่อให้เห็นภาพรวมของ Feature ของ component ใน Svelte ก่อน
Part 1 - Svelte
Ref: https://learn.svelte.dev/tutorial/welcome-to-svelte
เนื่องจาก Part 1 เราจะยังไม่ใช้ SvelteKit ดังนั้น เราจะขอลงจาก Vite มาก่อนแทน
npm create vite@latest sample-svelte -- --template svelte
โดยเราจะดำเนินการตามหัวข้อของ Tutorial ใน Svelte (ตาม link ที่ Reference นี้)
Component
ใน Svelte web app จะประกอบด้วยองค์ประกอบตั้งแต่ 1 component ขึ้นไป โดย component คือ block of code ที่นำมาใช้ซ้ำได้ ซึ่งห่อหุ้ม HTML, CSS และ JavaScript ไว้ด้วยกัน โดยเขียนเป็นไฟล์ .svelte ไว้
ตัวอย่าง App.svelte
<script>
let name = 'Svelte';
</script>
<h1>Hello {name.toUpperCase()}!</h1>
โดยสามารถส่งตัวแปรเข้า attribute ของ html จาก javascript ได้ภายใน component
<script>
let src = '/image.gif';
let name = 'Rick Astley';
</script>
<img {src} alt="{name} dances." />
Reactivity
การผูกตัวแปรแบบ reactive โดยสามารถเปลี่ยนแปลงและแสดงผลบน UI ออกมาเมื่อ data เกิดการ change ได้
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
Clicked {count}
{count === 1 ? 'time' : 'times'}
</button>
Props
Component สามารถรับค่าผ่าน Properties และส่งค่าผ่าน Props ได้
เช่นที่ App.svelte
<script>
import Hello from './Hello.svelte';
</script>
<Hello answer={42} />
โดยที่ Hello.svelte ทำกา ร import properties ผ่าน export let <ชื่อตัวแปร> เช่น เคสนี้ทำการสร้าง properties answer ที่เป็นตัวรับออกมา จึงสามารถรับค่า answer ออกมาได้
<script>
export let answer;
</script>
<p>The answer is {answer}</p>
Logic blocks
Component ของ Svelte อนุญาตให้สามารถเพิ่ม dynamic behavior หรือ control flow ของ web app ได้ โดย Logic blocks เหล่านี้ใช้เพื่อจัดการกับเงื่อนไข, ทำซ้ำ (loop), และคำสั่ง reactive statement ซึ่งเป็นส่วนหนึ่งของ template ใน Svelte ที่ได้เตรียมรูปแบบการจัดการเอาไว้
ตัวอย่าง code ของการใช้ if blocks ของ control flow สำหรับการควบคุมการแสดงผลเมื่อ count > 10 ให้แสดงข้อความออกมา
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
Clicked {count}
{count === 1 ? 'time' : 'times'}
</button>
{#if count > 10}
<p>{count} is greater than 10</p>
{/if}
ซึ่งนอกเหนือจาก if blocks ยังมี else, else-if, each รวมถึง await block ที่ใช้สำหรับดึง API ได้หน้าตาประมาณนี้
<script>
import { getRandomNumber } from './utils.js';
async function getRandomNumber() {
const res = await fetch('/random-number');
if (res.ok) {
return await res.text();
} else {
throw new Error('Request failed');
}
}
let promise = getRandomNumber();
function handleClick() {
promise = getRandomNumber();
}
</script>
<button on:click={handleClick}>
generate random number
</button>
{#await promise}
<p>...waiting</p>
{:then number}
<p>The number is {number}</p>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
อ่านได้จากหัวข้อ https://learn.svelte.dev/tutorial/if-blocks หรือ document หลัก https://svelte.dev/docs/logic-blocks
Event
ใช้คำสั่ง on: เพื่อ listener event ของ DOM ได้ เช่น
on:clickเพื่อทำการดักตามการ click ของปุ่ม ตามเคสใน code ด้านล่างนี้
<script>
let count = 0;
/** @param {MouseEvent} event */
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
อ่านได้จากหัวข้อ https://learn.svelte.dev/tutorial/dom-events
Binding
ผูก Data เข้ากับ form โดยใช้คำสั่ง bind:value โดยทำการใส่คู่กับ bind เข้ากับ input DOM ที่ต้องการทำการ binding ไว้
ตัวอย่างของการผูก name เข้ากับ input form
<script>
let name = 'world';
</script>
<input bind:value={name} />
<h1>Hello {name}!</h1>
อ่านได้จากหัวข้อ https://learn.svelte.dev/tutorial/text-inputs
Lifecycle
Cycle ของ Component ใน state onMount, beforeUpdate, afterUpdate
อ่านได้จากหัวข้อ https://learn.svelte.dev/tutorial/onmount
Stores
จัดการ state ตรงกลาง
อ่านได้จากหัวข้อ https://learn.svelte.dev/tutorial/writable-stores
โจทย์ของเรา
เราจะมาใช้ Svelte ทำ Project ที่มี feature ดังนี้
- ทำ Blog แสดงข้อมูลออกมาทั้งหมด ผ่าน API ของ mockapi
- ดู detail blog แต่ละหน้าได้ ผ่าน parameter
- ทำหน้า เพิ่ม Blog โดยมี input สำหรับใส่ชื่อ blog
- เพิ่มหน้า login เพื่อกั้นสิทธิ์ให้เฉพาะคนที่ login แล้วเท่านั้นที่แก้ blog ได้ (ใช้ server action เข้าไป)
- เพิ่ม jwt token cookie (เพิ่ม hook สำหรับเช็ค cookie ก่อนที่จะเปิดหน้าแก้ไข)
** tutorial ฉบับเต็มสามารถดูได้ผ่าน video ในส่วน source code สามารถดูผ่าน github นี้ได้เลย https://github.com/mikelopster/svelte-basic
Part 2 - SvelteKit
Ref: https://learn.svelte.dev/tutorial/introducing-sveltekit
Follow ตามหัวข้อของ Tutorial SvelteKit
Routing
Ref: https://kit.svelte.dev/docs/routing
การแยกระหว่าง Component โหลดจาก server, Component โหลดจาก Client โดยหลักการ SvelteKit คือ Router ที่ใช้ระบบชื่อไฟล์เป็นการกำนนด path ที่ user สามารถ access ได้ โดยจะกำหนดจาก directory ของ codebase เป็นหลัก:
src/routesคือ root routesrc/routes/aboutสร้าง/aboutroutesrc/routes/blog/[slug]สร้าง/blogroute ที่สามารถรับ parameter, slug ที่ใช้สำหรับการโหลด data แบบ dynamic ออกมาได้เมื่อ user มีการเปิด page เช่น/blog/hello-worldออกมา
โดยนอกจาก path แล้วจะมีชื่อ file ที่เป็นตัวกำหนดคุณสมบัติของ page นั้นออกมาได้ โดยจะมี 3 ประเภทใหญ่ๆที่จะมีโอกาสได้ใช้บ่อยๆ (ทุกประเภทสามารถดูผ่าน document ต้นฉบับได้)
+page.svelte
เป็นการกำหนด page component ของ web app = เทียบได้กับเป็น default ของ component ที่ใช้แสดงผลในแต่ละหน้า และ ทุก page นั้นโดย default จะ render ได้ทั้งบน Server (SSR) สำหรับการเรียก request รอบแรก และ บน Browser (CSR) สำหรับการเปิดไปในแต่ละหน้า
โดยสามารถเขียน code แบบ svelte component ได้เลย เช่นแบบนี้ ลองวางไว้ที่ src/routes/blog/+page.svelte
<script>
let data = 'Title Test'
</script>
<div>
Title: {data}
</div>
เมื่อเปิด web ผ่าน localhost:5173/blog ก็จะสามารถเปิดหน้านั้นแสดงผลออกมาได้
+page.server.js
หากเราต้องการ load ข้อมูลก่อนที่ component นั้นจะแสดงผลออกมา = เราต้องการ run load function จากบน server ออกมาก่อน
- ถ้าเราต้องการดึงข้อมูล data ไม่ว่าจะเป็นจาก database หรือ private environment variable ที่มี API key
- เราสามารถใช้
+page.server.jsเพื่อเป็นการกำหนดว่าเราจะโหลดข้อมูลออกจาก server มาก่อนได้
เช่น เคสนี้ หากเราต้องการดึงข้อมูลจาก Mock API ออกมาก่อน ใน folder เดียวกันเราจึงมี 2 files คือ +page.server.js และ +page.svelte โดย
+page.server.jsจะทำการดึงข้อมูลออกมาก่อนด้วย load function และส่งข้อมูลออกไปผ่าน propsdata- ที่ page component
+page.svelteจะนำข้อมูลจาก server component มาใช้ต่อและแสดงผลออกมาได้
code ก็จะมีหน้าตาประมาณนี้
// File: src/route/blog/+page.server.js
import { error } from '@sveltejs/kit'
import axios from 'axios'
export async function load({ params }) {
const response = await axios.get(
`<mock api url ของเราเอง>/blogs/${params.id}`
)
if (response.data) {
return {
post: response.data
}
}
error(404, 'Not found')
}
<!-- File: src/route/blog/+page.svelte -->
<script>
export let data // รับข้อมูลจาก server component
</script>
<div>
Title: {data.post.name}
<a href={`/blog/${data.post.id}/edit`}>
Go to edit page
</a>
</div>
ก็จะสามารถใช้งานข้อมูลจาก server ได้
+page.js
ในกรณีที่เราต้องการ load ข้อมูลมา แต่เราอยากให้การ load นี้เกิดขึ้นได้ทั้งจากฝั่งของ Server (ตอนเปิดหน้าเว็บขึ้นมาใหม่ - SSR) และ จากฝั่งของ Client (จากการเปลี่ยนหน้ามา และโหลดข้อมูลใหม่ - CSR) จะสามารถใช้ +page.js ในการดึงข้อมูลออกมาได้ โดยสามารถ export data ออกได้จาก load function (แบบเดียวกันกับ server component)
้เช่น code ลักษณะนี้
// File: src/route/blog/[slug]/+page.js
import { error } from '@sveltejs/kit';
export function load({ params }) {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
error(404, 'Not found');
}
สังเกตว่า code จะเหมือนๆกันกับฝั่ง Server เลยแต่ขอให้เข้าใจไว้้ว่า
+page.server.jsจะ run แค่บนฝั่ง Server เท่านั้น = เหมือนเรากำลังทำทุกอย่างบน Server+page.jsจะ run ทั้งฝั่ง Client (Browser) และ Server = แม้จะดึงข้อมูลเหมือนกัน แต่เมื่อมีการเปิดผ่าน navigation ออกมา จะเป็นการทำงานฝั่ง Client แทน ดังนั้น