假如 Web 當初不支持動態(tài)化
本文轉(zhuǎn)載自微信公眾號「前端向后」,作者黯羽輕揚。轉(zhuǎn)載本文請聯(lián)系前端向后公眾號。
楔子
Web 生而具有極其靈活的動態(tài)化基礎(chǔ)能力,諸如:
- 動態(tài)插入script標簽執(zhí)行任意腳本邏輯
- 動態(tài)插入style標簽引入任何 CSS 樣式規(guī)則
- 通過iframe標簽嵌入整站
- 以上標簽均可直接加載網(wǎng)絡(luò)資源
- 承載這些內(nèi)容的 Web 頁面部署在遠程服務(wù)器,可隨時動態(tài)更新,并且能立即生效
一直以來的探索和實踐似乎只是在不斷地發(fā)掘動態(tài)化能力的工程價值,為其尋找更合適的應(yīng)用場景,比如早期的frameset,如今的微前端/微應(yīng)用
而移動端正好相反,生而具有許多靈活性限制:
- 原生不支持動態(tài)執(zhí)行邏輯代碼
- 構(gòu)成移動應(yīng)用程序的關(guān)鍵資源大都要打入安裝包中(動態(tài)庫例外)
- 應(yīng)用程序安裝在用戶設(shè)備上,安裝包更新需經(jīng)應(yīng)用商店審核,用戶重新安裝才能生效
移動業(yè)務(wù)的發(fā)展不斷地對動態(tài)化能力提出更高的要求,但苦于缺少動態(tài)化的基礎(chǔ)能力,所以一直在探索更靈活的技術(shù)方案,像早期的熱修復/熱更新,到如今的小程序
實際上,二者在動態(tài)化技術(shù)能力上所要解決的工程問題是一致的,比如動態(tài)加載依賴庫、視圖組件、甚至整個應(yīng)用。所以不妨開個腦洞,假定 Web 不支持動態(tài)化,以 Native 的業(yè)務(wù)訴求來推演 Web 動態(tài)化技術(shù)的發(fā)展軌跡
伊始:原生 WebAssembly
- 0061 736d 0100 0000 0187 8080 8000 0160
- 027f 7f01 7f03 8280 8080 0001 0004 8480
- 8080 0001 7000 0005 8380 8080 0001 0001
- 0681 8080 8000 0007 9080 8080 0002 066d
- 656d 6f72 7902 0003 6763 6400 000a ab80
- 8080 0001 a580 8080 0001 017f 0240 2000
- 450d 0003 4020 0120 0022 026f 2100 2002
- 2101 2000 0d00 0b20 020f 0b20 010b
從前,Web 應(yīng)用程序只能被打包成這種wasm的二進制格式,發(fā)布到各大瀏覽器應(yīng)用商店。期間,不僅要等待數(shù)天的審核,通過之后還要等用戶主動安裝更新,等到新版本真正“生效”(覆蓋大多數(shù)用戶),可能已經(jīng)是數(shù)月之后了
版本更迭慢,無論是戰(zhàn)略性的重要功能還是十萬火急的問題修復,都無法及時觸達用戶。即便線上著火了,最快速的救火方案也要幾天甚至幾周之后才能起到作用
為了能夠更快地修復問題、降低風險,熱修復方案的探索就此展開
浪花:為熱修復引入腳本語言 JavaScript
熱修復意味著要加載并運行(安裝包之外的)邏輯代碼,所以有人直接從 WebAssembly 模塊加載機制入手,研究出了一些 Hook 方案,能夠動態(tài)地換掉某些模塊/文件
也有人沿著這個方向走得更遠,權(quán)衡時效性、性能、兼容性與穩(wěn)定性,通過編譯插樁、工程配套設(shè)施、運行時框架等手段解決了模塊依賴、版本管理、差量更新等問題,將應(yīng)用程序的各個功能模塊插件化
還有人另辟蹊徑,引入輕量級的腳本語言運行時(如 JavaScript 引擎),并在瀏覽器原生 WebAssembly 與 JavaScript 世界之間架起一座橋梁,允許通過 JavaScript 調(diào)用原生的系統(tǒng)平臺能力,從而擴展出了動態(tài)化的基礎(chǔ)能力
動態(tài)化漾起了一道波紋,緊接著是呼嘯而來的動態(tài)更新浪潮
海嘯:基于 JavaScript 的動態(tài)更新
往動態(tài)化方向邁出第一步之后,離全面動態(tài)化的大好前景也就一步之遙了:
Any application that can be written in JavaScript, will eventually be written in JavaScript. —— Jeff Atwood
(摘自The Principle of Least Power)
全面動態(tài)化意味著要:
- 將應(yīng)用程序中所有能夠動態(tài)化的部分全都遷由 JavaScript 實現(xiàn)
- 將龐大的 JavaScript 代碼按功能模塊組織起來,并管理好功能模塊之間的依賴關(guān)系
從而實現(xiàn)以功能模塊為單位的快速迭代,相當于將熱修復技術(shù)應(yīng)用到問題修復之外的需求迭代上,既不用發(fā)版,免去了審核周期,也不需要等待用戶主動安裝,新功能得以動態(tài)發(fā)布并迅速覆蓋到活躍用戶
堤壩:容器概念形成
隨著動態(tài)化程度的不斷提升,JavaScript 在應(yīng)用程序中的占比越來越高,最終僅剩余無法動態(tài)化(或沒有必要動態(tài)化)的部分仍由 WebAssembly 實現(xiàn),包括:
- 系統(tǒng)平臺能力橋接
- 基礎(chǔ) UI 控件、交互能力
- 視圖層框架(歷史棧管理、生命周期支持等)
- 特定業(yè)務(wù)領(lǐng)域能力(例如多媒體內(nèi)容生產(chǎn)、IM SDK 等)
- 通信機制(廣播、狀態(tài)共享等)
這些部分形成了容器(原生外殼),相當于運行在瀏覽器中的一個動態(tài)化運行時,在容器圈定的能力范圍內(nèi),業(yè)務(wù)能夠充分利用動態(tài)優(yōu)勢,實現(xiàn)快速修復、快速發(fā)布、快速觸達、快速迭代
但隨容器概念一同出現(xiàn)的,除了賦能業(yè)務(wù)跑得更快之外,還有動態(tài)業(yè)務(wù)與容器之間的依賴問題:
- 如何解除二者之間的強耦合,如路由、混合視圖容器等場景?
- 如何識別出二者之間的依賴關(guān)系?
- 如何保障依賴關(guān)系是可控的,比如禁止將依賴新能力的動態(tài)業(yè)務(wù)發(fā)布到舊容器中?
通過工程配套設(shè)施將依賴管束起來之后,接下來的首要問題是想辦法保證動態(tài)業(yè)務(wù)所依賴的底層容器的可靠性
邊界:HTML、JavaScript、CSS 構(gòu)成容器標準
隔離變化的慣用手段是加一層抽象,將變化的部分置于抽象層之下:
- BOM API:對系統(tǒng)平臺、視圖層框架能力以及通信機制的抽象
- Native Module API:對特定業(yè)務(wù)領(lǐng)域能力的抽象
- DOM API:對基礎(chǔ)視圖渲染能力的抽象
- JS API:對 JavaScript 運行時的抽象
- CSS:對樣式、布局能力的抽象
- HTML:對基礎(chǔ) UI 控件、交互能力的抽象
抽象出的這些標準確立了穩(wěn)固的容器邊界,邊界之內(nèi),動態(tài)業(yè)務(wù)能夠肆意發(fā)揮,邊界之下,容器同樣能夠不斷精進、豐富容器能力,將邊界拓寬。同時,具有標準定義的 API 能夠以結(jié)構(gòu)化的形式維護起來,對于開發(fā)體驗大有裨益
云海:瀏覽器支持加載網(wǎng)絡(luò)資源
另一方面,在標準化的過程中,一些動態(tài)化業(yè)務(wù)實踐也沉淀到了容器之中,例如:
- 動態(tài)腳本:script支持加載網(wǎng)絡(luò)資源
- 動態(tài)樣式:style支持加載網(wǎng)絡(luò)資源
- 動態(tài)路由:瀏覽器支持直接通過 URL 載入、或通過iframe嵌入網(wǎng)絡(luò)應(yīng)用程序
雖然從熱修復開始就能夠從CDN拉取 JS 文件,運行時動態(tài)解釋執(zhí)行了,但容器標準不僅對這種方式提供了便捷支持,還將動態(tài)化的基礎(chǔ)能力從邏輯擴大到了視圖、樣式、靜態(tài)資源等等
至此,動態(tài)化最關(guān)鍵的基礎(chǔ)能力已經(jīng)完備了。遷至 JavaScript 的功能模塊甚至能夠進一步部署到云端,實現(xiàn)離線集成、在線托管兩種模式的靈活切換
一色:同步、異步模式切換自如
完備的動態(tài)化基礎(chǔ)能力解鎖了許多新玩法,例如:
- 模塊化(加載器)
- 代碼拆分
- SSR
- Hydration
- lazy 組件
- Suspense
將業(yè)務(wù)模塊(bundle)進一步拆分成功能模塊(chunk),并將非核心模塊異步出去,實現(xiàn)動態(tài)按需加載,例如第三方 JS SDK、jQuery 插件、以及分享/評論/城市選擇等重磅組件
對于內(nèi)容呈現(xiàn)的偏靜態(tài)場景,還可以通過 SSR 在服務(wù)端完成(大部分)頁面渲染工作,加快首屏內(nèi)容展現(xiàn)
另一方面,Hydration、lazy 組件、Suspense 等運行時特性使得在線的動態(tài)部分能夠與離線的非動態(tài)部分充分融合,實現(xiàn)更細粒度的業(yè)務(wù)動態(tài)化,讓在線托管真正成為一種部署選項
與此同時,動態(tài)業(yè)務(wù)自身的組件化程度也在不斷加深,前端開發(fā)的核心工作從頁面、模塊開發(fā)轉(zhuǎn)向了組件、編排邏輯開發(fā)
流云:數(shù)據(jù)驅(qū)動的前端應(yīng)用程序
組件體系趨向成熟之后,一個由來已久的概念終于徹底浮出水面——數(shù)據(jù)驅(qū)動
從前后端分層的數(shù)據(jù)協(xié)議,逐漸演變成數(shù)據(jù)驅(qū)動,這里的數(shù)據(jù)包括 3 部分:
- 后端業(yè)務(wù)域數(shù)據(jù)
- 前端狀態(tài)數(shù)據(jù)
- (基于后端業(yè)務(wù)域數(shù)據(jù)的)前端衍生數(shù)據(jù)
將這些數(shù)據(jù)填入業(yè)務(wù)組件,即可渲染出完整的功能模塊(無論是在客戶端還是服務(wù)端),再將其放置到視圖容器中合適的坑位里,就完成了一次組件級的“發(fā)布”過程
這種模式涉及 5 個重要環(huán)節(jié):
- 業(yè)務(wù)數(shù)據(jù)(包括后端業(yè)務(wù)域數(shù)據(jù)和前端衍生數(shù)據(jù))的生產(chǎn)
- 業(yè)務(wù)組件(包括前端狀態(tài)數(shù)據(jù))的生產(chǎn)和維護
- 組件的渲染(業(yè)務(wù)數(shù)據(jù) + 業(yè)務(wù)組件 = 功能模塊)
- 坑位的生產(chǎn)
- 功能模塊的投放
其中,業(yè)務(wù)組件、坑位是進一步動態(tài)化的關(guān)鍵,可分為 4 個階段:
- 一個蘿卜一個坑:靜態(tài)業(yè)務(wù)組件 + 靜態(tài)坑位
- 一個蘿卜到處扔:靜態(tài)業(yè)務(wù)組件 + 動態(tài)坑位
- 多個蘿卜輪番扔:動態(tài)業(yè)務(wù)組件 + 靜態(tài)坑位
- 多個蘿卜到處扔:動態(tài)業(yè)務(wù)組件 + 動態(tài)坑位
要達到多個蘿卜到處扔的組件級動態(tài)化終極目標,就要求能夠動態(tài)發(fā)布業(yè)務(wù)組件、動態(tài)發(fā)布坑位
交融:動態(tài)業(yè)務(wù)組件 + 動態(tài)坑位
從端和云的視角來看,業(yè)務(wù)組件也可以看作數(shù)據(jù)(云)的一部分,相比之下坑位與端的關(guān)聯(lián)更為緊密,而動態(tài)化的唯一手段就是將端側(cè)的東西搬到云上去,所以要解決的關(guān)鍵問題是如何實現(xiàn)坑位的動態(tài)化
有 2 個思路:
- 干掉坑位的概念:將坑位的概念從組件級擴展到頁面級,一個頁面容器(一個 URL)即一個坑位
- 將坑位組件化:提供標準的坑位組件,就像iframe頁面是一種天然的動態(tài)坑位,可打開一個新的頁面容器加載任意 URL
對于除頁面之外的其它布局容器,如對話框、消息條、Banner 位、腰封等等,可以將坑位標準化成容器組件,與業(yè)務(wù)組件一并動態(tài)發(fā)布,將坑位的租賃關(guān)系維護在服務(wù)端,作為數(shù)據(jù)驅(qū)動的數(shù)據(jù)之一
至此,前后端分層的界限幾經(jīng)重新定義,終于迎來了 JSP/PHP 融合數(shù)據(jù)與模板的黃金年代……
原文鏈接:https://mp.weixin.qq.com/s/MssledyYt_2gqZ5Q7xgjFg