Vite + React 元件開發實踐

Vite + React 元件開發實踐

去年發表的《一個好的元件應該是什麼樣的?》 一文介紹了藉助 TypeScript AST 語法樹解析,對 React 元件 Props 型別定義及註釋提取,自動生成元件對應 截圖、用法、引數說明、README、Demo 等。在社群中取得了比較好的反響,同時應用在團隊中也取得了較為不錯的結果,現在內部元件系統中已經累計使用該方案沉澱 1000+ 的 React 元件。

Vite + React 元件開發實踐

之前我們是藉助了 webpack + TypeScript 做了一套用於開發 React 元件的腳手架套件,當開發者要元件開發時,即可直接使用腳手架初始化對應專案結構進行開發。

雖然主路徑上確實解決了元件開發中所遇到的元件無圖無真相、元件引數文件缺失、元件用法文件缺失、元件 Demo 缺失、元件無法索引、元件產物不規範等內部元件管理和沉澱上的問題,但 Webpack 的方案始終還是會讓元件開發多一層編譯,當一個元件庫沉澱超過 300+ 時,引入依賴不斷增長,還是會帶來元件編譯上的負荷導致開發者開發體驗下降。

一 Vite 帶來的曙光

Vite 給前端帶來的絕對是一次革命性的變化,這麼說毫不誇張。

或許應該說是 Vite 背後整合的 esbuild 、 Browser es modules、HMR、Pre-Bundling 等這些社群中關於 JS 編譯發展的先進工具和思路,在 Vite 這樣的整合推動下,給前端開發帶來了革命性變化。

我很早就說過,任何一個框架或者庫的出現最有價值的一定不是它的程式碼本身,而是這些程式碼背後所帶來的新思路、新啟發。所以我在寫文章的時候,也很注重能把我思考最後執行的整個過程講清楚。

Vite 為什麼快,主要是 esbuild 進行 pre-bundles dependencies + 瀏覽器 native ESM 動態編譯,這裡我不做過多贅述,詳細參考:Vite: The Problems

Vite + React 元件開發實踐

在這個思路的背景下,回到我們元件開發的場景再看會發現以下幾個問題高度吻合:

元件庫開發,實際上不需要編譯全部元件。

元件開發,編譯預覽頁面主要給開發者使用,瀏覽器相容可控。

HMR(熱更新)能力在 Vite 加持下更加顯得立竿見影,是以往元件開發和除錯花費時間最多的地方。

Vite 中一切原始碼模組動態編譯,也就是 TypeScript 型別定義和 JS 註釋也可以做到動態編譯,大大縮小編譯範圍。

那麼,以往像 StoryBook 和之前我們用於提取 tsx 元件型別定義的思路將可以做一個比較大的改變。

之前為了獲取元件入參的型別資料會在 Wwebpack 層面做外掛用於動態分析 export 的 tsx 元件,在該元件下動態加入一段 __docgenInfo 的靜態屬性變數,將從 AST 分析得到的型別資料和註釋資訊注入進元件 JS Bundle,從而進一步處理為動態引數設定:

TypeScript 對元件 Props 的定義

Vite + React 元件開發實踐

分析注入到 JS Bundle 中的內容

Vite + React 元件開發實踐

分析轉換後實現的引數互動設定

Vite + React 元件開發實踐

所以對於元件來說,實際上獲取這一份型別定義的元資料對於元件本身來說是冗餘的,不論這個元件中的這部分元資料有沒有被用到,都會在 Webpack 編譯過程中解析提取並注入到元件 Bundle 中,這顯然是很低效的。

在 Vite 的思路中,完全可以在使用到元件元資料時,再獲取其元資料資訊,比如載入一個 React 元件為:

import ReactComponent from ‘。/component1。tsx’

那麼載入其元資料即:

import ComponentTypeInfo from ‘。/component1。tsx。type。json’; // or const ComponentTypeInfoPromise = import(‘。/component1。tsx。type。json’);

