主打一個“小巧靈動”:Vite + Svelte
一、背景
為了統(tǒng)一技術(shù)標(biāo)準(zhǔn)、提升協(xié)作效率,通常在前端團(tuán)隊(duì)內(nèi)部只會保留一套通用的研發(fā)框架。尤其是在團(tuán)隊(duì)初創(chuàng)時期,團(tuán)隊(duì)成員會考慮易用性、社區(qū)活躍程度、學(xué)習(xí)成本等因素,選擇一個合適的研發(fā)框架并一直推行和使用下去。
國內(nèi)的前端團(tuán)隊(duì)比較青睞 Vue和 React,我們團(tuán)隊(duì)內(nèi)部的主要研發(fā)框架也是 Vue,包括組件庫、工具庫、腳手架等等,都是圍繞 Vue 展開來做研發(fā)。
堅持使用一個技術(shù)棧雖然讓團(tuán)隊(duì)協(xié)作變得高效,也不用重復(fù)“造輪子”,同時提升了人員“流通性”,有它不可忽略的優(yōu)勢。但沒有任何一款框架是“銀彈”,例如 Vue,它的通用性很好,但在某些特殊場景下,我們會有更好的選擇。
例如,當(dāng)我們在開發(fā)一些小型項(xiàng)目時,會發(fā)現(xiàn)至少有兩個明顯的問題:
- 框架太“重”了:通常一個小型項(xiàng)目只由少數(shù)幾個簡單頁面構(gòu)成,如果使用 Vue 或者 React 這些框架來研發(fā)的話,有點(diǎn)“大材小用”了。構(gòu)建的產(chǎn)物中包含了不少框架運(yùn)行時代碼(虛擬 DOM、響應(yīng)式、狀態(tài)管理等),這些代碼對于小型項(xiàng)目而言是冗余的,它們影響了包體大小,進(jìn)而影響頁面的啟動速度和執(zhí)行性能。
- 打包太慢了:以 Vue CLI 為例,它的底層基于 Webpack,雖然 Webpack 具備更強(qiáng)大的功能和靈活性,但相比于 Vite、Esbuild 這些以速度為標(biāo)桿的構(gòu)建工具來說,它的速度確實(shí)慢了一些,影響了研發(fā)效率。
面對這兩個問題,我們似乎有更好的技術(shù)方案可選:使用更輕量的 Vite + Svelte。本文就是針對開發(fā)小型項(xiàng)目的場景,談?wù)?Vite+Svelte 是如何讓項(xiàng)目變得“小巧靈動”。
注意:本篇所有針對 Svelte 的性能觀點(diǎn),都是基于小型項(xiàng)目這個前提下提出。事實(shí)上,隨著項(xiàng)目規(guī)模的增長, Svelte 的性能、包體大小優(yōu)勢會逐漸減小,甚至不如 Vue 或 React。
理論上在普通 CSR 項(xiàng)目中,組件數(shù)量超過19個時, Svelte 就失去了它的包體大小優(yōu)勢。
二、Vite 和 Svelte 簡介
先了解下 Vite 和 Svelte。
2.1 Vite
Vite 是尤雨溪尤大寫的一款高效的前端構(gòu)建工具,相比于 Webpack,它最大的優(yōu)勢就是“快”。
在開發(fā)環(huán)境下,Vite 使用高性能的 Esbuild 來進(jìn)行預(yù)構(gòu)建,并利用現(xiàn)代瀏覽器對 ESM 的支持,直接將預(yù)構(gòu)建好的ES模塊丟給瀏覽器進(jìn)行解析執(zhí)行,無需在每次變更代碼時都重新編譯代碼,具有更快的冷啟動速度和熱更新效率。
在生產(chǎn)環(huán)境下,Vite 基于 Rollup 進(jìn)行打包,Rollup 同樣支持 ESM 語法,并且具有更快速高效的 Treeshaking,一般情況下,Rollup 具有更小的包體大小和更快的構(gòu)建速度。
參考 Github 上的構(gòu)建工具橫向?qū)Ρ?benchmark。
(圖片來源:https://github.com)
可以看到無論是冷啟動、熱更新還是生產(chǎn)環(huán)境打包,Vite 都是優(yōu)于 Webpack 的。尤其是在開發(fā)過程中的熱更新很快,大大優(yōu)化了開發(fā)體驗(yàn)。
2.2 Svelte
Svelte 是由 Rollup 的作者 Rich Harris(前端輪子哥) 寫的一款前端框架。在語法上,Svelte 和 Vue 類似。它和傳統(tǒng)框架(如 Vue、React)的最大差異就在于:在構(gòu)建階段,Svelte 就將代碼編譯為“純粹”的 JavaScript 代碼,幾乎沒有運(yùn)行時。
這意味著在小型項(xiàng)目中,它打出來的包更小,在運(yùn)行時,它也不需要復(fù)雜的狀態(tài)管理和虛擬 DOM,在性能上的表現(xiàn)也更好。
在前端大佬 Jacek Schae 的前端框架橫向測評中(測評簡述:使用各個前端框架來編寫同一個標(biāo)準(zhǔn) App - RealWorld,并橫向?qū)Ρ人鼈兊谋憩F(xiàn)),可以看到,Svelte 無論是在首屏渲染速度、包體大小還是代碼行數(shù)上都展現(xiàn)了較大的優(yōu)勢,全部躋身前三。
首屏渲染速度:
(圖片來源:https://medium.com)
包體大小:
代碼行數(shù):
(圖片來源:https://medium.com)
三、搭建 Vite+Svelte 項(xiàng)目
在基本了解了 Vite 和 Svelte 之后,來看看如何著手去搭建一個 Vite+Svelte 的項(xiàng)目。
3.1 全局安裝 Vite
通過 npm 全局安裝 Vite:
npm install vite -g
3.2 創(chuàng)建 Svelte 項(xiàng)目
Vite 原生支持直接通過腳手架創(chuàng)建 Svelte 項(xiàng)目,執(zhí)行以下命令:
npm create vite@latest
命令行中會出現(xiàn)引導(dǎo),按照提示輸入項(xiàng)目的名稱并直接選擇 Svelte 框架即可。
? Project name: vite-svelte-demo
? Select a framework: ? - Use arrow-keys. Return to submit.
Vanilla
Vue
React
Preact
Lit
? Svelte
Solid
Qwik
Others
? Select a variant: ? - Use arrow-keys. Return to submit.
TypeScript
? JavaScript
SvelteKit ↗
3.3 運(yùn)行項(xiàng)目
通過以下命令運(yùn)行項(xiàng)目:
cd vite-svelte-demo
npm install
npm run dev
這樣一來,整個 Vite + Svelte 的項(xiàng)目結(jié)構(gòu)就搭建好了,開箱即用。整個項(xiàng)目的目錄結(jié)構(gòu)如下:
|-node_modules -- 項(xiàng)目依賴
|-public -- 公共文件
|--vite.svg
|-src -- 源文件
|-assets
|-lib
|--App.svelte -- 項(xiàng)目根組件
|--app.css -- 項(xiàng)目根樣式
|--main.js -- 項(xiàng)目入口文件
|--.gitignore
|-- index.html -- 首頁index.html
|-- package-lock.json
|-- package.json
|-- tsconfig.json
|-- svelte.config.js -- svelte配置文件
|-- vite.config.js -- vite配置文件
可以看到,整個項(xiàng)目結(jié)構(gòu)基本和 Vue 類似,只是組件的后綴名改為了.svelte,新增了 vite.config.js 和 svelte.config.js 兩個配置文件。
3.4 配置
Vite+Svelte 支持開箱即用,項(xiàng)目的初始配置也很簡單,沒有任何額外配置。
vite.config.js:用于配置 Vite 構(gòu)建工具的行為,例如入口文件、輸出目錄、插件、devServer 等。
// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte()],
})
svelte.config.js:用于配置 Svelte 項(xiàng)目的各種選項(xiàng),例如別名、預(yù)處理器、插件等。
// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess(),
}
在工程配置這里,唯一需要注意的是,Vite 默認(rèn)情況打出的包體僅支持現(xiàn)代瀏覽器(支持 ESM ),如果要兼容低版本瀏覽器,可以使用官方提供的 @vitejs/plugin-legacy插件:
// vite.config.js
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import legacy from "@vitejs/plugin-legacy";
export default defineConfig({
plugins: [
svelte(),
legacy({
// 設(shè)置需要兼容的目標(biāo)瀏覽器版本
targets: [
"Android >= 4.4",
"iOS >= 9",
"ie >= 11",
"not Android < 4.4",
"not iOS < 9",
"not ie < 11",
],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
renderLegacyChunks: true,
}),
});
更多配置項(xiàng)可參考官網(wǎng)的 Vite 配置文檔和 Svelte 配置文檔。
四、開發(fā)體驗(yàn)優(yōu)化
項(xiàng)目搭建完成后,后續(xù)就是根據(jù)業(yè)務(wù)需求來開發(fā) Svelte 組件和完善業(yè)務(wù)邏輯。整個開發(fā)過程中,體驗(yàn)感還是不錯的。
詳細(xì)的開發(fā)流程不再贅述,想要了解更多關(guān)于 Vite 和 Svelte 內(nèi)容,可以參考 Vite 官方文檔和 Svelte 官方文檔。這里我們主要來看看 Vite 和 Svelte 分別在開發(fā)體驗(yàn)上帶來的一些優(yōu)化。
4.1 Vite 的開發(fā)體驗(yàn)太棒了
首先,Vite 的開發(fā)體驗(yàn)太棒了。它的構(gòu)建速度極快,對開發(fā)效率有很明顯的提升。
在開發(fā)環(huán)境下,Webpack 每次都需要對改動的部分進(jìn)行重新編譯打包,耗時幾秒鐘,而 Vite 則不需要重新打包,只需要把更新后的 ESM 代碼交付給瀏覽器就ok,幾乎是即時更新。
同時在構(gòu)建生產(chǎn)環(huán)境包時,也明顯比原先的 Vue 項(xiàng)目要快了近50%。別小看這一點(diǎn)速度的提升,讓整個開發(fā)體驗(yàn)好了不少。
4.2 Svelte 的語法更優(yōu)雅?
如果你跟隨上面步驟搭建了項(xiàng)目,那你可以打開項(xiàng)目中的 Counter.svelte 文件看看,會發(fā)現(xiàn) Svelte 組件的基本結(jié)構(gòu)和 Vue 幾乎一致,也是由 script、template 和 style 三部分組成,唯一的差異在于 Svelte 中不需要像 Vue 一樣額外使用來作為 DOM 結(jié)構(gòu)的根標(biāo)簽。
<script>
let count = 0
const increment = () => {
count += 1
}
</script>
<button on:click={increment}>
count is {count}
</button>
<style>
...
</style>
在語法上,Svelte 和 Vue 也非常相似,但個人更喜歡 Svelte 的一些簡潔的語法設(shè)計,舉一些例子:
插值表達(dá)式:Svelte 的插值表達(dá)式只需要單個{},而 Vue 則需要兩層{{}},并且 Svelte 還有一些簡寫語法,例如 src={src} 可以簡寫為 {src}。
// Svelte的插值表達(dá)式
<div {src}>
count is {count}
</<div>
// Vue的插值表達(dá)式
<template>
<div :src="src">
count is {{ count }}
</div>
</template>
響應(yīng)式:Svelte 聲明的變量直接支持響應(yīng)式,而在 Vue 中則需要使用 ref 來聲明。
// Svelte
let message = 'Svelte';
// Vue
import { ref } from 'vue';
const message = ref('Vue');
computed 計算屬性 / watch 監(jiān)聽:Svelte 的$: 代碼塊類似于 Vue 的 computed 計算屬性和 watch 狀態(tài)監(jiān)聽的結(jié)合體。
// Svelte的計算屬性
$: info = name + age
// Svelte的監(jiān)聽
let val
$: {
console.log(`你輸入的內(nèi)容是: ${val}`);
}
// Vue的計算屬性
import { computed } from 'vue';
const info = computed(() => {
return name + age
});
// Vue的監(jiān)聽
import { watch } from 'vue';
let val = ref('');
watch(val, (newValue, oldValue) => {
console.log(`你輸入的內(nèi)容是: ${val}`);
});
雖然差異沒有很大,但這些簡潔的語法,至少讓我個人感覺寫項(xiàng)目時更流暢,體驗(yàn)感很不錯。
這些就是 Vite 和 Svelte 帶來的開發(fā)體驗(yàn)上的一些優(yōu)化。
五、Svelte 和 Vue 性能對比
上面我們了解了如何搭建 Vite+Svelte 項(xiàng)目,并感受到 Vite 和 Svelte 帶來的開發(fā)體驗(yàn)優(yōu)化。那接下來看看這套方案在實(shí)際項(xiàng)目中的性能表現(xiàn)。
為了對比 Svelte 和 Vue 之間的性能差異,我特地找了一個小型項(xiàng)目進(jìn)行改造,用 Svelte 對它重寫,并通過 ABTest 進(jìn)行線上的性能數(shù)據(jù)對比。
5.1 首先,來看打包之后的 bundle 大小對比
Vue 的包體:
Svelte 的包體:
是否壓縮 | Vue | Svelte | 差值 |
gzip 前 | 360K | 207K | 降低 42.5% |
gzip 后 | 124K | 77K | 降低 38% |
可以看到 Svelte 相比于 Vue,在包體大小上確實(shí)優(yōu)化了不少,基本在40%左右,效果很明顯。
5.2 其次,看看本地的啟動速度(FCP)對比
在本地進(jìn)行10次平均性能測試,發(fā)現(xiàn) Svelte 的 FCP 指標(biāo)比 Vue 要快了約46%,說明 Svelte 的首屏渲染速度比 Vue 快了不少,提升很明顯。
_ | Vue | Svelte |
FCP(ms) | 1128 | 738 |
FCP(ms) | 1897 | 545 |
FCP(ms) | 990 | 701 |
FCP(ms) | 1016 | 608 |
FCP(ms) | 1002 | 620 |
FCP(ms) | 1391 | 587 |
FCP(ms) | 1294 | 662 |
FCP(ms) | 983 | 803 |
FCP(ms) | 1231 | 618 |
FCP(ms) | 1279 | 757 |
FCP 均值(ms) | 1234.7 | 663.9 |
優(yōu)化程度(%) | - | 46.2% ↑ |
下面這個錄屏是對比兩個頁面首次打開時的效果,左側(cè)為 Svelte,右側(cè)為 Vue。
5.3 最后,看看線上的啟動速度數(shù)據(jù)對比
我們通過 ABTest 進(jìn)行線上的啟動速度數(shù)據(jù)對比,在投放了1周后,數(shù)據(jù)結(jié)論如下:
平均啟動速度 | |
vue | 2493 ms |
svelte | 2132 ms |
差值 | 361 ms |
優(yōu)化程度 | 14.5% ↑ |
以看到 Svelte 在線上的啟動速度表現(xiàn)也比 Vue 要快一些,優(yōu)化程度約為14.5%。
注意:這里的平均啟動速度指標(biāo)是我們團(tuán)隊(duì)內(nèi)部定義的頁面啟動速度指標(biāo),計算方式:業(yè)務(wù)啟動速度 = 頁面加載完成時間 - 頁面入口點(diǎn)擊時間。由于我們是在客戶端 App 中進(jìn)行測試,因此該時間包含了客戶端 webview 的啟動時間。所以實(shí)際上 Svelte 對前端部分的啟動速度優(yōu)化程度可能會更高,參考前面的 FCP 指標(biāo)的本地測試結(jié)果。
總之,對于小型項(xiàng)目,Svelte 在包體大小和啟動速度上還是有不少提升的,如果你的項(xiàng)目對啟動速度有強(qiáng)要求,也可以嘗試使用 Svelte 來開發(fā)或改造,應(yīng)該會有不錯的效果。
六、Svelte 比 Vue 快在哪?
通過上面的數(shù)據(jù)分析,雖然我們了解到 Svelte 在構(gòu)建小型項(xiàng)目時確實(shí)有更快的頁面啟動速度,但具體快在哪,還需要進(jìn)一步深入分析。
我們可以通過 Chrome 的 devtool 來觀察 Svelte 和 Vue 兩者打包后的頁面在加載流程上的差異。
Vue 頁面加載流程:
Svelte 頁面加載流程:
對比二者的加載流程,可以看到耗時上有兩個主要的差異點(diǎn):
耗時差異 | Vue | Svelte |
JS Bundle 加載時間 | 627 ms | 274 ms |
Html/JS 解析耗時 | 292 + 148 + 85 = 525ms | 290 ms |
因此,在小型項(xiàng)目中,Svelte 相比于 Vue,有兩個明顯的優(yōu)勢:
- 資源加載更快:由于包體較小,所以加載首屏 JS Bundle 時網(wǎng)絡(luò)耗時更短。
- Html/JS解析執(zhí)行更快:由于 Svelte 幾乎無運(yùn)行時,因此在執(zhí)行相同業(yè)務(wù)邏輯時,解析執(zhí)行耗時更短。并且在解析執(zhí)行過程中,沒有二次加載更多的首屏 JS、CSS 資源,因此整體的解析執(zhí)行速度更快。
七、總結(jié)
總之,對于小型項(xiàng)目而言,Vite + Svelte 這對組合還是很不錯的,既提升了開發(fā)效率,又優(yōu)化了頁面性能,更加“小巧靈動”。而且這兩者的學(xué)習(xí)曲線都比較緩和,基本在2-3天內(nèi)就可以完成學(xué)習(xí)加一個簡單項(xiàng)目的改造。
一點(diǎn)題外話,很多時候大家會低估“技術(shù)嘗新”的重要性,我們被困在團(tuán)隊(duì)圈定的“技術(shù)穹頂”中,永遠(yuǎn)用一個“套路”去實(shí)現(xiàn)所有業(yè)務(wù),這會讓我們感到疲乏、缺乏價值感和動力,甚至影響工作的投入程度,久而久之,整個團(tuán)隊(duì)也會在技術(shù)上變得遲滯。
所以在有機(jī)會時,要盡量合理地發(fā)掘和嘗試新的技術(shù)方案,一方面是讓自己保持技術(shù)活力,另一方面也是在加強(qiáng)團(tuán)隊(duì)的技術(shù)沉淀。