半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

前端

Svelte Summit 2020 筆記與心得

Svelte Summit 2020 筆記與心得

前言

Svelte 是我相當喜歡的前端框架之一,簡單的語法與彈性,作者的理念,還有各種動畫、過場的使用方式都讓我相當喜歡,之前也有寫過幾篇關於自己對 Svelte 的看法:

Svelte Summit 在 2020 年 10 月 18 日舉行,因為疫情的關係是以全程線上直播的形式播出,總共 7 個小時與 17 個演講。在這邊紀錄一些我覺得有趣的演講與自己的心得。

The Zen of Svelte

這個演講在說 Svelte 裡頭的各種「哲學」,它的設計與 Python 當中的哲學類似。

Svelte 從語法上著手,盡可能減低學習成本,就算是非前端領域甚至是初學者都可以非常容易上手。從官方文件上也可以略知一二,除了閱讀教學文字之外,另外一邊就是 REPL,你可以直接操作程式碼來看看實際效果如何。

Svelte 官方教學文件

作者 Rich Harris 在 Rethink Reactivity 這場演講曾經提過:

Rich Harris - Rethinking reactivity

Frameworks are not tools for organizing your code, frameworks are tools for organizing your mind 框架不是整理程式碼的工具,而是整理「心智」的工具。 — Rich Harris

舉例來說,React 的出現讓 Component、Immutable、Reactivity、State 等概念在前端造成一波革命,不少後續框架的出現都是以此為根基,徹底改變了前端開發手法。比起框架的「寫法」本身,更重要的是這些框架背後想要傳達的概念與設計哲學。

作者也提到,像 jQuery 這樣的工具出現,讓像他這樣非本科出身、非程式背景的人也可以變成網頁開發的一份子。從這點可以略知一二作者對 Svelte 的願景是什麼。

註:我在這篇文章中有談論更多,有興趣的話可以一起參考

harris-jquery

你懂 HTML, CSS, JavaScript,就代表你會寫 Svelte;反過來也一樣。跟一般 template engine 不太一樣的地方是 Svelte 編譯的方式不同。template engine 往往會和後端做搭配,將變數傳到 template engine 之後變成 HTML 程式碼;在 Svelte 當中全部的程式碼會變成對應的 JavaScript(在不是 SSR 模式時),並且可以動態執行。

我喜歡這樣的哲學,寫程式不一定就要變成工程師,就像做料理不一定要是廚師一樣。有可能只是想動手解決日常生活中的問題。

從另外一個角度來看,或許有人會問跳過 JavaScript 基礎直接學習 Svelte 是件好事嗎?我的想法是:「看目的」。

如果你今天打算成為一位合格的前端工程師,那在某個時間點勢必要去了解背後運作的原理,但如果你今天只是想打造一個網頁解決自己的問題,就算對基礎沒有那麼了解也沒關係。

這個演講裡頭還有提到 The zen of just writting css 這篇文章。在 Svelte 裡頭你可以直接將 style 寫在元件裡頭,達到 HTML, CSS, JavaScript 三者合一的效果,Svelte 會幫你做 hash 所以可以直接避免命名衝突的問題。在 React 當中我們時常會使用 CSS-in-JS 的方案來避免這樣的問題,不過我們是否有更好的方式來做到 styling 這件事呢?

例如在 styled-components 中我們可以這樣子寫:

const Component = styled.div`
  padding: ${props => props.padding}px;
`

// 搭配 theme
const ThemeComponent = styled.div`
  background-color: ${props => props.theme.mainColor};
`;

這是個非常好用且有趣的寫法,我們可以定義 theme 在 context 當中,並且在宣告元件時動態取用。不過也因為動態宣告的原因,這些樣式勢必需要在 runtime 執行,因為我們沒辦法知道可能傳進來的 prop 有哪些,這也就代表樣式沒辦法完全透過靜態生成。

但是在 Svelte 當中可以直接用 SSR 額外渲染出一個獨立的樣式表:

result: {
	js,
	css,
	ast,
	warnings,
	vars,
	stats
} = svelte.compile(source: string, options?: {...})

