前言:希望像做游戲一樣做 Web?開發的 dexteryy?同學今天在?GMTC?技術大會上又搞了一場「跨年演講」(內容超多的意思),不但現場爆滿、超時嚴重,而且一如既往的講完之后只要把講稿和幻燈片拼起來就能發出來,大家可按需取用。
亮點:為了方便大家理解,dexteryy 同學為這次分享畫了 90 張圖(工具是 Keynote),其實在內部版《Modern Web Stack》里有 120 多張圖…
大家好,我是來自字節跳動 Web Infra 部門的楊揚。在開始分享前先解釋下:
可以看到幻燈片上的標題,跟會議日程里的標題有些不一樣,「現代 Web 開發」這幾個字加上了引號,做這個修改是因為,原文很容易被斷句成「現代的,Web 開發實踐」,「現代」看上去只是一個普通的形容詞,其實「現代 Web 開發」是作為一個整體的專有名詞,來代指現在全球技術社區和全行業里,越來越重要的一個「大趨勢」(Megatrend)、一場正在進行中的「范式轉移」。
今天這場分享的主題,就是字節跳動如何把「現代 Web 開發」轉化成具體的技術棧和研發工具體系,在內部廣泛落地和從中獲益。
這次分享的內容可以分成三個部分。
第一部分,先整體回顧「傳統 Web 開發」范式中的「前端開發」技術和工程體系,有哪些瓶頸問題。
第二部分,對于在這些問題的背后、在這些問題的驅動下,正在發生的轉變,做一下歸納和比較。
第三部分,介紹字節跳動在落地和推動這種轉變中,發展和建設出的技術體系。對于字節這個「App 工廠」來說,這種發展相當于一場「引擎升級」的過程。
大家都知道字節跳動在業界有一個既含貶義也含褒義的外號,叫作「App 工廠」,如果我們從軟件研發的角度來看待這個外號,那其實在字節內部,各種產品、工具、軟件應用的開發,比大家從外部看到的更像「App 工廠」:無論數量還是多樣性,形態和場景的豐富度,都是非常高、海量的。
而這些軟件項目中,基于 Web 技術、前端技術的,占了大部分(這并不是因為字節有特殊的技術選型,而是行業的大背景和必然規律,我在19年一次關于「現代 Web 開發」的分享里有介紹過)。
由于字節有這種特點,所以前端技術和工程體系中的問題和瓶頸,在字節會體現的很全面、很典型,很多時候也會體現的更明顯。
*?現代 Web 開發的現狀與未來:https://zhuanlan.zhihu.com/p/88616149
傳統的前端技術體系,無論在字節內部,還是在全行業,都可以總結為圖上這個樣子。
圖中從下到上,代表抽象層從底層到頂層。最右邊三個方塊,都從最下面延伸到了最上面,代表它們都是端到端的解決方案,跟左邊的體系,以及彼此之間,都是割裂的,包含大量重復,這次分享因為時間關系,不講這幾個部分。
藍色的方塊都是代碼層面的,綠色的方塊都是平臺層面的。
這套體系是字節曾經的主流,是從業務中自然發展出來的,但隨著這種發展趨于停滯,這套技術棧正在同樣自然的演變成歷史遺留的「祖傳技術棧」,其中每個部分都有比較大的瓶頸問題。
我們逐個看一下圖里的每個問題域,首先是大家最熟悉的「前端腳手架」。
不管哪種形式的腳手架,本質都是復制粘貼一堆樣板代碼,組成新的項目。
雖然跟建筑行業使用的腳手架一樣,都是在搭建過程中使用,用完就放到一邊,只留下搭完的項目。但建筑腳手架拆掉之后留下的建筑,有一套不能動的鋼筋混凝土骨架,而腳手架生成的前端項目,是混雜在一起的樣板代碼,雖然有文件結構,但可以隨意修改,而且因為基建和示例代碼混在一起,所以不僅是「可以改」,而且經常是「必須改」樣板文件的內容和結構,才能完成真實項目的完整搭建。
假設一個腳手架包含 A、B、C 三塊功能,用這個腳手架創建出的三個項目,最初都是一樣的,也都包含 A、B、C 功能。
接下來,三個項目必然需要在腳手架的生成結果上,做各種增刪改,可能是因為業務需求,也可能是因為技術沉淀和工程改進。時間長了之后,三個項目之間會差別非常大,如果要做統一的改進,或者把一個項目的沉淀和改進,應用到另一個項目里,都會很困難。有時甚至要推遲需求開發,先對這些項目做一輪統一的重構,但這也只能應付眼下,之后這些項目仍然會繼續分裂和腐化。
另一方面,腳手架本身也在迭代改進,但因為腳手架是一次性的,一用即拋,這些改進不能對原先創建的項目帶來好處,引入這些改進的成本,跟從其他項目里引入改進的成本差不多。
進一步看看腳手架中「項目模板」的問題。
腳手架的必然結果,是需要各種項目模板。不但腳手架建設者需要提供多種模板,覆蓋不同的需求,使用者也經常需要復制原有模板,修改成新的模板,比如產品的技術形態是 SPA 還是 MPA,都會產生不同的模板。
圖上每個方塊,都代表一個真實存在的模板,可以看到這些模板中有大量重復、又不會完全相同的內容,升級維護模板、在模板之間同步技術沉淀,都有成本。很多模板會缺少更新,長期停滯,把成本留給搭建項目的人。
如果從項目場景的角度出發來設計和維護模板,也有相同的問題。圖中的方塊是一些最基礎、最典型的場景,和場景中的技術需求,可以看到,不同場景之間的技術需求,重合度很高。
除了場景類型,一個項目還有很多類型維度,圖中的每個方塊,代表一種維度,比如按照圖上的第三種維度,不同項目之間僅僅因為「組件庫」和「設計系統」不同,就要設計和建設不同的模板。
這些維度之間的排列組合,要么會導致模板進一步分裂和數量爆炸,每種模板的維護成本更高,應用場景更小,ROI 因此變低,更加傾向于停滯;要么會導致模板對很多維度中的需求,不做考慮,只覆蓋小部分需求,對項目開發的支持,局限在比較低的水平。
「傳統技術棧」的第三個問題域,來自前端工程化建設中常見的對 Webpack 的「包裝」。
為了避免每個前端開發者都成為「Webpack 工程師」, 很多腳手架、工程化建設,都會對 Webpack 做圖上這樣的包裝,在最上層,提供圍繞編譯構建的兩個命令 dev 和 build,搞出不同「規范」的配置文件和插件機制。
這種包裝的第一個問題,是抽象程度很有限,配置 API 的設計也五花八門,體現不同的個人偏好和業務經驗,這種配置雖然被稱作「規范」,但在真實業務項目中存在感不高,業務項目還是要直接靠 Webpack 來解決很多問題,項目中包含很多 webpack 配置,腳手架模板也包含大量相關的樣板代碼,避免不了前面說的問題。
圖中這段話來自 Redux 作者 Dan 寫的一篇文章,講 JS 工具的配置 API 的設計,這段話就是在講這方面的抽象和設計能帶來巨大的影響,有很高的門檻,需要非常嚴肅專業的對待,這種工作也需要高度的集中,而不是交給「Webpack 工程師」們搞各種各樣的「規范」。
*?The melting pot of JavaScript: https://increment.com/development/the-melting-pot-of-javascript/
業務項目深度依賴 Webpack、包含很多 Webpack 配置,還帶來另一個問題:
JS 開發工具從去年開始又出現新一輪更新換代的征兆,有人把這種趨勢稱作 「JS 的第三紀元」,新范式的項目涌現,開始進入到日常業務的開發實踐,很多場景下已經沒有 Webpack。
*?The Third Age of JavaScript: https://www.swyx.io/writing/js-third-age
圖中右側的 esbuild 和 swc 這樣的構建工具,把編譯、構建、打包、壓縮等在 Webpack 里屬于不同環節的部分,合并在一起,加上非 JS 的系統編程語言的幫助,大幅提升構建速度。另一方面,也能支持 ESM 優先的、不需要打包的構建場景。
Snowpack、Vite 這樣的工具,在此基礎上實現了開發者體驗(DX)優先的、不打包(Unbundled)的開發調試模式。
這些工具和模式跟 Webpack 的設計有一些本質矛盾,目前已經被用于公共庫的構建、業務項目的開發調試等真實場景里。
基于 Webpack 包裝的工程化建設,第三個問題是:對項目開發的工程支持只停留在比較低的水平,比如就像 dev 和 build 命令一樣,局限于跟編譯構建有關的環節。
而隨著行業和業務的發展,隨著前端技術的發展,一個前端工程的完整需求,就像圖中一樣,會包含藍色方塊代表的整個研發鏈路的每個環節,以及每個環節下面,綠色方塊代表的工程需求。這些都超出了 Webpack 的能力范圍。
剛才說的這個問題,其實也是傳統前端工程化建設本身的問題。
傳統的工程化建設,就像圖中文字說的,只能實現「代碼層面」的基礎建設,在創建項目的時候,做的事情大部分都屬于「代碼初始化」。
現代的 web 工程和前端工程,越來越多的包含「代碼層面」之外的「平臺層面」。
圖中綠色方塊代表的,是靠「代碼層面」來實現工程需求,橙色方塊代表的,是要靠「平臺層面」才能更好實現的需求。
第五個問題域是,很多前端開發場景,比如像字節內部的前端開發,都在統一收斂到 React 技術棧,但 React 本身也是有局限的。
在 Web Infra 部門建立之前,字節內部的業務方向,比如今日頭條和抖音,就已經在推進統一到 React 技術棧,目前字節的現代 Web 研發體系建設,也有意收斂和圍繞著 React 來進行。
選擇 React 的原因可以歸納為圖上這四個,其中,符合技術趨勢,設計演進更快,走在最前面,這兩點讓 React 在基礎建設中,在有基礎建設團隊支持的業務場景中,都具備很大的優勢。
圖上高亮的這句話,很好的表達了 React 引領業界和社區發展的特點。
剛才在選擇 React 的原因里,還提到 React 龐大的生態紅利或驗證規模。圖中可以看到,在比較貼近實際使用情況的依賴下載數據里,React 在絕對數量遙遙領先的情況下,增長勢頭也是更快的,React 生態下的 CRA、Next.js,單獨拿出來都達到或接近其他非 React 技術的使用量。
*?https://www.npmtrends.com/@babel/preset-react-vs-@vue/cli-shared-utils-vs-next-vs-nuxt-vs-react-vs-react-scripts-vs-vue-vs-vue-loader-vs-vite
這種生態和規模上的差距,在國內環境中也回避不了,國內 JS 開發者的數量差不多是全球的十分之一,所以單靠國內開發者,影響不了整個行業和技術社區的生態和基建,導致在業務支持和工程建設中,React 目前都無法取代。
* 「全球的 JS 開發者已經上千萬了」:https://zhuanlan.zhihu.com/p/111204309
但在工程體系中只靠 React 自己是遠遠不夠的,React 本身只解決視圖層的問題,距離一個 Web 框架還缺很多東西,在框架層面上,React 是無偏見的,比如沒有限制路由實現、組件類型、SSR 解決方案等,也沒提供默認的配置和工具體系,跟一個真正框架的必備要素,是完全相反的。
* Advancing the web framework ecosystem (Chrome Dev Summit 2019): https://youtu.be/QDljY2I1Pfw
由于 Webpack、React 都不解決全鏈路的問題,缺乏框架級別的解決方案,很多前端項目會把目光轉向發展時間更長、已經形成框架級別基建的服務器端框架領域,但這方面的 Node.js 框架,也有瓶頸問題。
業界主流的 Node.js 框架 NestJS 的作者,在文檔首頁的設計理念部分,就指出 React、Vue、各種開發工具,都沒有解決「應用架構」的問題,而 NestJS 就是要提供開箱即用的應用架構。
傳統的前端開發局限于視圖層,跟完整的軟件開發、產品開發相比,最缺的就是「應用架構」。
* NestJS Philosophy: https://docs.nestjs.com/#philosophy
但是 Node.js 框架能提供的,只是「服務器端應用架構」,是以服務器端開發為中心的。
有些情況下,比如活動頁面,客戶端部分本來就很薄,談不上「客戶端應用架構」,但這種情況下的業務關注點仍然在客戶端部分,如果基于有完整服務器端應用架構的 Node.js 框架去開發這種項目,對前端開發者來說有些偏離重點。
在另一些情況下,客戶端部分比較厚,這種情況下需要的「客戶端應用架構」,就像圖中右邊的藍色部分,跟左邊代表「服務器端應用架構」的橙色部分,是完全不同的,如果使用 Node.js 框架,藍色部分仍然需要自己摸索和搭建。
不管什么軟件架構,核心都是「分層」和「關注點分離」,完善的現代 Web 工程里,「服務器端應用架構」和「客戶端應用架構」不會割裂,而是混為一體的。
*?The Clean Architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
除了架構,Node.js 框架也解決不了前面說的其他問題,同時還引入了圖中右邊的新問題:
業務邏輯既分散割裂,同時又重復,導致項目變得「低內聚高耦合」。
在「前后端分離」之后從「后端項目」里獨立出來的「前端項目」,使用 Node.js 框架之后,又混入了很重的后端業務邏輯和研發需求。而 Node.js 框架隨著發展,本質也越來越清楚:還是服務于專業服務器端開發的,降低不了前端開發者的服務器端開發門檻,這是一個最大的瓶頸。
從這張圖可以看到,包含服務器端部分的「前端項目」,并不是真正的「全棧」,只是包含了服務器端最上面的、很薄的一層,解決 Web 和 BFF 需求(注意這里說的 Web 是指處理 HTML 請求,而 BFF 這個詞在國內有很多誤用,實際上是專指面向特定 UI 的 API 服務)
對于前端項目里的這些服務器端需求,用 Node.js 框架來開發,很多時候帶來的問題比解決的問題更多。
*?The BFF Pattern (Backend for Frontend): An Introduction: https://blog.bitsrc.io/bff-pattern-backend-for-frontend-an-introduction-e4fa965128bf
最后,看下最底層的 IaaS 和 PaaS 部分。
傳統云計算中的 IaaS 和 PaaS,就像圖中的綠色部分,從最底層到最上層,有這樣幾層,最上面這層,有不同的平臺和服務形態,傳統上,前端項目跟后端項目一樣,直接部署這一層上,比如字節內部后端技術棧的項目,都部署在被稱作 TCE 的 PaaS 上,而前端技術棧的項目,以前也只能這樣部署。
* 《容器云在頭條的落地和實踐》:https://time.geekbang.org/dailylesson/detail/100016772
但是在這種 PaaS 上或直接在 IaaS 上做部署和運維,對前端開發者來說,不但復雜低效,而且在很多前端部署需求上,沒有獲得任何支持。
比如圖中粉色部分中這些前端部署方面的通用需求。由于 IaaS 和 PaaS 都源自后端開發場景,是后端研發技術的平臺化沉淀,設計和演進都天然的受到局限,對前端研發場景缺乏理解,就算理解,也無法在相同的基礎設施里去兼顧這些前端部署需求。
也就是說,多數前端項目和底層的 IaaS 和 PaaS 之間,存在一大片空白,也就是圖中粉色的區域。
在真實業務中,為了避免每個前端項目都直接跟 IaaS 和 PaaS 打交道,很多業務都會有意無意的做一些集中建設,填補這片空白。
比如字節的一些 web app 項目,會由后端負責的 go server 來運行。
很多有 SSR 需求的業務,會搞一個統一的 SSR server 項目,在開發各種前端倉庫的時候,需要本地有 SSR server 的倉庫,才能運行和調試這些前端項目。
像這樣的集中建設,要么把前端項目中的正常組成部分,放到了前端開發者無法掌控的地方;要么反復重復的建設出被業務需求扭曲的部署方案,難以沉淀和演進,最大化的提效。
所以圖中粉色這片區域的空缺,該由什么來填補,是傳統前端技術棧里的一個大問題。
回顧了「祖傳技術棧」的這些問題,接下來我們看下在這些問題的驅動下,業界和技術社區里已經形成的趨勢,這些趨勢帶來的發展和優勢,也反過來,讓前面說的這些問題變得更明顯,更急需解決。
這種趨勢可以歸納為「傳統 Web 開發」范式到「現代 Web 開發」范式的轉變。我在圖中這兩次技術活動上,分別介紹過「現代 Web 開發」范式的起源、背景和發展狀態,這里不再重復了。
*?現代 Web 開發的現狀與未來(JSDC 2019 演講全文):?
https://zhuanlan.zhihu.com/p/88616149
*?理解現代 Web 開發(JSConf China 2017):
https://youtu.be/515pkXWHgnE
https://2017.jsconfchina.com/files/02-modern-web-dev-dexteryy.pdf
這種轉變,可以稱得上是一場「范式轉移」,原有的理解和習以為常的假設被打破,在原有的東西上小修小補解決不了問題,需要建一套新的東西,做出根本性的變化。
圖中左邊藍色部分是最能代表「傳統 Web 開發」的一些技術棧和規范,右邊橙色部分是在「現代 Web 開發」趨勢下出現的技術棧和技術形態,接下來把它們展開看看包含哪些要素,就會看到左邊的東西跟右邊的東西,差別非常大。
* 「范式轉移」:https://wiki.mbalib.com/wiki/%E8%8C%83%E5%BC%8F%E8%BD%AC%E6%8D%A2
Ruby on Rails 是「傳統 Web 開發」范式的一個典型代表, 本質上是以服務器端開發為中心的、MVC 架構的「服務器端 Web 框架」,就像圖上右邊這樣,產出網頁和 API。
網頁部分需要的前端代碼和工程化,一般包含在同一個倉庫里,框架本身也會內置一些前端相關的工程化,經常會需要被專業全面的前端工程化替代掉。
*?The Asset Pipeline: https://guides.rubyonrails.org/asset_pipeline.html
* Hotwire:?https://github.com/hotwired/hotwire-rails
「十二要素應用宣言」本質上是一種工程標準,是進入云計算時代之后,服務器端應用和工程項目中形成的規范,這套標準可以保證應用能很好的運行在「后端 PaaS」或其他的傳統云計算平臺上。?
可以看到,在 Node.js 幫助下形成的前端工程規范,不管有沒有服務器端代碼,也都受到了這個規范很大影響。
*?The Twelve-Factors App: https://12factor.net/
MERN 技術棧,是前端代碼和工程,從其他技術棧的服務器端框架中「分離」出來之后,又融合相同技術棧的服務器端框架,形成的「全棧」開發方案。
組成 MERN 的 4 個首字母,能體現這套技術棧的本質,也就是圖上右邊的 4 個組成要素,整體上還是以服務器端的研發方式、應用架構、平臺基建為中心的。
*?MERN Stack:?https://www.mongodb.com/mern-stack
在「現代 Web 開發」趨勢下,在國外被稱作「JAMstack」的技術棧,變得越來越清晰和主流。它還有另一種命名建議是圖中第二行的「SHAMstack」。
這兩個縮寫的首字母組合雖然不同,但就像圖中的推導結果,本質是一樣的,都是由 4 個要素組成。
* JAMstack:?https://jamstack.org/
*?SHAMstack:?https://css-tricks.com/jamstack-more-like-shamstack/
* "Content Mesh":?https://www.gatsbyjs.com/blog/2018-10-04-journey-to-the-content-mesh/
再展開看這 4 個要素。
跟剛才說的 MERN 技術棧比較,Serverless 模式和基建取代了 M 代表的傳統后端基建;前端應用的構建產物 、 服務器端渲染、靜態網站生成、前端程序中的 BFF、等等,取代了 E 代表的服務器端 Web 框架和服務器端代碼。
這種技術棧的項目仍然是「全棧」的 Web 項目,但變成以客戶端研發方式、客戶端應用架構為中心,看上去很像是純靜態的、純前端的項目。
*?ooooops I guess we’re* full-stack developers now:?https://css-tricks.com/ooooops-i-guess-were-full-stack-developers-now/
還有人歸納了這種叫「STAR」的技術棧,本質上是圖中右邊這4個要素組成。
同一個現代 Web 項目,可以同時符合 JAMstack 和 STAR,JAMstack 強調的是前后端架構和開發范式,STAR 強調的是專業的編程方式。
特別要注意的是,STAR 其實體現了一個現代 Web 項目中的「前端工程化」,除了編譯環節,也需要 Runtime 環節和代碼編寫環節的支持,除了視圖層,也需要完整的應用架構和 Model 層的支持。
*?STAR Apps: A New Generation of Front-End Tooling for Development Workflow: https://css-tricks.com/star-apps-a-new-generation-of-front-end-tooling-for-development-workflows/
在「現代 Web 開發」趨勢下,國外技術社區里越來越多的提到 meta-framework,本質上是把 JAMstack 和 STAR 強調的部分加起來,用以客戶端為中心的、包含更上層抽象的、通用的 Web 框架的形式,整體系統的滿足這些需求,抽象、簡化這里面的各種模式。
在「現代 Web 開發」里,這種 meta-framework 是更合適的基礎抽象層,取代了以腳手架和構建工具為代表的更原始的基礎。
「S 型曲線」是一種事物「發展成長規律」的表示方法,這張 JS 框架的 S 型曲線圖,很好的表達了 meta-framework 在 JS 技術發展中的意義。
在圖中左側的早期階段,各種 UI 框架在行業和技術社區里不斷涌現,包含各種各樣的嘗試和破壞性創新,隨著發展,UI 框架不再是快速發展的前沿了,選擇也不再那么多,技術發展逐漸穩定和收斂成了基于 React 做上層的整合和框架建設,這種框架變成了新的發展前沿,也產生了大量選擇,同時作為更成熟的技術,以更快的速度被采納使用,之后又逐漸穩定和收斂,形成主流技術。
*?React Distros and The Deployment Age of JavaScript Frameworks: https://www.swyx.io/react-distros/
比較了「傳統 Web 開發」和「現代 Web 開發」中的不同現象,最后我們來歸納和定義一下這兩種范式的本質特征和組成要素。
從框架和 UI 的角度來看,「傳統 Web 開發」是以服務器端框架為中心來做全棧開發,客戶端是圍繞 HTML 和對象樹的編程范式。
「現代 Web 開發」是以新一代客戶端框架為中心來做全棧開發,客戶端同樣圍繞專業、通用的軟件開發語言和編程范式。
從架構的角度來看,「傳統 Web 開發」中存在兩套比較獨立和欠缺融合的程序,「前后端分離」改善了分工協作,但對于分離出來的「前端技術棧的 Web 項目」,在架構和開發范式上沒有真正實現「分離」,前端開發者需要跟服務器代碼打交道。
「現代 Web 開發」實現了在同一套程序里一體化開發,在開發、調試、運行、部署等環節都能做到「無服務器化」,讓前端技術棧的開發者更容易成為真正的「產品開發者」。
從抽象的角度來看,「傳統 Web 開發」中前端部分的基礎抽象是原始、初級的,比如腳手架、樣板代碼、基本的 Webpack 包裝和 CLI 工具、React 和 JS 生態中的海量選擇,很有「前端特色」,跟成熟的軟件開發有顯著差距。圖中的 DX 是指開發者自身的效率,UX 是指開發出的應用的能力和質量,傳統范式中的抽象太原始,導致 DX 和 UX 沒法同時提升,提升其中一個,就會損害另一個。
「現代 Web 開發」中有了一體化的、更成熟的、框架級別的基礎抽象,能同時追求和確保 DX 和 UX 的最大化。
*?What Is DX? (Developer Experience): https://medium.com/swlh/what-is-dx-developer-experience-401a0e44a9d9
*?What is developer experience?: https://www.tiny.cloud/blog/developer-experience/
從部署的角度來看,「傳統 Web 開發」的項目,要么純靜態托管,要么作為服務器端應用來部署和運維。不同部署方式,會導致整個工程都不同,需要不同的腳手架模板。
「現代 Web 開發」的項目有更多樣強大的運行和部署方式,但在 meta-framework 的支持下,項目模板的變化變少了,申請有機會完全融合、收斂成一個模板,成為「Universal App」。
*?"SPR":?https://vercel.com/blog/serverless-pre-rendering
*?"SSG":?https://www.freecodecamp.org/news/static-site-generation-with-nextjs/
* "ISG": https://www.smashingmagazine.com/2021/04/incremental-static-regeneration-nextjs/
*?"Micro Frontend": https://micro-frontends.org/
最后,從平臺角度來看,「傳統 Web 開發」基于后端的云基礎設施,由于代碼層面缺乏抽象,客戶端代碼的復雜性容易停留在很基礎的程度,有些時候會干脆直接改成 JSON 之類的配置。
「現代 Web 開發」基于新一代 Serverless 平臺。在 Serverless 的支持下,能實現以前難以實用化的云研發。由于代碼層面有統一的、上層的抽象,在開發中能引入更多低代碼模式。
針對這些問題和趨勢背景,字節在起步比較晚的 Web Infra 建設中,主動發展和建設了整套「現代 Web 技術棧」的研發體系,建立了「MWA」 —— 也就是「現代 Web 應用」—— 這種工程標準,形成了新一代「研發引擎」,支持了大量、多樣的真實業務項目。
之前第一部分里我們講了前端的「傳統技術棧」是圖上這樣子。
可以看到「現代 Web 技術棧」的這張圖,發生了非常大的變化,所以說這種轉變是一場「范式轉移」。
有幾個需要注意的變化是:
圖中綠色代表的平臺化基建,占比超過藍色代表的純代碼層面的基建。
沒有割裂,中間的研發平臺建立在「前端云」之上,工程框架基于研發平臺支持研發全鏈路,右邊的工程方案跨越上中下三層,是指可以通過工程方案,定制左邊這三層的需求。
這套技術棧在字節內部,是一套被稱作「Goofy」的研發體系,可以看到圖中有多個名字里 Goofy 開頭的產品,既可以整體使用獲得更多高級能力,也可以單獨使用,其中最早推出、應用最廣泛的,就是最下層的「前端部署平臺」,提供了 Serverless 的「前端云」模式,明天下午有一場分享是專門介紹這個部分的。
這次分享以圖中藍色部分代表的代碼層為主,這個部分的解決方案,形成了一套被稱作 Jupiter 的研發框架。
先通過視頻演示直觀的看一下。
屏幕上的這個應用,是 Goofy 前端研發工作臺的 SaaS 版。
先在團隊里創建一個工程項目。
默認會推薦用這套基于 Jupiter 框架的、標準化的工程方案體系,這里選擇了 符合前面提到的 MWA 工程標準的「MWA 工程方案」。
后面會介紹這個工程方案非常強大,能力很多,但創建過程中需要做的配置很少,這里我們什么都不需要改就可以繼續創建。
可以看到除了初始化代碼,也會一站式的完成各種底層平臺的初始化和配置。
剛創建出來的項目,已經是一個完備的工程,可以馬上執行一次上線發布。
可以看到發布成功了,點擊自動生成的域名,可以看到 Jupiter 框架的初始運行效果。
接下來看看開發環節。
在一個開發任務里,進入「代碼」面板,可以看到項目代碼的可視化展示和低碼編輯界面,這個界面顯示之前,會先初始化任務專用的 Web IDE 研發環境。
接下來直接進入 Web IDE。
可以看到一個 MWA 工程的代碼,是極簡、輕量的,只要在 src 下默認導出一個 React 組件,就能得到完善的 web 應用,項目默認是零配置的,看不到 webpack.config.js 之類的工程配置和樣板代碼。
接下來直接執行 dev esm 命令,可以看到瞬間就啟動了調試命令,幾乎沒有編譯耗時,就生成了開發環境的訪問地址。
接下來快速做一段前后端一體化的開發,可以看到我們在 api 目錄下準備了一個 BFF 函數文件。
可以在 web app 代碼中直接導入這個函數文件,像普通函數一樣調用。
可以看到輸入代碼的過程中,我完全不關心代碼風格問題,保存的時候,會自動生成規范的、符合最佳實踐的代碼。
這里我寫錯了 API,會自動提示錯誤。
這個 useLoader 是 Jupiter 框架的運行時 API,能支持一體化的 SSR。
查看剛才打開的調試頁面,可以看到修改的效果,這段文本實際上是通過 BFF API 獲取到的,這個視頻忘記錄相關的演示。
接下來回到工作臺的低碼編輯界面,可以看到 MWA 工程的配置狀態。
把服務器端渲染功能開啟。
回到 Web IDE,可以看到 package.json 里已經自動增加了相關的配置。
打開調試頁面,從 HTML 文件可以看到整個 UI,包括通過 BFF API 獲取到的內容,都已經在提前渲染好了。
可以看到這個 MWA 工程的代碼雖然極簡,但能力已經比很多代碼復雜的傳統前端工程都要更強。
剛才演示中的工程項目,在傳統前端開發里,是一個模糊、虛的概念,不止包含代碼,還包括研發環境和各種平臺上對應的注冊和配置。
在字節前端研發體系里,「工程」被實體化了,在研發平臺上,「工程」是「看得見摸得著」的對象,可以創建和管理,在里面做所有研發相關工作,每個工程都包含全鏈路每個環節,和一體化的工作流。
「工程」可以通過「工程方案」來一鍵創建,「工程方案」相當于「工程」的「模板」,取代了傳統前端開發中的腳手架,而「工程方案」都有一個「工程類型」,這種類型相當于「模板的標準」。
建立和維護工程標準,推進圍繞這些標準的基建和技術生態,是 Web Infra 部門的工作重點之一。
目前的建設已經基本完成了「工程類型」的收斂,形成了少量的、數量固定的「默認工程類型」,能支持體系中各平臺的更多功能。Jupiter 框架實現了這些「工程類型」在代碼層面的要求。
每種「工程類型」,除了直接提供「默認工程方案」,也支持開發者創建屬于這種類型的、自己的「業務工程方案」,能跟標準保持兼容和同步。
以 MWA 為例,這是默認工程類型中唯一的 Web 項目類型,滿足所有 Web 項目的開發需求,傳統前端開發中的各種 Web 項目的模板,都被收斂成同一個「模板」。
MWA 之所以能承擔唯一 Web 項目類型的責任,是因為有 MWA 框架的支持,MWA 框架不但是前面說過的 meta-framework,而且在 JAMstack 技術棧的基礎上繼續發展,把微前端、傳統 Web 開發范式中的 Node.js Web 框架、等等,抽象融合到了一起。
這種融合不但不是簡單的疊加,導致工程變得大而全、更重。反而通過抽象變得更輕量更簡單,所以是 1+1<1,另一方面因為能一體化的考慮多種需求,能交付原本單個項目類型不好實現的功能,所以也是 1+1>2。
目前收斂得到的默認工程類型,有圖上這些,每種類型都覆蓋一個在研發方式和工程需求上有本質差別的場景領域,解決這個場景下的工程標準問題。
雖然工程類型要收斂,但業務工程方案的數量不做任何限制,比如圖中是字節GIP 部門做的叫作 GHome 的內部研發門戶,其中包含大量子平臺,這些子平臺之間有共通的業務邏輯和額外的工程要求。
在這種情況下,可以基于 MWA 標準,定制出 GHome 子平臺工程方案。
圖上右邊還列舉了其他一些業務工程方案。
進一步介紹下 Jupiter 和 MWA 框架。
一個最簡單的 MWA 項目,可以只包含圖上左邊這樣的兩個文件,這樣的MWA 已經有完善的工程能力,能馬上做產品級的部署。圖上右邊列出了一部分能力。
我們接下來對這個項目做些小調整,就能實現傳統上需要很多配置和樣板代碼,或是需要不同腳手架才能實現的效果。
這個項目當前是圖中左上角這樣的「單入口」模式,改成左下角這樣的「多入口」結構,就自動變成了 MPA,自動得到了服務器端路由和多個 HTML。
如果像右上角這樣,把 landing-page 的 App.tsx 入口組件,換成 pages 目錄,就可以啟用約定式路由功能,基于文件路徑自動得到客戶端路由。
如果像右下角這樣增加 index.ts 和 head.html,可以啟用高級的自定義能力,定制或掌控原本由框架自動處理的環節。
之前的演示里,我們通過低碼界面啟用了 SSR 功能,其實也可以在代碼里增加 Jupiter 配置文件,用代碼來控制配置。
一個 MWA 項目自帶產品級的 web server,稱作 Jupiter Server,項目自己就可以運行自己。
第一方 Serverless 平臺使用相同的 web server,但還能基于 MWA 標準來理解項目的代碼,自動生成更復雜的多進程的集群化的系統。比如 SSR 部分會獨立部署,業務開發者寫出來的 app 代碼,容易在 SSR 中出現異常報錯或內存泄露,這時前面的 web server 會讓應用自動降級,變成兜底的純 CSR 模式,保證用戶請求不會出錯不會超時。
要實現這種自動兜底,不但平臺和框架的協同,也需要 MWA 提供的一體化 SSR 開發方式,用同一套 app 代碼開發前后端業務邏輯,讓業務邏輯始終支持 CSR,否則兜底就無效了。
MWA 支持用一體化的方式,開發同一套 app 代碼,在圖上這三個環節中以不同模式運行,也支持根據業務需求,讓 app 代碼中的不同部分,在不同環節以不同模式運行。
這種收斂后的開發方式,符合業界的一個發展趨勢,就是讓代碼的運行盡可能「前置」,優先在編譯時運行,剩余的才在服務器端 SSR 環節運行,最后剩下的才在瀏覽器里運行。
比如可以像圖上左邊這樣,app 整體做 SSR 或 SSG,把局部留到 CSR。
也可以像圖上中間這樣,反過來,整體默認 CSR,允許對局部做 SSR 和 SSG。還可以進一步實現右邊這樣更復雜的效果。
要實現前面說的 1+1 < 1,除了合適的抽象,MWA 框架也通過插件和微生成器等方式,把核心功能拆分出來,可以按需啟用。可以像圖上這樣運行 new 命令,選擇啟用這兩個功能,微生成器會自動重構項目代碼,增加必要的依賴和配置。
也可以在工作臺的界面上,查看項目可選功能的開關狀態,做修改調整。
之前的演示里,我們提前開啟過 Unbundled 開發模式功能,可以用 dev esm 命令取代默認的 dev 命令,耗時可以從幾秒減少到1秒不到。
*?What is Unbundled Development: https://medium.com/habilelabs/snowpack-what-is-unbundled-development-8562205d0539
這么快的編譯速度,是因為背后已經完全沒有 webpack,也不做任何打包,只用 esbuild 編譯 ESM 模塊,用自研的類似 Snowpack 和 Vite 的方案運行項目,也會自動從 Goofy PDN 平臺上加載預編譯的依賴包,進一步優化性能。
之前的演示里,還開啟了一體化 BFF 功能,可以在項目里寫「BFF 函數」文件,這種函數文件可以實現任意的 REST API,同時自動生成一體化風格的客戶端 SDK,通過 SDK 調用,就像調用普通文件里的函數。
用這種一體化 API 調用 BFF,不但更直觀、安全,也因為多了框架的抽象,框架可以自動做更多事情,比如在 SSR 中運行的時候,自動把 BFF 請求切換成內網方式。
Serverless 平臺也會自動對 BFF 做優化,做獨立部署,BFF 和 Web 之間不會互相干擾。
MWA 實現了圖上這種「三位一體」應用,圖上右邊三種開發方式之間,可以無縫切換。
比如可以從現代 Web 開發范式,切換成傳統的 Node.js Web 應用。也可以把 MWA 項目中的 BFF 拆分成獨立的純 API 服務項目。一個純 API 服務,也可以隨時切換成包含 web 的 MWA 項目。
這種能力有利于業務項目從傳統 Web 開發范式向現代 Web 開發范式的轉換。
前面說的這種「三位一體」,實際上是 MWA 最重要的「Universal App」功能的一部分。
「Universal JS」是指同一份 JS 代碼既支持在瀏覽器端運行,也支持在服務器端運行。而「Universal App」是進一步發展,讓同一份 app 代碼,可以用圖上中間這一排的方框代表的任意方式來運行,也支持同時部署多個不同運行方式的版本。
比如一個靜態網站切換成 SSR,或啟用 SPR 的時候,不需要調研技術選型和考量成本收益,幾乎什么都不用改。也可以隨時退回原來的方式。
比如為中后臺項目同時提供 SaaS 版、桌面安裝版和私有化部署版。
比如讓微前端子應用,既能在主應用里訪問,也能獨立訪問。
前面提到過 Node.js Web 框架只能提供「服務器端應用架構」,MWA 提供開箱即用的、符合現代 Web 開發范式、以客戶端應用為中心、前后端一體化的應用架構,類似圖上這樣。
可以用 createModel、useModel 這樣的 API,輕易實現 React 開發中欠缺的 Model 層和 Controller 層。可以利用 Redux 的 FP 編程優勢和生態紅利,同時不需要關心創建和配置 store,如何組織和組裝 reducer、action 等。
前面說過,在抽象不足的情況下,前端開發中一直存在 DX 和 UX 不可兼得的問題,比如如果降低了開發成本,產品功能上可能會不靈活,性能不夠好,反之把業務需求和性能做到位,開發體驗就會很差。
MWA 框架在盡可能多的環節,實現最大化的抽象,類似圖上這樣,幫助前端開發者脫離「軟件開發的石器時代」,獲得更成熟的軟件開發范式。
Jupiter 和 MWA 框架在字節內部,經歷了半年的內測,和一年的正式使用,在內部已經進入推廣普及階段。
這個項目也很快會開源出來,幫助行業和社區里的更多項目,落地「現代 Web 開發」范式。
這個開源項目叫作 Modern.js,目前我們已經創建了 Github 項目,上線了開源內測主頁,和一個現代 Web 開發者問卷調查,希望大家幫忙填寫和轉發下這個問卷,調查報告之后會公開出來。
(由于 modernjs.dev 這個域名還沒備案,沒辦法部署在我們自己的 Serverless 平臺上,目前是用 Github Pages 部署,似乎有被墻的問題,正在解決)
*?https://modernjs.dev/
*?https://github.com/modern-js-dev/modern.js
*?https://webinfra.org/
?
?點擊上方卡片關注我、加個星標?
?
學習源碼整體架構系列、年度總結、JS基礎系列