透過 Vite 中 Rollup 的外掛能力載入 。type。json 檔案型別,從而做到對應元件元資料的解析。同時藉助 Rollup 本身對於編譯依賴收集和 HMR 的能力,做到元件型別變化的熱更新。

二 設計思路

以上是看到 Vite 的模組載入思路,得到的一些靈感和啟發,從而做出的一個初步設想。

但如果真的要做這樣一個基於 Vite 的 React 、 Rax 元件開發套件,除了元件入參元資料的獲取以外,當然還有其他需要解決的問題,首當其衝的就是對於 。md 的檔案解析。

1 元件 Usage

參照 dumi 及 Icework 所提供的元件開發思路,元件 Usage 完全可以以 Markdown 寫文件的形式寫到任何一個 。md 檔案中,由編譯器動態解析其中關於 jsx、tsx、css、scss、less 的程式碼區塊,並且把它當做一段可執行的 script 編譯後,執行在頁面中。

這樣既是在寫文件,又可以執行除錯元件不同入參下元件表現情況,元件有多少中Case,可以寫在不同的區塊中交由使用者自己選擇檢視,這個設計思路真是讓人拍案叫絕!

最後,如果能結合上述提到 Vite 的 esbuild 動態載入和 HMR 能力,那麼整個元件開發體驗將會再一次得到質的飛躍。

所以針對 Markdown 檔案需要做一個 Vite 外掛來執行對 。md 的檔案解析和載入,預期要實現的能力如下:

import { content, modules } from “。/component1/README。md”;// content README。md 的原文內容// modules 透過解析獲得的`jsx`,`tsx`,`css`,`scss`,`less` 執行模組

預期設想效果,請點選放大檢視:

Vite + React 元件開發實踐

2 元件 Runtime

一個常規的元件庫目錄應該是什麼樣的?不論是在一個單獨的元件倉庫,還是在一個已有的業務專案中,其實元件的目錄結構大同小異,大致如下:

components├── component1│ ├── README。md │ ├── index。scss│ └── index。tsx├── component2│ ├── README。md│ ├── index。scss│ └── index。tsx

在我們的設想中你可以在任意一個專案中啟動元件開發模式,在執行 vite-comp 之後就可以看到一個專門針對元件開發的介面,在上面已經幫你解析並渲染出來了在 README。md 中編寫的元件 Usage,以及在 index。tsx 定義的 interface,只需要訪問不同的檔案路徑,即可檢視對應元件的表現形態。

同時,最後可以幫你可以將這個介面上的全部內容編譯打包,截圖釋出到 NPM 上,別人看到這個元件將會清晰看到其元件入參,用法,截圖等,甚至可以開啟 Demo 地址,修改元件引數來檢視元件不同狀態下的表現形態。

如果要實現這樣的效果,則需要一套元件執行的 Runtime 進行支援,這樣才可以協調 React 元件、README。md、TypeScript 型別定義串聯成我們所需要的元件除錯+文件一體的元件開發頁面。

在這樣的 Runtime 中,同樣需要藉助 Vite 的模組解析能力,將其 URL 為 *//(README|*)。html 的請求,轉換為一段可訪問的元件 Runtime Html 返回給瀏覽器,從而讓瀏覽器執行真正的元件開發頁面。

http://localhost:7000/components/component1/README。html-/components/component1/README。html -/components/component1/README。md-Runtime Html

3 元件 Props Interface

正如我上述內容中講到的,如果利用 Vite 新增一個對 tsx 的元件 props interface 型別解析的能力,也可以做成獨立外掛用於解析 。tsx。type。json 結尾的檔案型別,透過 import 這種型別的檔案,從而讓編譯器動態解析其 tsx 檔案中所定義的 TypeScript 型別,並作為模組返回給前端消費。

Vite + React 元件開發實踐

其載入過程就可以當做是一個虛擬的模組,可以理解為你可以透過直接 import 一個虛擬的檔案地址,獲取到對應的 React 元件元資訊:

// React Componentimport Component from ‘。/component1。tsx’;// React Component Props Interfaceimport ComponentTypeInfo from ‘。/component1。tsx。type。json’;// orconst ComponentTypeInfoPromise = import(‘。/component1。tsx。type。json’);

