我是如何開發(fā)了一個前端庫 or 框架?
前言
前端發(fā)展速度已經(jīng)遠遠超出了我們的預計范圍,前端基于 JS 的框架/庫更是層出不窮。
那么框架與庫有什么區(qū)別呢?庫更多是一個封裝好的特定的集合,提供給開發(fā)者使用,而且是特定于某一方面的集合(方法和函數(shù)),庫沒有控制權,控制權在使用者手中,在庫中查詢需要的功能在自己的應用中使用,我們可以從封裝的角度理解庫;框架顧名思義就是一套架構,會基于自身的特點向用戶提供一套相當于叫完整的解決方案,而且控制權的在框架本身,使用者要找框架所規(guī)定的某種規(guī)范進行開發(fā)。
我們嘗試舉個例子:你想烹飪一條魚,這時你需要一些原料,比如油、鹽、醋、蔥、香料以及烹飪工具等,同時魚也是你需要的主要材料,當你聚齊所有材料時,再經(jīng)過烹飪就得到成品;現(xiàn)在我們進行對比,其中油、鹽、醋、蔥、香料以及烹飪工具和魚其實就是庫,組合到一起就成了框架,成品就是最終開發(fā)好的應用,或者這樣能是你更好里的理解框架與庫之間的區(qū)別。
我們熟知的 React,它就是一個庫,官網(wǎng)稱是一個用于構建用戶界面的 JavaScript 庫。剛接觸 React 的時候,在 JS 文件 中寫 HTML 語法 覺得很奇妙,后來就覺得自己可不可以也搞一個庫來鍛煉下自己的能力呢!于是在業(yè)余時間自己通過各種摸索搞了一個迷你版 JSX,但是在數(shù)據(jù)驅動層面上遇到了一些問題,幾天的輾轉反側,自己終于想明白了。我為什么先要做一個仿 React 的庫呢?我能不能換一種方式來實現(xiàn)這種類 JSX 語法呢!我們知道 ES6 語法中有模板字符串,你可以在里面寫 HTML 標簽,并且在字符串中可以嵌入變量。找到了開發(fā)方向,我就開始搜索各種資料,正式進入開發(fā)庫的過程。這個過程是非常痛苦的,但慶幸的是庫的開發(fā)都是我一個人參與的,我可以按照我的想法去做,去完善它。
我將這個庫命名為 Strve.js,為什么會這樣命名呢?它其實是字符串(String)與 視圖(View)的名稱縮寫拼接而成。它實現(xiàn)了在 JS 字符串中寫 HTML 標簽,并且可以嵌入變量,達到了數(shù)據(jù)驅動視圖的效果。并且你可以靈活地分離代碼塊,提高工作效率。到這里你會覺得這有什么難的,使用 innerHTML 也可以實現(xiàn)類似的效果。效果是一樣的,但是在一定場景下 DOM 性能是不同的。Strve.js 另一個定位就是一個輕量級的 MVVM 框架,你只需要關心數(shù)據(jù)操作,其他事情由 Strve.js 內部的 Virtual DOM 來處理,這也是目前優(yōu)化 DOM 性能的一種常用手段。
最終,功夫不負有心人。在 2021 年 11 月 3 日發(fā)布了第一個版本 1.0.4,然后接下來就是不斷地更新迭代,讓它的生態(tài)更加完善,更加符合一個庫或者框架的要求。中途也遇到過架構重新搭建等等情況,到目前為止,Strve.js 最新版本是 5.1.1,發(fā)布時間為 2023 年 1 月 1 日。
下面我來詳細介紹下 Strve.js。
文檔
Strve.js 文檔是基于 VitePress 搭建,分別部署到 Github 和 Gitee 上。
Github 中文網(wǎng)址:https://maomincoding.github.io/strve-doc/zh
Gitee 中文網(wǎng)址:https://maomincoding.gitee.io/strve-doc/zh
下面,將根據(jù)文檔內容分成幾大模塊,分享我設計這個庫時的心得體會。
開始
首先,我們進入開始頁。嘗試學習一樣新的技術,最簡單的就是做一個小的案例。通過這些簡單的案例來進一步學習它的用法,因為往往這些最簡單之處最能體現(xiàn)出它的核心用途。
你可以通過兩種方式去使用它,一種是 ES Modules,另一種是UMD。我們先分別列舉兩種方式,然后統(tǒng)一講一下代碼結構。
現(xiàn)代瀏覽器大多都已原生支持 ES 模塊。因此我們可以像這樣通過 CDN 以及原生 ES 模塊使用 Strve.js:
UMD 叫做通用模塊定義規(guī)范(Universal Module Definition)。也是隨著大前端的趨勢所誕生,它可以通過運行時或者編譯時讓同一個代碼模塊在使用 CommonJs、CMD 甚至是 AMD 的項目中運行。未來同一個 JavaScript 包運行在瀏覽器端、服務區(qū)端甚至是 APP 端都只需要遵守同一個寫法就行了。
你也可以選擇使用 <script> 標簽直接引入,這樣就可以直接在瀏覽器中打開了。
兩種不同的方式其中相同的代碼無非是這幾行,通過數(shù)據(jù)與視圖頁面的綁定,操作業(yè)務邏輯,你會發(fā)現(xiàn)Strve.js更加關注結果,至于如何實現(xiàn)這個結果的,我們并不關心。換句話說,Strve.js幫我們封裝了過程。
通過上面的案例你會發(fā)現(xiàn)一切代碼邏輯都是在JS中,可以說離 All in JS 更近了一步。
安裝
我們簡單快速地了解 Strve.js 的使用,那么我們在這一篇詳細說明下 Strve.js 有哪些安裝方法。
CDN
使用CDN在上面也提到了,一種是ES Modules,另一種是UMD。因為篇幅的原因,這里就不再復述了。
包管理器
當你構建大型應用時,推薦使用 包管理器 安裝。
命令行工具
當你構建大型應用時,推薦使用 Strve.js 提供的官方項目腳手架來搭建項目。為單頁面應用 (SPA) 快速搭建繁雜的腳手架。它為現(xiàn)代前端工作流提供了開箱即用的構建設置。
目前有兩款工具:
- CreateStrveApp
- CreateStrve
下面會再詳細介紹。
對不同構建版本的解釋
在 NPM 包的 dist/ 目錄你將會找到很多不同的 Strve.js 構建版本。這里列出了它們之間的差別:
ES Module (基于構建工具使用) | ES Module (直接用于瀏覽器) | UMD | |
完整版 | - | strve.full-esm.js | strve.full.js |
完整版(生產環(huán)境) | - | strve.full-esm.prod.js | strve.full.prod.js |
運行時版 | strve.runtime-esm.js | - | - |
運行時版(生產環(huán)境) | strve.runtime-esm.prod.js | - | - |
不同的版本:
- 完整版本:包括編譯器(用于將模板字符串編譯為 JavaScript 呈現(xiàn)函數(shù)的代碼)和運行時版本;
- 運行時版:用于創(chuàng)建實例、渲染和處理虛擬 DOM 的代碼?;旧?,它是從編譯器中刪除所有其他內容;
在Strve.js@5.1.1發(fā)布之前,用戶可以提供 HTML 字符串,我們將其編譯為數(shù)據(jù)對象后再交給運行時處理。準確地說,上面的代碼其實是運行時編譯,意思是代碼運行的時候才開始編譯,而這會產生一定的性能開銷,因此我們也可以在構建的時候就執(zhí)行編譯程序將用戶提供的內容編譯好,等到運行時就無須編譯了,這對性能是非常友好的。 這也是為什么我們發(fā)布了不同的構建版本,以適用于不同場景。
API
API目前總共有10個。
createApp、h、setData這三個為基本API,主要用于渲染頁面;onMounted、onUnmounted這兩個API為生命周期鉤子函數(shù);nextTick、domInfo這兩個API則為與DOM相關的API;version則為輔助API,用于獲取Strve.js版本號;propsData、defineCustomElement則為特性API,為適配一些特性而設計。
其中,值得一提的是defineCustomElement。這個API用于支持 Web Components 的引入,它可以傳兩個參數(shù)。
第一個參數(shù)是對象類型,對象屬性如下:
屬性 | 類型 | 必選 | 含義 |
id | ? | 是 | 原生自定義組件ID,應保持其唯一性 |
template | ? | 是 | 返回一個模版字符串函數(shù) |
styles | ? | 否 | 原生自定義組件樣式集合 |
attributeChanged | ? | 否 | 原生自定義組件監(jiān)聽屬性集合 |
immediateProps | ? | 否 | 原生自定義組件是否開啟立即監(jiān)聽屬性變化 |
lifetimes | ? | 否 | 原生自定義組件生命周期,與Web Components生命周期一致 |
第二個參數(shù)是字符串類型,原生自定義組件的名稱,名稱中必須含有-字段。
示例1:
示例2:
下面,我發(fā)布了一個Web Components的案例。
預覽鏈接:https://codepen.io/maomincoding/pen/RwBBZpr
用法
目前共有11種用法,有些用法是對一些API的補充。
其中數(shù)據(jù)綁定、屬性綁定、條件渲染、列表渲染、事件處理 這五種用法為基本用法。詳細用法可以去文檔中查閱,這里就不做贅述。
其余6種則為拓展用法,比如里面提到的靜態(tài)標簽中$key,比如我們在更改數(shù)據(jù)時,Strve.js內部需要用到Diff算法進行差異對比,但是也不是所有的節(jié)點都需要進行差異對比,像一些靜態(tài)文本,前后變化都一樣,就不需要再參與對比。那么用戶在一些需要變化的動態(tài)節(jié)點上提供$key靜態(tài)標記,標識這需要動態(tài)變化。
還有我們也可以自己封裝自定義組件,我將其命名為命名功能組件,比如像下面這樣。另外,你需要父子傳值,也可以通過搭配$props實現(xiàn)。
我們也內置了很多組件,比如fragment標簽,它創(chuàng)建一個文檔片段標簽。它不是真實 DOM 樹的一部分,它的變化不會觸發(fā) DOM 樹的重新渲染,且不會對性能產生影響。
最后,我們在上文中提到支持Web Components,這里做一下補充。
自定義元素的主要好處是,它們可以在使用任何框架,甚至是在不使用框架的場景下使用。當你面向的最終用戶可能使用了不同的前端技術棧,或是當你希望將最終的應用與它使用的組件實現(xiàn)細節(jié)解耦時,它們會是理想的選擇。
工具
到目前為止,Strve.js不僅僅是一個渲染頁面的庫,它可以搭配其他工具為你生成一個項目搭建框架。
現(xiàn)在有4款工具,分別是CreateStrveApp、CreateStrve、StrveRouter、BabelPluginStrve。
CreateStrveApp與 CreateStrve 都是快速構建 Strve.js 項目的命令行工具,與早期的腳手架 CreateStrve 相比,CreateStrveApp 更好,可以直接輸入命令快速創(chuàng)建 Strve 項目。CreateStrveApp 是使用 Vite 構建的,這是一個新的前端構建工具,可以顯著提升前端開發(fā)體驗。
當我們使用Strve.js構建單頁面應用時,一旦頁面加載完畢,整個頁面就不會因為用戶的操作而重新加載或者頁面跳轉,沒有頁面的跳轉,是如何實現(xiàn)頁面跳轉的效果呢?使用路由機制,實現(xiàn)內容的切換,實現(xiàn)不同內容的展示。
StrveRouter 是 Strve.js 的官方路由管理器。它與 Strve.js 的核心深度集成,輕松構建單頁應用程序。你可以通過下面代碼快速了解StrveRouter的使用。
我們在上文中的提到的編譯器就是指的是BabelPluginStrve,BabelPluginStrve是一款babel 插件,將 HTML 模板字符串轉化為 Virtual Dom。從之前的運行時轉移到編譯時,大幅度提高渲染性能。
相關安裝與使用方式可以參照文檔。
其他
目前總共分為5個模塊,分別為更新日志、IDE支持、UI 框架、瀏覽器兼容性、關于。
其中值得一提的是,在使用編輯器時,如何使HTML標簽在模版字符串中高亮顯示。比如這里我們使用Visual Studio Code時,下載es6-string-html插件后,在h``中間添加 /* html */。
另外,UI框架除了支持Bootstrap5、Tailwindcss、還支持Quark,Quark 是一款基于 Web Components 的跨框架 UI 組件庫。它可以同時在任意框架或無框架中使用。
這是基于Quark的預覽地址:??https://codepen.io/maomincoding/pen/MWOmyLW??
然后,Strve.js也可以跟Three.js搭配使用,以下是預覽鏈接:??https://codepen.io/maomincoding/pen/GRBYzzM??
結語
至此,關于Strve.js的介紹到這里就結束了,感謝閱讀。如果大家想進一步了解Strve.js,可以到官方文檔查閱。Strve.js我也會繼續(xù)維護下去,雖然路很長,很難走,但是我也不會放棄。
Strve.js 源碼倉庫:https://github.com/maomincoding/strve
Strve.js 中文文檔:??https://maomincoding.gitee.io/strve-doc/zh/??
本文轉載自微信公眾號「前端歷劫之路」,可以通過以下二維碼關注。轉載本文請聯(lián)系前端歷劫之路公眾號。