<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【總結(jié)】1319- 前端歷史項(xiàng)目的 Vite 遷移實(shí)踐總結(jié)

          共 6124字,需瀏覽 13分鐘

           ·

          2022-05-16 04:22

          當(dāng)前,前端社區(qū)用 Vite 替代 Webpack 的呼聲正日趨高漲。但對于長期維護(hù)的業(yè)務(wù)項(xiàng)目,很多同學(xué)可能仍然對上車存有疑慮——Vite 真的足夠支撐非玩具級的項(xiàng)目嗎?為此本文會分享一個(gè)實(shí)際案例,介紹我們是如何(比較輕松地)在公司核心業(yè)務(wù)中落地 Vite 的。

          稿定 Web 端業(yè)務(wù)中的平面編輯器已經(jīng)有五年以上的歷史。作為一個(gè)歷經(jīng)多人主導(dǎo)維護(hù)的前端項(xiàng)目,它有這么一些復(fù)雜度:

          • 編輯器使用基于 Yarn workspace 和 Lerna 的宏倉庫來管理源碼,其中有近 20 個(gè) package,初始化時(shí)會加載超過 400 個(gè)模塊,并有 2GB 以上的 node_modules 依賴。
          • 編輯器模塊最早使用 Vue 0.8 和 AMD 模塊語法 ,歷經(jīng) Vue 1.x 和 2.x 時(shí)代維護(hù)至今。Webpack 也是從無到有,再從 1.x 一路升級到了現(xiàn)在的 4.x 版本。
          • 編輯器內(nèi)的部分高級渲染功能,用到了 Worker 和 WASM 的能力。
          • 編輯器整體作為單個(gè) NPM 包發(fā)布到公司私有倉庫上供業(yè)務(wù)接入,有獨(dú)立的打包和發(fā)版流程。

          編輯器在 2016 年的第一次提交,基于 Vue 0.8 和 AMD 語法

          我們不敢說這就是所謂的「大型企業(yè)級」項(xiàng)目,但這至少肯定不是個(gè)玩具項(xiàng)目。然而超乎預(yù)期的是,「Vite 的遷移成本甚至比升級 Webpack 和 Babel 大版本還要低」。只花了一個(gè)下午的時(shí)間,基于 Vite 的編輯器最小可用 MVP 就跑起來了。下面分幾點(diǎn)介紹相關(guān)的實(shí)踐經(jīng)驗(yàn):

          • 如何規(guī)劃基本的遷移思路,以及一些基礎(chǔ)的知識儲備。
          • 如何通過編寫插件來解決一些 Webpack loader 的問題。
          • 如何遷移常見的 Webpack 配置。
          • 如何處理上游依賴問題。

          知識背景與思路

          我們知道,以 Webpack 為代表的主流前端 bundler 之所以慢,根源在于它們冷啟動時(shí)必須遞歸打包出整個(gè)項(xiàng)目的依賴樹,并受限于 JavaScript 的天性(解釋執(zhí)行與單線程模型)而存在吞吐量上的瓶頸。為了解決這兩個(gè)痛點(diǎn),Vite 另起爐灶切換了路線:

          • 對于項(xiàng)目中的業(yè)務(wù)模塊,Vite 利用現(xiàn)代瀏覽器內(nèi)置的 ES Module 支持,由瀏覽器直接向 dev server 逐個(gè)請求加載這些模塊——因此你往往可以看到本地環(huán)境下大量的 HTTP 請求刷屏,這也是 Vite 最鮮明的特征。
          • 對于項(xiàng)目中的 node_modules 依賴,Vite 借助 esbuild 這類由原生語言開發(fā)的高性能 bundler,將這些庫中非 ESM 標(biāo)準(zhǔn)(CommonJS 或 UMD)的模塊整體打包為 ESM,即所謂的 Dependency Pre-Bundling。這個(gè)過程的打包結(jié)果具備緩存,并且冷啟動重建緩存的效率也極高。

          Vite 的這個(gè)設(shè)計(jì)與 webpack-dev-server 之間的區(qū)別,在其文檔中也已經(jīng)展示得很清楚,一圖勝千言:

          Webpack 式的經(jīng)典 bundler 示意圖
          Vite 式的 No-bundler 示意圖

          基于這個(gè)差異我們就可以知道,要讓 Vite 支持原有的 Webpack 項(xiàng)目,需要保證的無非兩件事:

          • 確保業(yè)務(wù)模塊源碼均符合 ESM 規(guī)范。
          • 確保依賴均可正確被 esbuild 處理。

          當(dāng)然這只是最簡單的思維模型。實(shí)際的前端項(xiàng)目中往往還會引入一些奇怪的東西,比如 CSS、JSON、Worker、WASM、HTML 模板……雖然 Vite 對這些需求已經(jīng)內(nèi)建了良好的支持,但確實(shí)誰也不敢保證能一鍵開箱即用——這并不是 Vite 或 Webpack 的問題,而是移植代碼構(gòu)建環(huán)境時(shí)的共通難點(diǎn)。對這類任務(wù),「最難的地方總在于從零到一的「點(diǎn)亮」」。因此這里對此的建議是這樣的:「充分熟悉從項(xiàng)目入口到各組件渲染完成之間所經(jīng)歷的代碼(子)樹,確保這一個(gè)最小的子集能夠在新環(huán)境下正常運(yùn)作」。其他代碼都可以大刀闊斧地暫時(shí)移除掉。

          對于架構(gòu)設(shè)計(jì)合理的軟件項(xiàng)目,一般都可以容易地實(shí)現(xiàn)模塊的精簡和擴(kuò)展。例如在這個(gè)編輯器中,我們就支持了可配置并按需加載的元素類型。對于現(xiàn)有的 20 余種業(yè)務(wù)元素,它們對應(yīng)的模塊都已經(jīng)支持了按需加載,只會在遇到相應(yīng)數(shù)據(jù)時(shí) import() 導(dǎo)入。因此在遷移時(shí),只需保留若干基礎(chǔ)元素模塊實(shí)現(xiàn)用于測試即可。類似地,在業(yè)務(wù)項(xiàng)目中也可以通過精簡路由配置等方式,定制出一個(gè)用于走通主流程的最小可用版本。

          自定義插件實(shí)現(xiàn)

          上述的代碼精簡過程,其實(shí)不外乎是建立一個(gè)干凈的 example 頁面來導(dǎo)入項(xiàng)目,注釋掉部分代碼然后反復(fù)執(zhí)行 vite 命令測試,這里不再贅述。對于 Vite 遷移,很多同學(xué)最擔(dān)憂的可能還是 Webpack 插件兼容性方面的問題。我們恰好也遇到了類似的問題,這里簡單分享一下。

          在前面 2016 年的編輯器上古版本代碼截圖中有一個(gè)細(xì)節(jié),那就是其中引入了 editor.html 作為組件的 HTML 模板。這個(gè)行為歷經(jīng)多年一直保留到了現(xiàn)在——也就是說這里沒有使用 SFC 單文件組件,而是對 text-element.js 等組件配套放一個(gè) text-element.html 作為其模板,像這樣:

          //?導(dǎo)入?HTML?源碼?--code秘密花園
          import?TextElementTpl?from?'./text-element.html'

          //?Vue?2.0?的經(jīng)典配置??--code秘密花園
          export?default?{
          ??template:?TextElementTpl,
          ??methods:?{
          ????//?...
          ??},
          ??created()?{
          ????//?...
          ??}
          }

          在 Webpack 配置中,我們一般會用 HTML loader 來支持它,那么 Vite 呢?這類需求似乎并沒有內(nèi)置,而現(xiàn)在社區(qū)的 vite-plugin-html 是為 EJS 模板設(shè)計(jì)的,star 數(shù)量好像也不多……但真的就要等社區(qū)做現(xiàn)成的給你嗎?

          其實(shí),Vite 的插件系統(tǒng)是直接依賴 rollup 的。對于這個(gè)需求,只要這樣在 vite.config.js 里寫個(gè)幾行的插件就夠了:

          //?使用?rollup?附帶的?plugin?utils??--ConardLi
          const?{?createFilter,?dataToEsm?}?=?require('@rollup/pluginutils');

          function?createMyHTMLPlugin()?{
          ??//?建立一個(gè)用于篩選模塊的?filter
          ??const?filter?=?createFilter(['**/*.html']);
          ??return?{
          ????name:?'vite-plugin-my-html',?//?起個(gè)名字??--ConardLi
          ????//?根據(jù)?id?來篩選模塊,并在遇到匹配的模塊時(shí)變換其?source
          ????transform(source,?id)?{
          ??????if?(!filter(id))?return;
          ??????//?這樣?HTML?字符串就能被?export?default?給其他?JS?模塊了
          ??????return?dataToEsm(source);
          ????},
          ??};
          }

          //?這樣就可以按照?Vite?的標(biāo)準(zhǔn)?API?來使用插件了
          module.exports?=?{
          ??plugins:?[createMyHTMLPlugin()],
          }

          這個(gè) createMyHTMLPlugin 不就是個(gè)非常簡單的函數(shù)而已嗎?但它卻切實(shí)地解決了一個(gè)實(shí)際問題。個(gè)人認(rèn)為對用戶友好的構(gòu)建系統(tǒng)應(yīng)該做到在大多數(shù)時(shí)候能開箱即用,并能通過簡單的邏輯自行擴(kuò)展。在這一點(diǎn)上,可以說 Vite 還是做得相當(dāng)出色的。另外 Vite 相比 Snowpack 的一個(gè)主要區(qū)別,就是它的插件系統(tǒng)與 Rollup 有更深的集成,由此實(shí)現(xiàn)了在 dev 和 build 兩種模式下通用的插件 API。因此在業(yè)務(wù)中,也有機(jī)會自行「套殼」一些成熟的 Rollup 插件來實(shí)現(xiàn)需求。

          常見 Webpack 配置遷移

          在這次實(shí)踐中用到的 Vite 配置相當(dāng)少,值得一提的主要是這么幾條:

          • 通過 resolve.alias 配置,可以覆寫(或者說劫持)掉模塊路徑。注意最好盡量讓這個(gè)配置少一點(diǎn),濫用它容易降低代碼模塊結(jié)構(gòu)對工具鏈的友好性。
          • 通過 define 配置,可以支持 process.env.__DEV__ 這樣的環(huán)境變量注入。注意 Vite 會把字符串直接注入成產(chǎn)物代碼中的 raw expression,所以如果只想傳遞 true 這種簡單常量,要額外 JSON.stringify 包一層。
          • 通過 vite-plugin-vue2 可以支持 Vue 2.0 的 SFC。這里的理由在于雖然編輯器內(nèi)的主要組件沒有使用 SFC,但測試頁面的 demo 入口是個(gè) app.vue。通過這個(gè)插件,可以讓它們良好地共存。
          • Less 和 CSS 依賴了 Vite 的內(nèi)置支持,沒有引入額外的配置。當(dāng)然另一種變通方案是先執(zhí)行獨(dú)立打包 CSS 的命令,然后 import "./dist.css" 即可。
          • 通過 import Worker from "worker.js?worker" 的語法,可以支持 Web Worker。另外也可以進(jìn)一步將其配合 resolve.alias 配置,來繼續(xù)兼容 Webpack。
          • 對于 WASM,除了形如 import init from "./a.wasm" 的內(nèi)置支持以外,還有一種實(shí)踐是讓 WASM 的 JS 適配層支持傳入可配置的 WASM 路徑,這方面比較典型的例子可以參考 CanvasKit 等包。

          上游依賴問題處理

          基于上面介紹的這些實(shí)踐,應(yīng)當(dāng)已經(jīng)足夠解決 Vite 對各類業(yè)務(wù)模塊的加載問題了。但最后還有一個(gè)比較頭疼的地方:如果 node_modules 中的依賴不能被 esbuild 正確打包,又該怎么辦呢?

          在這次遷移中,這樣的問題我們有遇到兩處,各自的原因有所不同:

          • 圖片重采樣庫 Pica 依賴了一個(gè)簡易的 Web Worker 轉(zhuǎn)換庫,它會直接在模塊代碼頂層讀取 arguments 數(shù)據(jù),導(dǎo)致 esbuild 報(bào)錯(cuò)。
          • 字體解析庫 OpenType.js 為了同時(shí)兼容瀏覽器端和 Node,在 ESM 源碼中封裝了若干 require('fs') 的函數(shù)。這也會導(dǎo)致報(bào)錯(cuò)。

          對于這兩個(gè)問題,其實(shí)都有一種通用的 workaround 手法:「建立一個(gè) third_party 目錄,把存在問題的上游模塊拷貝一份進(jìn)去,在這里修復(fù)問題并調(diào)整模塊依賴即可」。如 Pica 庫內(nèi) require('./a.js') 的代碼,就可以復(fù)制到 third_party 目錄后,將模塊導(dǎo)入路徑改為 require('pica/src/a.js'),這樣并不需全量復(fù)制整個(gè)上游依賴。而對于這里遇到的兩個(gè) CommonJS 問題,具體的修復(fù)也都很容易,例如把對 arguments 的讀取放到 export default 的函數(shù)體內(nèi),并直接移除在瀏覽器環(huán)境下用不到的 Node 文件讀取邏輯等。這樣的 third_party 模式實(shí)際上倒也不算什么 hack,在很多語言的工程中有很廣泛的使用,但也有些地方值得注意:

          • 建議在改動位置添加 // FIXME 之類的注釋,方便接受者確認(rèn)修改之處。
          • 如果需要集成很大的上游依賴,那么不建議直接放到代碼庫里,可以使用 git submodule 或 CDN 等形式。
          • 理想情況下應(yīng)當(dāng)向上游反饋 patch,解決問題后移除相應(yīng)的本地版本。

          以上就是全部值得列出的問題了,最后放一張基于 Vite 啟動本地環(huán)境成功時(shí)的截圖:

          上圖的日志有個(gè)問題,即加載了兩個(gè)不同的 Vue 版本。這是因?yàn)?SFC 部分和依賴 HTML 模板的代碼誤用了不同的 Vue 依賴。這個(gè)問題后來通過 alias 配置將 vue 全部重寫到 vue/dist/vue 而解決了。

          由于編輯器 SDK 原本就使用 Babel 獨(dú)立發(fā)版,因此原有的 NPM 發(fā)布過程不受影響,Vite 整體的侵入性也并不高。至于最終效果上也沒有什么別的,就是油門踩到底加速了一下:

          • Webpack 40 秒以上的 dev server 冷啟動時(shí)間縮短到了 1.5 秒內(nèi),在建立 .vite 目錄緩存后,啟動 vite 命令的時(shí)間僅需約 300 毫秒。
          • 修改單個(gè)文件后 2 秒左右的增量編譯時(shí)間被完全優(yōu)化掉了,同時(shí)瀏覽器中加載頁面的效率并沒有明顯差異。

          這樣一來,這個(gè)歷史項(xiàng)目就重新獲得了即時(shí)反饋級別的開發(fā)體驗(yàn),同時(shí)也讓更高效的 CI 集成成為了可能。這里的想象空間還很大,我們很期待讓 Vite 在未來發(fā)揮出更大的作用。

          總結(jié)

          • Vite 做到了以低接入代價(jià)換取開發(fā)體驗(yàn)上的大幅提升,有望引領(lǐng)前端構(gòu)建工具領(lǐng)域的下一波 paradigm shift 浪潮。按 ROI 的話說,「其落地的潛在收益遠(yuǎn)大于成本」。
          • 實(shí)際業(yè)務(wù)中的代碼應(yīng)當(dāng)盡量貼合標(biāo)準(zhǔn),少使用需依賴工具鏈黑魔法的特性,以換取更好的后向兼容性。
          • 對于代碼移植,實(shí)踐中其實(shí)還有很多(未必上得了臺面的)奇技淫巧,比如正則替換、編寫 codemod 和為下游業(yè)務(wù)提供 deprecated API 檢測腳本等等——捫心自問,把抄來代碼里的 var 全部查找替換成 let 這種事你干過沒有?這些手段并沒有什么高下之分,能簡單方便地解決問題就好。
          • JavaScript 本身哪怕作為編譯后的產(chǎn)物,仍然是易讀、易修改,且易向上游 backport 反饋的。主流的編譯型語言都不容易做到這一點(diǎn)——類似于你把 DLL 里函數(shù)符號的機(jī)器碼或 Java class 文件里的字節(jié)碼改完,馬上就能照著 diff 直接去給上游庫提 PR。這是黑魔法的源頭,可能也是種前端的「道路自信」吧。

          實(shí)際上作為本文的作者,之前個(gè)人還嘗試過一些類似的代碼移植。這類工作就像是一個(gè)破解密室逃脫游戲的過程,非常有趣。個(gè)人感覺像這次的 Vite 遷移,在實(shí)踐手段上其實(shí)和之前的經(jīng)歷都是相當(dāng)共通的:

          • 將 1995 年世界上最早的 JS 引擎源碼編譯回 JavaScript
          • 將 Dart VM 從 Flutter 中抽離出來,單獨(dú)在 iOS 原生項(xiàng)目中使用
          • 為國產(chǎn)掌機(jī)搭建嵌入式 Linux 工具鏈,把 QuickJS 引擎移植上去

          所以最后,非常鼓勵(lì)大家多做興趣驅(qū)動的技術(shù)嘗試。沒準(zhǔn)未來的哪天,折騰它們的經(jīng)驗(yàn)就能幫助你找到抓手,賦能業(yè)務(wù),形成閉環(huán),打出一套組合拳呢(

          參考

          作者:doodlewind https://zhuanlan.zhihu.com/p/391077878

          • https://vitejs.dev/guide/why.html
          • https://vitejs.dev/guide/dep-pre-bundling.html
          • https://vitejs.dev/guide/api-plugin.html
          • https://vitejs.dev/config/

          瀏覽 32
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成 年 人 黄 色 视频 网站 久久久 | 全国男人天堂网 | 欧美日色| sm在线观看 | 免费成人视频久久 |