在元件中宣告的樣式會額外放到 css ,這樣就可以將樣式與程式碼拆開。說實在的這樣是好是壞或許是見仁見智,像是 constant 之類需要共用的顏色、字型等等用起來可能就沒那麼方便(比起 styled-components 等方案)

Prototyping with Svelte

Svelte 內建的 transition 與 animation 等 directive 非常好用,所以就算是設計師也可以馬上作 prototype 來驗證想法。在 REPL 上也可以很快看到結果作驗證。推薦給想要打臉工程師做不出來的設計師們。

How does Svelte's crossfade function work

crossfade 是個相當好用的 transition,什麼是 crossfade 呢?像這樣元素在兩個區域之間做移動就叫做 crossfade。

在這場 talk 當中講者講述 crossfade 在 Svelte 裡頭是如何實作,然後實際上是怎麼使用在他的產品上的。

crossfade 本身使用了 Svelte 當中 key 的概念,每次執行時會去找對應 key 的節點,計算兩者之間的位置之後使用 transition 做過場動畫。因為可以套用 transition 這個 Svelte 內建的 directive,所以使用起來相對方便很多。(範例取自官方教學文件)

Svelte Animation

Svelte 當中有一個內建的 directive 叫做 animate ,在使用迴圈 each 時,如果列表內的元素有變動,就會觸發 animate。使用的方式像下面這樣:

