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