簡介: 絕不誇張的說,Vite 給前端帶來的絕對是一次革命性的變化。或者也能夠說是 Vite 背後整合的 esbuild 、 Browser es modules、HMR、Pre-Bundling 等這些社區中關於 JS 編譯發展的先進工具和思路,在 Vite 這樣的整合推進下,給前端開發帶來了革命性變化。css
做者 | 風水
來源 | 阿里技術公衆號html
去年發表的《一個好的組件應該是什麼樣的?》 一文介紹了藉助 TypeScript AST 語法樹解析,對 React 組件 Props 類型定義及註釋提取,自動生成組件對應 截圖、用法、參數說明、README、Demo 等。在社區中取得了比較好的反響,同時應用在團隊中也取得了較爲不錯的結果,如今內部組件系統中已經累計使用該方案沉澱 1000+ 的 React 組件。前端
以前咱們是藉助了 webpack + TypeScript 作了一套用於開發 React 組件的腳手架套件,當開發者要組件開發時,便可直接使用腳手架初始化對應項目結構進行開發。webpack
雖然主路徑上確實解決了組件開發中所遇到的組件無圖無真相、組件參數文檔缺失、組件用法文檔缺失、組件 Demo 缺失、組件沒法索引、組件產物不規範等內部組件管理和沉澱上的問題,但 Webpack 的方案始終仍是會讓組件開發多一層編譯,當一個組件庫沉澱超過 300+ 時,引入依賴不斷增加,仍是會帶來組件編譯上的負荷致使開發者開發體驗降低。web
一 Vite 帶來的曙光
Vite 給前端帶來的絕對是一次革命性的變化,這麼說絕不誇張。json
或許應該說是 Vite 背後整合的 esbuild 、 Browser es modules、HMR、Pre-Bundling 等這些社區中關於 JS 編譯發展的先進工具和思路,在 Vite 這樣的整合推進下,給前端開發帶來了革命性變化。瀏覽器
我很早就說過,任何一個框架或者庫的出現最有價值的必定不是它的代碼自己,而是這些代碼背後所帶來的新思路、新啓發。因此我在寫文章的時候,也很注重能把我思考最後執行的整個過程講清楚。前端工程師
Vite 爲何快,主要是 esbuild 進行 pre-bundles dependencies + 瀏覽器 native ESM 動態編譯,這裏我不作過多贅述,詳細參考:Vite: The Problems框架
在這個思路的背景下,回到咱們組件開發的場景再看會發現如下幾個問題高度吻合:less
- 組件庫開發,實際上不須要編譯所有組件。
- 組件開發,編譯預覽頁面主要給開發者使用,瀏覽器兼容可控。
- HMR(熱更新)能力在 Vite 加持下更加顯得立竿見影,是以往組件開發和調試花費時間最多的地方。
- Vite 中一切源碼模塊動態編譯,也就是 TypeScript 類型定義和 JS 註釋也能夠作到動態編譯,大大縮小編譯範圍。
那麼,以往像 StoryBook 和以前咱們用於提取 tsx 組件類型定義的思路將能夠作一個比較大的改變。
以前爲了獲取組件入參的類型數據會在 Wwebpack 層面作插件用於動態分析 export 的 tsx 組件,在該組件下動態加入一段 __docgenInfo 的靜態屬性變量,將從 AST 分析獲得的類型數據和註釋信息注入進組件 JS Bundle,從而進一步處理爲動態參數設置:
TypeScript 對組件 Props 的定義
分析注入到 JS Bundle 中的內容
分析轉換後實現的參數交互設置
因此對於組件來講,實際上獲取這一份類型定義的元數據對於組件自己來講是冗餘的,不論這個組件中的這部分元數據有沒有被用到,都會在 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` 運行模塊
預期設想效果,請點擊放大查看:
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 類型,並做爲模塊返回給前端消費。
其加載過程就能夠當作是一個虛擬的模塊,能夠理解爲你能夠經過直接 import 一個虛擬的文件地址,獲取到對應的 React 組件元信息:
// React Component import Component from './component1.tsx'; // React Component Props Interface import ComponentTypeInfo from './component1.tsx.type.json'; // or const 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 和 Rollup 的關係,在模塊解析過程當中依賴了大量 Vite 的 Server 提供的服務端中間件能力。致使在考慮到 Build 態時,才意識到其中的問題,最後幾乎從新寫了以前的加載邏輯。
四 總結
我姑且把這個方案(套件)稱之爲 vite-comp,其大體的構成就是由 Vite + 3 Vite Pugins 構成,每一個插件相互不耦合,相互職責也不相同,也就是說你能夠拿到任意一個 Vite 插件去作別的用途,後續會考慮單獨開源,分別是:
- Markdown,用於解析 .md 文件,加載後可獲取原文及 jsx、tsx 等可運行區塊。
- TypeScript Interface,用於解析 .tsx 文件中對於 export 組件的 props 類型定義。
- Vite Comp Runtime,用於運行組件開發態,編譯最終組件文檔。
結合 Vite,已經實現了 Vite 模式下的 React、Rax 組件開發,它相比於以前使用 Webpack 作的組件開發,已經體現出瞭如下幾個大優點:
- 無懼大型組件庫,即便有 2000 個組件在同一個項目中,啓動依舊是 <1000ms。
- 高效的組件元數據加載流,項目一切依賴編譯按需進行。
- 毫秒級熱更新響應,藉助 esbuild 幾乎是按下保存的一瞬間,就能夠看到改動效果。
預覽體驗:
啓動
Markdown 組件文檔毫秒級響應
TypeScript 類型識別
Vite 如今仍是隻是剛剛起步,這種全新的編譯模式,已經給我帶來了很是多的開發態收益,結合 Vite 的玩法將來必定還會層出不窮,好比 Midway + lambda + Vite 的前端一體化方案也是看得讓人拍案叫絕,在這個欣欣向榮的前端大時代,相信不一樣前端產物都會和 Vite 結合出下一段傳奇故事。
我是一個熱愛生活的前端工程師!Yooh!
相關連接
https://vitejs.dev/guide/why.html#the-problems
https://d.umijs.org/
https://ice.work/
前端開發技術圖譜
6 大知識點,14 個課程,680 個課時,將前端開發知識和實戰經驗融入圖譜,包含 HTML 、CSS、JavaScript 、jQuery 、Vue 、React 、Angular 、NodeJS 等前端開發必備技能,幫你迅速提高。
本文爲阿里雲原創內容,未經容許不得轉載。