{#each list as item (item.id)}
  <div animate:flip>
    ...
  </div>
{/each}

在 Svelte 當中有內建一個 animate 叫做 flip ,分別是 First, Last, Invert, Play 的簡寫搭配而成,是一個做動畫時的技巧。首先我們先紀錄元素中尚未變化與變化後的位置(First, Last),然後開始計算兩者之間的差異(寬高、移動偏移量等),最後執行動畫。詳細說明可以參考 Tech Bridge 的專欄文章 — FLIP 技巧總複習

Svelte 把這一連串的計算簡化成 flip 給你開箱即用,搭配 crossfade 的效果像這樣:

所有的移動都有一個 transition,感覺就比較舒適了對吧!

Unlocking The Power of Svelte action

這篇是在講 Svelte 當中的 action,有點類似 hook 的感覺。在 Svelte 當中你可以這樣寫:

<div use:keyboard={{shortcut}} class="player-container">
</div>

這邊的 use 就是 Svelte action 的 directive。後面的 keyboard 則是自定義的函數,他的函數簽名會像這樣:

function keyboard(node, options) {}

這個函數可以回傳一個物件,裡頭可以包含 update 與 destory,分別代表當 parameter 有改變時的更新函數與 destory 時會執行的函數。因為第一個函數就是節點本身,所以要做 DOM 相關的操作時非常方便。

講者在影片裡頭講解了他使用 Svelte Action 的心得,值得參考。

Demystifying Svelte Transitions

這篇是在講 Svelte Transition 是怎麼實作的,並且一步步拆解原始碼給你看,相當精彩!Svelte 的 transition 機制非常有趣,並不是用 JavaScript 動態操作的。

在使用 jQuery 的動畫時,jQuery 會根據你給定的參數加入 inline style 給元素,像是:

$('.div').animate({
  ...
})

jQuery 會不斷更新 inline css 的屬性直到動畫停止為止。也就是說內部的實作大概會像這樣(偽程式碼)

while (!stop) {
  updateCSS()
}

setTimeout(() => stop = true, duration);

也就是說這段時間 JavaScript 會一直執行程式碼,當應用變大的時候就會明顯影響效能。

那麼一般的框架是怎麼實作的呢?像是 Vue 與 React 當中,你可以這樣寫:

// vue
<transition name="fade">
    <p v-if="show">hello</p>
</transition>

// React
<CSSTransition in={inProp} timeout={200} classNames="fade">
  <div>
		
  </div>
</CSSTransition>

其中,Vue 會在對應的時間點分別幫你塞入 fade-leave-active fade-enter-active fade-enter 等對應的 class name;而 React 也是類似的原理,他會幫你塞入 fade-enter fade-active fade-exit fade-exit-active 等 class name。但是 transition 的方式,需要你自己在 css 裡頭定義。

這點雖然避免了透過 JavaScript 計算造成的效能問題,但有時候光靠 CSS 做動畫似乎也有些麻煩。有沒有辦法結合 JavaScript 可以動態計算的特性,再加上 CSS 動畫的輔助?有!Svelte 做到了。

在你宣告以下程式碼的時候, Svelte 會做幾件事:

<div transition:scale={{duration: 1000}}>
</div>
  1. transition 代表出場、入場都套用同樣的過場方式
  2. Svelte 開始計算這個動畫需要幾個幀數。假設動畫時間為 1000 毫秒,計算方式為 1000 / 16 為 62.5 幀。這邊取 16 代表更新率 60Hz 的情況下每個幀數所需要的毫秒數約為 16ms(1000 / 60)。
  3. Svelte 會以動畫開始時間點當作 0,動畫結束時間點當作 1,以變數 t 為命名,並且用 easing 函數當作插值給 t
  4. Svelte 會透過動態建立一個 css keyframe,並且加入此 animation 給元素
  5. 透過 stylesheet.insertRule 動態加入 style 到當前 document 中。

我們來看看原始碼:

// src/runtime/internal/style_manager.ts
export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) {
	const step = 16.666 / duration;
	let keyframes = '{\\n';

	for (let p = 0; p <= 1; p += step) {
		const t = a + (b - a) * ease(p);
		keyframes += p * 100 + `%{${fn(t, 1 - t)}}\\n`;
	}

	const rule = keyframes + `100% {${fn(b, 1 - b)}}\\n}`;
	const name = `__svelte_${hash(rule)}_${uid}`;
	const doc = node.ownerDocument as ExtendedDoc;
	active_docs.add(doc);
	const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet);
	const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});

	if (!current_rules[name]) {
		current_rules[name] = true;
		stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
	}

	const animation = node.style.animation || '';
	node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`;

	active += 1;
	return name;
}

這個函數執行之後會產生類似下面的 css:

@keyframes __svelte__dynamic_hash_name {
  0% {
    transform: scale(0);
  }
  1.6% {
    transform: scale(0);  
  }
  ...
  100% {
    transform: scale(1);
  }
}

以上為範例程式碼,實際情形會根據 duration 及 easing 函數不同。Svelte 不僅會幫你建立 transition,也會幫你管理這些 transition。例如在 transition 做到一半停止時,Svelte 會幫你把 animation 刪除並停止,也會幫你管理各種生命週期。

這樣一來同時結合了 CSS Animation 的效能,同時也達到用 JavaScript 動態控制的好處。

想要看更多的話,可以實際到影片當中瞧瞧,講者講得非常清楚!

Futuristic Web Development

Rich Harris 本人現身說法,來講述下一代網站開發的流程變化。不過目前的說法都還不是最終定案,所以就先抱著期待的心看看就好。

以往我們打包資源時,都是用像 rollup 或是 webpack 的方式作打包,並透過他們的依賴追蹤機制來更新,當時的瓶頸在於瀏覽器普遍看不懂 ES6 語法及 import 機制,為了讓開發者能夠更好管理程式碼,才催生這些工具產生。現在瀏覽器支援逐漸完整,越來越多的應用是直接仰賴於原生的 ES Module 上面。例如最近出的 VuePressSnowpack 都是直接走原生的 ES Module 應用,最大的好處在於我們不需要等待那麼長的時間才能看到結果,而是直接透過瀏覽器的機制引入,所以也不用再等 bundler 們把程式碼編譯好。Svelte 在之後也預計使用 snowpack ,有興趣的話可以瞧瞧。

後記

我在這次 IT 鐵人賽有拍了一系列關於 Svelte 的教學影片,講解了 Svelte 的各種功能、進階應用、常見 UI 實作、Svelte 原理,到最後實作一個簡單版本的 Svelte,有興趣的話都可以來瞧瞧。

後續我也會將影片當中的內容轉成文章方便閱讀,請大家持續關注我的部落格或是直接訂閱 RSS