SEE Conf: Umi 4 设计思路文字稿
大家好,我是若川。持續(xù)組織了5個(gè)月源碼共讀活動(dòng),感興趣的可以點(diǎn)此加我微信 ruochuan12?參與,每周大家一起學(xué)習(xí)200行左右的源碼,共同進(jìn)步。同時(shí)極力推薦訂閱我寫(xiě)的《學(xué)習(xí)源碼整體架構(gòu)系列》?包含20余篇源碼文章。
復(fù)制此鏈接 https://www.yuque.com/seeconf/2022/keynote?或者點(diǎn)擊文末閱讀原文可查看下載SEE Conf PPT和錄播。
編者按:本文為 2022.1.8 在 SEE Conf 分享的文字稿,介紹了 Umi 4 的一些設(shè)計(jì)思路,時(shí)間原因,只聊 4 個(gè),包含編譯時(shí)框架、依賴(lài)預(yù)打包、默認(rèn)快、約束與開(kāi)放。這幾天 colors?和 faker.js 鬧得前端社區(qū)沸沸揚(yáng)揚(yáng),但 Umi 卻能獨(dú)善其身,希望其中「依賴(lài)預(yù)編譯」的部分能給大家一些啟發(fā)。
背景
大家好,我是來(lái)自螞蟻集團(tuán)的云謙。
Umi 開(kāi)發(fā)了 3 個(gè)版本,并且即將發(fā)布第四個(gè)大版本,這中間踩了很多坑,也有不少有意思的設(shè)計(jì)思路和思考,今天會(huì)找一些典型的來(lái)和大家展開(kāi)聊下。
Umi 是什么
如左圖所示,在 pages 下新建一個(gè)文件,里面導(dǎo)出組件,經(jīng)過(guò) Umi 做一層魔法轉(zhuǎn)換,即可產(chǎn)出右圖的頁(yè)面。之所以能做到這樣,是因?yàn)?Umi 在背后構(gòu)建、路由、運(yùn)行態(tài)等等的事情,把開(kāi)發(fā)者的上手門(mén)檻降低最低。
Umi 是螞蟻集團(tuán)出品的前端開(kāi)源框架,基于 React。我們?cè)趦?nèi)部服務(wù)了 10000+ 項(xiàng)目,在去年(2021)中文社區(qū)的某個(gè)調(diào)研報(bào)告中,也有 25% 以上的受訪(fǎng)者采用 Umi 進(jìn)行項(xiàng)目開(kāi)發(fā)。Umi 4 目前研發(fā)中,其中包含很多新特性。
這是 Umi 到螞蟻內(nèi)部開(kāi)發(fā)者的鏈路圖。Umi 和最佳實(shí)踐開(kāi)源開(kāi)發(fā),直接服務(wù)社區(qū)。但是這在內(nèi)部使用還遠(yuǎn)遠(yuǎn)不夠,所以加上內(nèi)部業(yè)務(wù)和平臺(tái)的處理后,再形成更高層的框架 Bigfish,用于服務(wù)內(nèi)部開(kāi)發(fā)者。
Umi 從 1 到 4,遇到很多問(wèn)題,趟過(guò)很多坑,也總結(jié)了很多經(jīng)驗(yàn)。圖中這些,部分是設(shè)計(jì)上的思路,部分是遇到坑后的方案總結(jié),包含著螞蟻?lái)?xiàng)目和開(kāi)發(fā)者的🩸和💧。時(shí)間原因,今天從其中挑了 4 個(gè)和大家展開(kāi)聊聊。
編譯時(shí)框架
不知大家有沒(méi)有發(fā)現(xiàn),相比 10 年前前端編碼有了很多變化,以下這些是和編譯時(shí)相關(guān)的。
1、代碼寫(xiě)少了。少了很多腳手架代碼,比如數(shù)據(jù)流、國(guó)際化、模塊加載、路由等,從而讓開(kāi)發(fā)者有更多精力專(zhuān)注在業(yè)務(wù)視圖和邏輯上
2、報(bào)錯(cuò)提前了。比如模塊不存在,使用了不存在的變量,之前是運(yùn)行時(shí)才能發(fā)現(xiàn),現(xiàn)在報(bào)錯(cuò)提前了,在命令行里就能看到。除了 DX 的提升,額外的好處的此類(lèi)問(wèn)題不會(huì)再被帶到線(xiàn)上
3、產(chǎn)物變小了。之前用一個(gè) button 要引入整個(gè) antd,用一個(gè) isEqual 要引入整個(gè) lodash,現(xiàn)在通過(guò) tree-shaking 或 babel-plugin-import 或 TailwindCSS 的 JIT 引擎,能準(zhǔn)確知道你用了啥,然后做按需打包,讓產(chǎn)物變小
4、功能配置化了。現(xiàn)在很多標(biāo)準(zhǔn)化的功能都配置化了,比如想要兼容 ie11,做個(gè)配置,框架就會(huì)在背后加補(bǔ)丁,轉(zhuǎn) es 5,比如想要用 external 自動(dòng)提速,想要高清方案,想要埋點(diǎn),想要用 esbuild 壓縮,都是一個(gè)配置的事情
5、配置約定化了。有些場(chǎng)景配置化還是繁瑣了,比如路由、數(shù)據(jù)流 model、國(guó)際化語(yǔ)言文件,可以通過(guò)約定的方式,就沒(méi)必要做配置了
為啥會(huì)有這些變化?哪有什么歲月靜好,是框架在背后默默做了很多事。
那么編譯時(shí)框架和非編譯時(shí)框架的區(qū)別是啥?非編譯時(shí)比如非常流行的 create-react-app,把源碼簡(jiǎn)單直接地交給 webpack 就完成使命;編譯時(shí)框架則會(huì)自己加很多戲,比如拿到源碼后做 ast 分析,拿到依賴(lài)圖譜,做檢查,生成臨時(shí)文件,等等,最后把編譯后的源碼交給 webpack,這中間的很多事,本來(lái)是需要開(kāi)發(fā)者手動(dòng)處理或編碼的。
社區(qū)有很多編譯時(shí)的嘗試。比如 Angular 的 AOT 和 JIT,可以簡(jiǎn)單理解 AOT 為編譯時(shí),JIT 為運(yùn)行時(shí),AOT 可以讓產(chǎn)物更小,同時(shí)運(yùn)行更快;比如 facebook 之前出的 prepack,也是編譯時(shí)優(yōu)化的嘗試,在保證結(jié)果一致的前提下,改變?cè)创a,讓性能更快;還有最近的 React Forget 更是編譯時(shí)優(yōu)化的典型。
Umi 做了很多編譯時(shí)的事,如果你用過(guò) umi,應(yīng)該了解 src 下有個(gè) .umi 臨時(shí)目錄,這里存放的文件本是需要開(kāi)發(fā)者自己寫(xiě)的,現(xiàn)在由框架或插件在編譯時(shí)自動(dòng)生成。比如在 pages 目錄下新建文件即是路由,新建 access.ts 文件即是權(quán)限,在 locales 目錄下新建文件即是國(guó)際化語(yǔ)言,等等。
這部分的 One More Thing 是 Low Import 開(kāi)發(fā)模式,他會(huì)隨著 Umi 4 發(fā)布,但默認(rèn)不開(kāi)啟。左圖是開(kāi)啟前,右圖是開(kāi)啟后,區(qū)別是大部分 import 語(yǔ)句不用寫(xiě),交給框架自動(dòng)補(bǔ)全。這個(gè)方案很有爭(zhēng)議,喜歡的很喜歡,不喜歡的很不喜歡,但不管如何,這也是編譯時(shí)領(lǐng)域的一次嘗試。
依賴(lài)預(yù)打包
不知大家是否有此經(jīng)歷。睡一覺(jué)醒來(lái),很多事發(fā)生了變化。比如 dev 或 build 跑不起來(lái),啥都沒(méi)干迭代發(fā)布后線(xiàn)上白屏還背了個(gè)故障,npm i 時(shí)出現(xiàn)某人的求職廣告,依賴(lài)庫(kù)被黑客掛馬,等等。
然后你打開(kāi) package.json 一看,只有 10 個(gè)依賴(lài)呀,我還寫(xiě)死了版本,這是為啥?因?yàn)槟愫雎粤顺汕先f(wàn)的間接依賴(lài),而這些依賴(lài)總有一個(gè)會(huì)發(fā)生點(diǎn)意外,比如某個(gè)依賴(lài)的不兼容更新,就會(huì)導(dǎo)致你項(xiàng)目掛掉。
問(wèn)題的根源是 semver。理想的 semver 是 breaking.feat.bugfix,現(xiàn)實(shí)的 semver 是 breaking.breaking.breaking。并發(fā) breaking 的 bugfix 版本是社區(qū)的常規(guī)操作。
是問(wèn)題就有解,社區(qū)已有不少。臨時(shí)的比如 cnpm 提供的 bug-versions,npm 提供的 resolutions,侵入式改代碼的 patch-package 等;長(zhǎng)期的比如 npm、yarn 和 pnpm 具備的 lock 能力,tnpm/cnpm 目前暫不支持,但可以用 yarn mode。
還有個(gè)思路是「中間商鎖依賴(lài),定期更新,并對(duì)此負(fù)責(zé)」。框架是開(kāi)發(fā)者的倒數(shù)第二道防線(xiàn),自然而然就應(yīng)該是這個(gè)中間商。
這個(gè)思路在 Umi 里的實(shí)現(xiàn)是依賴(lài)預(yù)打包。打包前,umi 通過(guò) dependency 依賴(lài) webpack、babel 等,這時(shí)如果 babel 出現(xiàn) bug,會(huì)導(dǎo)致 umi 掛,然后用戶(hù)項(xiàng)目也掛,睡不好,😴;打包后,umi 通過(guò) devDependency 依賴(lài) webpack、babel 等,如果 babel 又出現(xiàn) bug,umi 會(huì)不會(huì)受影響,umi 用戶(hù)的項(xiàng)目也不會(huì)受影響,睡得香,😄。
通過(guò)預(yù)打包,Umi 把依賴(lài)的 node 數(shù)從 1309 降到 314,這帶來(lái)的不僅有安全和穩(wěn)定,還有安裝提速、node_modules 目錄瘦身、命令行啟動(dòng)提速、無(wú) peerDependency 警告等等。
簡(jiǎn)單介紹下如何預(yù)打包,分代碼和類(lèi)型定義兩部分,分別通過(guò) ncc 和 dts-packer 實(shí)現(xiàn)。比如 webpack,借助兩個(gè)工具,會(huì)分別在 compiled/webpack 目錄生成 index.js 和 index.d.ts,以實(shí)現(xiàn)預(yù)打包的目的。
這部分的 One More Thing 是 Father 的下個(gè)版本 V4,他是基于 Umi 的組件打包工具,在 V4 里,除了其他 nb 的特性外,還有個(gè)重要的點(diǎn)就是前面我們介紹的依賴(lài)預(yù)打包功能,大家可以期待下。
還有另一個(gè) Two More Thing 是 browser 側(cè)依賴(lài)的鎖。前面我們聊的其實(shí)只適用于 node 側(cè)依賴(lài),比如 webpack、babel 這些,那像 antd 這些 browser 依賴(lài)呢?他們不能被預(yù)打包。原因包括:1、尺寸問(wèn)題,browser 要考慮尺寸,預(yù)打包會(huì)讓 tree-shaking 失效 2、browser 庫(kù)直接影響線(xiàn)上,風(fēng)險(xiǎn)更高,人肉回歸成本高。有一個(gè)解法是「importmaps 鎖 + 灰度 + 定期人肉更新的中間依賴(lài)」,時(shí)間原因,具體不展開(kāi)。
默認(rèn)快
大家在日常工作中,應(yīng)該多少都經(jīng)歷過(guò)各種類(lèi)型的慢。比如 Lint 慢、依賴(lài)安裝慢、構(gòu)建慢到 OOM、CI 慢、本地啟動(dòng)慢、提交慢、node_modules 大、測(cè)試慢等,一些具體的數(shù)據(jù)比如 ant-design-pro 腳手架啟動(dòng)時(shí)間在 30s,改完代碼后熱更新時(shí)間在 3s,而螞蟻某中后臺(tái)較慢應(yīng)用的啟動(dòng)時(shí)間在 5 分鐘,熱更新時(shí)間半分鐘。改完代碼去趟廁所回來(lái),可能還沒(méi)好...
關(guān)于提速我們之前整理了三個(gè)法寶,緩存、延遲處理和 Native Code。緩存能提速是因?yàn)樽鲞^(guò)的事情不過(guò)第二遍,比如 webpack 5 的物理緩存,babel 緩存,預(yù)編譯依賴(lài)作為緩存等;延遲處理能提速是因?yàn)榘巡恢匾氖虑椴鸪鋈ズ?#xff0c;關(guān)鍵進(jìn)程就快了,比如各種按需和延遲編譯都屬于此類(lèi);Native Code 主要指 esbuild、swc 此類(lèi),能提速是利用語(yǔ)言特性進(jìn)行降維打擊,主要用在壓縮和編譯上,效果顯著。
這是我們整理個(gè)各個(gè)階段的提速方案,兩個(gè)維度,時(shí)間和方法。可以看到,1、利用緩存的方案很多 2、現(xiàn)在和未來(lái)的方案大量基于 Native Code 3、效果好的方案是多個(gè)方法結(jié)合使用,比如 esbuild 雖然單體快,但純用的效果卻不一定好。
Umi 在這部分的第一個(gè)解是 MFSU,基于 webpack 5 Module Federation 特性的提速方案。1、基于 webpack,解決我們既要 webpack 的功能和生態(tài),又要 Vite 的速度的問(wèn)題 2、在螞蟻內(nèi)部已服務(wù) 1000+ 應(yīng)用 3、快是他的主要特點(diǎn),除了啟動(dòng)快、熱更快、頁(yè)面打開(kāi)也快,注意頁(yè)面打開(kāi)也快,這是 esm bundless 方案所不具備的 4、可上生產(chǎn),除了本地快,CI & CD 流程也要快。
這是 MFSU 兩個(gè)版本和 webpack 對(duì)比的效果。V2 在二次啟動(dòng)和熱更方面相比 webpack 都有大幅提升。V3 在 V2 的基礎(chǔ)上,對(duì)首次啟動(dòng)也做了改進(jìn),有一點(diǎn)在圖上沒(méi)體現(xiàn)的是,V3 在頁(yè)面打開(kāi)速度上也做了改進(jìn),不會(huì)有通常 esm bundless 的大量請(qǐng)求問(wèn)題。
介紹下 MFSU 的原理。項(xiàng)目源碼會(huì)走到 babel/swc 插件,插件會(huì)做兩件事,1、修改源碼,從 remote 獲取資源 2、收集依賴(lài)到依賴(lài)圖譜;然后依賴(lài)圖譜會(huì)通知 dep builder 做依賴(lài)的預(yù)編譯,這里可以選 esbuild 或 webpack,產(chǎn)出的格式都是 module federation;最后修改后的源碼會(huì)加載這份預(yù)編譯后的依賴(lài),形成 BI 環(huán)。
這是 MFSU 的時(shí)間線(xiàn)。到目前已經(jīng)迭代了 3 個(gè)大版本。V1 版本是最理想的版本,依賴(lài)預(yù)編譯走 cdn,讓首次啟動(dòng)也快,但覆蓋率有限,所以效果有限,并且維護(hù)成本高;被打擊后 V2 版本回歸現(xiàn)實(shí),依賴(lài)預(yù)編譯走本地,和 Vite 的模式類(lèi)似,覆蓋率 95%+,效果很好,但也留了些邊界場(chǎng)景;V3 是 V2 的優(yōu)化,解了目前遇到的所有問(wèn)題,不僅首次啟動(dòng)快,頁(yè)面打開(kāi)也快;V4 在路上,2022 年做,主要是關(guān)于協(xié)作的。
MFSU 這么好,怎么用呢?😄 為大家準(zhǔn)備了兩種方式。1、umi 4 默認(rèn)啟用了 mfsu,兩行命令即可嘗鮮 2、大家的項(xiàng)目可能不是基于 umi,也可以用,基于底層庫(kù),適用于任意 webpack 5 項(xiàng)目,我特意準(zhǔn)備了一個(gè)例子,帶上 antd 等庫(kù)之后,空緩存首次啟動(dòng)也是 1s 內(nèi)。
Umi 的第二個(gè)解是多構(gòu)建引擎。不止支持 webpack,也支持 vite,還有試驗(yàn)性的 esbuild,照顧朋友們的不同偏好。Umi 通過(guò)配置在不同模式之間切換,并盡可能保證功能的一致性。大家是否有感受到,現(xiàn)在社區(qū)有一種趨勢(shì)是,dev 用 vite 提速,build 用 webpack 提速。
Umi 4 對(duì)于默認(rèn)快還有更多解。源碼編譯用 swc、依賴(lài)編譯用前面介紹的 MFSU、然后把 esbuild 用到 js 壓縮、css 壓縮、依賴(lài)編譯、jest 編譯、以及配置文件和 MOCK 文件的編譯上。此外,還有右下角的 fast refresh、lazy import、remote cache、code splitting 策略等。總之,要默認(rèn)快,是個(gè)細(xì)節(jié)多又體系化的活。
這部分的 One More Thing 是 ESMi,時(shí)差原因,大家在 D2 上可能已經(jīng)聽(tīng)過(guò)他的介紹,這里再介紹下他的原理。ESMi 是我們的 ESM Bundless 方案,面向未來(lái)的方案,不僅適用于本地命令,還適用于搭建系統(tǒng)。他包含 Server 和 Client 兩部分功能。Client 會(huì)把 depinfo 傳給 Server 并要求 ImportMaps,Server 需要分析依賴(lài)并做云端構(gòu)建,繼而返回 ImportMaps,Client 拿到 ImportMaps 后就可以在瀏覽器里渲染了。
約束與開(kāi)放
社區(qū)同學(xué)經(jīng)常一方面抱怨 Umi 太黑盒,一方面又抱怨這么多選擇,我應(yīng)該選哪個(gè);螞蟻內(nèi)部同學(xué)經(jīng)常抱怨內(nèi)置方案我不喜歡,能否換一個(gè)?
之所以有這個(gè)問(wèn)題,歸根結(jié)底還是場(chǎng)景不同。是個(gè)人還是團(tuán)隊(duì),是同一個(gè)團(tuán)隊(duì)還是不同團(tuán)隊(duì)。團(tuán)隊(duì)需要一致性。到達(dá)終點(diǎn)的路很多,但這些路在一個(gè)團(tuán)隊(duì)內(nèi)卻不應(yīng)該放開(kāi)讓大家選。
所以「社區(qū)要開(kāi)放,團(tuán)隊(duì)要約束」。然后約束要有度。約束越多,越一致。但又不能把路堵死,堵久了容易固步自封。
螞蟻內(nèi)部有 50 條「強(qiáng)約束」規(guī)則集,目的除了方案和編碼一致性,還可以提升安全性、規(guī)避常見(jiàn)錯(cuò)誤、提升可維護(hù)性等。為了讓這些規(guī)則不像 eslint 可以在本地輕易跳過(guò),采取了服務(wù)器下發(fā)的方式。
舉一些規(guī)則的例子。比如不能使用除 dva、use model 之外的數(shù)據(jù)流方案,不能無(wú)理由使用 eval、new Function、不能混用 cjs 和 esm 模塊規(guī)范,組件代碼不能超過(guò) 600 行,不能使用 resolution 鎖定一方庫(kù)和二方庫(kù)版本。
還有個(gè)特殊規(guī)則是同一 major version 下「只能使用框架最新版」,這使得我們?nèi)种挥幸粋€(gè)框架版本,對(duì)于框架升級(jí)和應(yīng)用治理都有很大幫助,并且副作用很小,RIO 很高。
再來(lái)看社區(qū)的方案。面向社區(qū)的 Umi 框架會(huì)更傾向「原子功能 + 組裝」的使用方式,盡量白盒,相比「集中式」用戶(hù)會(huì)有更多控制權(quán)。
舉個(gè)例子,比如提供功能 A,集中式是配置 A: {} 開(kāi)啟,組裝式是分別提供 base 和 A,用戶(hù)通過(guò) A + base 的方式開(kāi)啟。再具體點(diǎn)比如 jest 配置,Umi 4 的組裝式是在 jest.config.ts 里配置 export default configUmiAlias(createConfig(opts)),把 createConfig 和 configUmiAlias 做個(gè)組裝。
One More Thing,螞蟻內(nèi)部強(qiáng)約束的規(guī)則集會(huì)在 SEE Conf 分享中公開(kāi),可能會(huì)大家會(huì)有些借鑒意義。
Umi 4
最后,Umi 4 將于近期發(fā)布,在此和大家分享下 Umi 4 的特點(diǎn)。
1、體系化有體感的默認(rèn)快
2、依賴(lài)預(yù)打包讓你的項(xiàng)目安全又穩(wěn)定
3、雙構(gòu)建引擎給用戶(hù)更多選擇
4、技術(shù)棧最新把底層依賴(lài)升到最新,尤其是 react router 6,我太喜歡這個(gè)版本了
5、最佳實(shí)踐 V2,包含請(qǐng)求、數(shù)據(jù)流和國(guó)際化的相關(guān)更新
6、Umi Pro 是內(nèi)部 Bigfish 框架的對(duì)外版本,解我們自己的問(wèn)題,同時(shí)也給社區(qū)另一個(gè)集中化框架的選擇。
·················?若川簡(jiǎn)介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開(kāi)發(fā)“工程師”。寫(xiě)有《學(xué)習(xí)源碼整體架構(gòu)系列》20余篇,在知乎、掘金收獲超百萬(wàn)閱讀。
從2014年起,每年都會(huì)寫(xiě)一篇年度總結(jié),已經(jīng)寫(xiě)了7篇,點(diǎn)擊查看年度總結(jié)。
同時(shí),最近組織了源碼共讀活動(dòng),幫助3000+前端人學(xué)會(huì)看源碼。公眾號(hào)愿景:幫助5年內(nèi)前端人走向前列。
識(shí)別上方二維碼加我微信、拉你進(jìn)源碼共讀群
今日話(huà)題
略。分享、收藏、點(diǎn)贊、在看我的文章就是對(duì)我最大的支持~
總結(jié)
以上是生活随笔為你收集整理的SEE Conf: Umi 4 设计思路文字稿的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 易语言-VB keypress事件中键盘
- 下一篇: PS教程第二十二课:羽化选区