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