由於這種解析能力並不是藉助於 esbuild 進行,所以在轉換效能上無法和元件主流程編譯同步進行。

在請求到該檔案型別時,需要考慮在 Vite 的 Serve 模式下,新開執行緒進行這部分內容編譯,由於整個過程是非同步行為,不會影響元件主流程渲染進度。當請求返回響應後,再用於渲染元件 Props 定義及側邊欄面板部分。

在熱更新過程中,同樣需要考慮到 tsx 檔案修改範圍是否涉及到 TypeScript 型別的更改,如果發現修改導致型別變化時,再觸發 HMR 事件進行模組更新。

三 元件 Build

以上都是在討論元件在 Vite 的 Serve 態(也就是開發態)下的情況,我們上文中大量藉助 Vite 利用瀏覽器 es module 的載入能力,從而做的一些開發態的動態載入能力的擴充套件。

但是 Vite 在元件最終 Build 過程中是沒有 Server 服務啟動,當然也不會有瀏覽器動態載入,所以為了讓別人也可以看到我們開發的元件,能夠體驗我們開發時除錯元件的樣子,就需要考慮為該元件編譯產出一份可以被瀏覽器執行的 html。

所以在 Vite 外掛開發過程中,是需要考慮在 Build 狀態下的編譯路徑的,如果是在 Build 狀態下,Vite 將使用 Rollup 的編譯能力,那麼就需要考慮手動提供所有元件的 rollup。input(entries)。

在外掛編寫過程中,一定需要遵循 Rollup 所提供的外掛載入生命週期,才能保證 Build 過程和 Serve 過程的模組載入邏輯和編譯邏輯保持一致。

Vite + React 元件開發實踐

我一開始在實現的過程中,就是沒有了解透徹 Vite 和 Rollup 的關係,在模組解析過程中依賴了大量 Vite 的 Server 提供的服務端中介軟體能力。導致在考慮到 Build 態時,才意識到其中的問題,最後幾乎重新寫了之前的載入邏輯。

四 總結

我姑且把這個方案(套件)稱之為 vite-comp,其大致的構成就是由 Vite + 3 Vite Pugins 構成,每個外掛相互不耦合,相互職責也不相同,也就是說你可以拿到任意一個 Vite 外掛去做別的用途,後續會考慮單獨開源,分別是:

Markdown,用於解析 。md 檔案,載入後可獲取原文及 jsx、tsx 等可執行區塊。

TypeScript Interface,用於解析 。tsx 檔案中對於 export 元件的 props 型別定義。

Vite Comp Runtime,用於執行元件開發態,編譯最終元件文件。

Vite + React 元件開發實踐

結合 Vite,已經實現了 Vite 模式下的 React、Rax 元件開發,它相比於之前使用 Webpack 做的元件開發,已經體現出了以下幾個大優勢:

無懼大型元件庫,即使有 2000 個元件在同一個專案中,啟動依舊是1000ms。

高效的元件元資料載入流,專案一切依賴編譯按需進行。

毫秒級熱更新響應,藉助 esbuild 幾乎是按下儲存的一瞬間,就可以看到改動效果。

預覽體驗:

啟動

Vite + React 元件開發實踐

Markdown 元件文件毫秒級響應

Vite + React 元件開發實踐

TypeScript 型別識別

Vite + React 元件開發實踐

Vite 現在還是隻是剛剛起步,這種全新的編譯模式,已經給我帶來了非常多的開發態收益,結合 Vite 的玩法未來一定還會層出不窮,比如 Midway + lambda + Vite 的前端一體化方案也是看得讓人拍案叫絕,在這個欣欣向榮的前端大時代,相信不同前端產物都會和 Vite 結合出下一段傳奇故事。

我是一個熱愛生活的前端工程師!Yooh!

相關連結https://vitejs。dev/guide/why。html#the-problemshttps://d。umijs。org/https://ice。work/

作者 | 風水

相關文章