自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vite 微前端實(shí)踐,實(shí)現(xiàn)一個(gè)組件化的方案

開發(fā) 前端
微前端是一種多個(gè)團(tuán)隊(duì)通過(guò)獨(dú)立發(fā)布功能的方式來(lái)共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。

本文轉(zhuǎn)載自微信公眾號(hào)「前端星辰」,作者旋律 。轉(zhuǎn)載本文請(qǐng)聯(lián)系前端星辰公眾號(hào)。

什么是微前端

微前端是一種多個(gè)團(tuán)隊(duì)通過(guò)獨(dú)立發(fā)布功能的方式來(lái)共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。

微前端借鑒了微服務(wù)的架構(gòu)理念,將一個(gè)龐大的前端應(yīng)用拆分為多個(gè)獨(dú)立靈活的小型應(yīng)用,每個(gè)應(yīng)用都可以獨(dú)立開發(fā)、獨(dú)立運(yùn)行、獨(dú)立部署,再將這些小型應(yīng)用聯(lián)合為一個(gè)完整的應(yīng)用。微前端既可以將多個(gè)項(xiàng)目融合為一,又可以減少項(xiàng)目之間的耦合,提升項(xiàng)目擴(kuò)展性,相比一整塊的前端倉(cāng)庫(kù),微前端架構(gòu)下的前端倉(cāng)庫(kù)傾向于更小更靈活。

特性

  • 技術(shù)棧無(wú)關(guān) 主框架不限制接入應(yīng)用的技術(shù)棧,子應(yīng)用可自主選擇技術(shù)棧
  • 獨(dú)立開發(fā)/部署 各個(gè)團(tuán)隊(duì)之間倉(cāng)庫(kù)獨(dú)立,單獨(dú)部署,互不依賴
  • 增量升級(jí) 當(dāng)一個(gè)應(yīng)用龐大之后,技術(shù)升級(jí)或重構(gòu)相當(dāng)麻煩,而微應(yīng)用具備漸進(jìn)式升級(jí)的特性
  • 獨(dú)立運(yùn)行時(shí) 微應(yīng)用之間運(yùn)行時(shí)互不依賴,有獨(dú)立的狀態(tài)管理
  • 提升效率 應(yīng)用越龐大,越難以維護(hù),協(xié)作效率越低下。微應(yīng)用可以很好拆分,提升效率

目前可用的微前端方案

微前端的方案目前有以下幾種類型:

基于 iframe 完全隔離的方案

作為前端開發(fā),我們對(duì) iframe 已經(jīng)非常熟悉了,在一個(gè)應(yīng)用中可以獨(dú)立運(yùn)行另一個(gè)應(yīng)用。它具有顯著的優(yōu)點(diǎn):

  • 非常簡(jiǎn)單,無(wú)需任何改造
  • 完美隔離,JS、CSS 都是獨(dú)立的運(yùn)行環(huán)境
  • 不限制使用,頁(yè)面上可以放多個(gè) iframe 來(lái)組合業(yè)務(wù)

當(dāng)然,缺點(diǎn)也非常突出:

  • 無(wú)法保持路由狀態(tài),刷新后路由狀態(tài)就丟失
  • 完全的隔離導(dǎo)致與子應(yīng)用的交互變得極其困難
  • iframe 中的彈窗無(wú)法突破其本身

整個(gè)應(yīng)用全量資源加載,加載太慢

這些顯著的缺點(diǎn)也催生了其他方案的產(chǎn)生。

基于 single-spa 路由劫持方案

single-spa 通過(guò)劫持路由的方式來(lái)做子應(yīng)用之間的切換,但接入方式需要融合自身的路由,有一定的局限性。

qiankun 孵化自螞蟻金融科技基于微前端架構(gòu)的云產(chǎn)品統(tǒng)一接入平臺(tái)。它對(duì) single-spa 做了一層封裝。主要解決了 single-spa 的一些痛點(diǎn)和不足。通過(guò) import-html-entry 包解析 HTML 獲取資源路徑,然后對(duì)資源進(jìn)行解析、加載。

通過(guò)對(duì)執(zhí)行環(huán)境的修改,它實(shí)現(xiàn)了 JS 沙箱、樣式隔離 等特性。

京東 micro-app 方案

京東 micro-app 并沒(méi)有沿襲 single-spa 的思路,而是借鑒了 WebComponent 的思想,通過(guò) CustomElement 結(jié)合自定義的 ShadowDom,將微前端封裝成一個(gè)類 webComponents 組件,從而實(shí)現(xiàn)微前端的組件化渲染。

在 Vite 上使用微前端

我們從 我們從 UmiJS 遷移到了 Vite 之后,微前端也成為了勢(shì)在必行,當(dāng)時(shí)也調(diào)研了很多方案。

為什么沒(méi)用 qiankun

qiankun 是目前是社區(qū)主流微前端方案。它雖然很完善、流行,但最大的問(wèn)題就是不支持 Vite。它基于 import-html-entry 解析 HTML 來(lái)獲取資源,由于 qiankun 是通過(guò) eval 來(lái)執(zhí)行這些 js 的內(nèi)容,而 Vite 中的 script 標(biāo)簽類型是 type="module",里面包含 import/export 等模塊代碼, 所以會(huì)報(bào)錯(cuò):不允許在非 type="module" 的 script 里面使用 import。

退一步實(shí)現(xiàn),我們采用了 single-spa 的方式,并使用 systemjs 的方式進(jìn)行了微前端加載方案,也踩了不少的坑。single-spa 沒(méi)有一個(gè)友好的教程來(lái)接入,文檔雖然多,但大多都在講概念,當(dāng)時(shí)讓人覺(jué)得有一種深?yuàn)W的感覺(jué)。

后來(lái)看了它的源碼發(fā)現(xiàn),這都是些什么……里面大部分代碼都是圍繞路由劫持而展開的,根本沒(méi)有文檔上那種高大上的感覺(jué)。而我們又用不到它路由劫持的功能,那我們?yōu)槭裁匆盟?

從組件化的層面來(lái)說(shuō) single-spa 這種方式實(shí)現(xiàn)得一點(diǎn)都不優(yōu)雅。

  • 它劫持了路由,與 react-router 和組件化的思維格格不入
  • 接入方式一大堆繁雜的配置
  • 單實(shí)例的方案,即同一時(shí)刻,只有一個(gè)子應(yīng)用被展示

后來(lái)琢磨著 single-spa 的缺點(diǎn),我們可以自己實(shí)現(xiàn)一個(gè)組件化的微前端方案。

如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單、透明、組件化的方案

通過(guò)組件化思維實(shí)現(xiàn)一個(gè)微應(yīng)用非常簡(jiǎn)單:子應(yīng)用導(dǎo)出一個(gè)方法,主應(yīng)用加載子應(yīng)用并調(diào)用該方法,并傳入一個(gè) Element 節(jié)點(diǎn)參數(shù),子應(yīng)用得到該 Element 節(jié)點(diǎn),將本身的組件 appendChild 到 Element 節(jié)點(diǎn)上。

類型約定

在此之前我們需要約定一個(gè)主應(yīng)用與子應(yīng)用之間的一個(gè)交互方式。主要通過(guò)三個(gè)鉤子來(lái)保證應(yīng)用的正確執(zhí)行、更新、和卸載。

類型定義:

  1. export interface AppConfig { 
  2.   // 掛載 
  3.   mount?: (props: unknown) => void; 
  4.   // 更新 
  5.   render?: (props: unknown) => ReactNode | void; 
  6.   // 卸載 
  7.   unmount?: () => void; 

子應(yīng)用導(dǎo)出

通過(guò)類型的約定,我們可以將子應(yīng)用導(dǎo)出:mount、render、unmount 為主要鉤子。

React 子應(yīng)用實(shí)現(xiàn):

  1. export default (container: HTMLElement) => { 
  2.   let handleRender: (props: AppProps) => void; 
  3.  
  4.   // 包裹一個(gè)新的組件,用作更新處理 
  5.   function Main(props: AppProps) { 
  6.     const [state, setState] = React.useState(props); 
  7.     // 將 setState 方法提取給 render 函數(shù)調(diào)用,保持父子應(yīng)用觸發(fā)更新 
  8.     handleRender = setState; 
  9.     return <App {...state} />; 
  10.   } 
  11.  
  12.   return { 
  13.     mount(props: AppProps) { 
  14.       ReactDOM.render(<Main {...props} />, container); 
  15.     }, 
  16.     render(props: AppProps) { 
  17.       handleRender?.(props); 
  18.     }, 
  19.     unmount() { 
  20.       ReactDOM.unmountComponentAtNode(container); 
  21.     }, 
  22.   }; 
  23. }; 

 

Vue 子應(yīng)用實(shí)現(xiàn):

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3.  
  4. export default (container: HTMLElement) => { 
  5.   // 創(chuàng)建 
  6.   const app = createApp(App); 
  7.   return { 
  8.     mount() { 
  9.       // 裝載 
  10.       app.mount(container); 
  11.     }, 
  12.     unmount() { 
  13.       // 卸載 
  14.       app.unmount(); 
  15.     }, 
  16.   }; 
  17. }; 

主應(yīng)用實(shí)現(xiàn)

React 實(shí)現(xiàn)

其核心代碼僅十余行,主要處理與子應(yīng)用交互 (為了易讀性,隱藏了錯(cuò)誤處理代碼):

  1. export function MicroApp({ entry, ...props }: MicroAppProps) { 
  2.   // 傳遞給子應(yīng)用的節(jié)點(diǎn) 
  3.   const containerRef = useRef<HTMLDivElement>(null); 
  4.   // 子應(yīng)用配置 
  5.   const configRef = useRef<AppConfig>(); 
  6.  
  7.   useLayoutEffect(() => { 
  8.     import(/* @vite-ignore */ entry).then((res) => { 
  9.       // 將 div 傳給子應(yīng)用渲染 
  10.       const config = res.default(containerRef.current); 
  11.       // 調(diào)用子應(yīng)用的裝載方法 
  12.       config.mount?.(props); 
  13.       configRef.current = config; 
  14.     }); 
  15.     return () => { 
  16.       // 調(diào)用子應(yīng)用的卸載方法 
  17.       configRef.current?.unmount?.(); 
  18.       configRef.current = undefined; 
  19.     }; 
  20.   }, [entry]); 
  21.  
  22.   return <div ref={containerRef}>{configRef.current?.render?.(props)}</div>; 

完成,現(xiàn)在已經(jīng)實(shí)現(xiàn)了主應(yīng)用與子應(yīng)用的裝載、更新、卸載的操作。現(xiàn)在,它是一個(gè)組件,可以同時(shí)渲染出多個(gè)不同的子應(yīng)用,這點(diǎn)就比 single-spa 優(yōu)雅很多。

entry 子應(yīng)用地址,當(dāng)然真實(shí)情況會(huì)根據(jù) dev 和 prod 模式給出不同的地址:

  1. <MicroApp className="micro-app" entry="//localhost:3002/src/main.tsx" /> 

Vue 實(shí)現(xiàn)

  1. <script setup lang="ts"
  2. import { onMounted, onUnmounted, ref } from 'vue'
  3.  
  4. const { entry, ...props } = defineProps<{ entry: string }>(); 
  5. const container = ref<HTMLDivElement | null>(null); 
  6. const config = ref(); 
  7.  
  8. onMounted(() => { 
  9.   const element = container.value; 
  10.   import(/* @vite-ignore */ entry).then((res) => { 
  11.     // 將 div 傳給子應(yīng)用渲染 
  12.     const config = res.default(element); 
  13.     // 調(diào)用子應(yīng)用的裝載方法 
  14.     config.mount?.(props); 
  15.     config.value = config; 
  16.   }); 
  17. }); 
  18.  
  19. onUnmounted(() => { 
  20.   // 調(diào)用子應(yīng)用的卸載方法 
  21.   config.value?.unmount?.(); 
  22. }); 
  23. </script> 
  24.  
  25. <template> 
  26.   <div ref="container"></div> 
  27. </template>

如何讓子應(yīng)用也能獨(dú)立運(yùn)行

single-spa 等眾多方案,都是將一個(gè)變量掛載到 window 上,通過(guò)判斷該變量是否處于微前端環(huán)境,這樣很不優(yōu)雅。在 ESM 中,我們可以通過(guò) import.meta.url 傳入?yún)?shù)來(lái)判斷:

  1. if (!import.meta.url.includes('microAppEnv')) { 
  2.   ReactDOM.render( 
  3.     <React.StrictMode> 
  4.       <App /> 
  5.     </React.StrictMode>, 
  6.     document.getElementById('root'), 
  7.   ); 

入口導(dǎo)入修改:

  1. // 添加環(huán)境參數(shù)和當(dāng)前時(shí)間避免被緩存 
  2. import(/* @vite-ignore */ `${entry}?microAppEnv&t=${Date.now()}`); 

瀏覽器兼容性

IE 瀏覽器已經(jīng)逐步退出我們的視野,基于 Vite,我們只需要支持 import 的特性瀏覽器就夠了。當(dāng)然,如果考慮 IE 瀏覽器的話也不是不可以,很簡(jiǎn)單:將上面代碼的 import 替換為 System.import 即 systemjs,也是 single-spa 的所推崇的用法。

瀏覽器 Chrome Edge Firefox Internet Explorer Safari
import 61 16 60 No 10.1
Dynamic import 63 79 67 No 11.1
import.meta 64 79 62 No 11.1

模塊公用

我們的子組件必須要使用 mount 、unount 模式嗎?答案是不一定,如果我們的技術(shù)棧都是 React 的話。我們的子應(yīng)用只導(dǎo)出一個(gè) render 就夠了。這樣用的就是同一個(gè) React 來(lái)渲染,好處是子應(yīng)用可以消費(fèi)父應(yīng)用的 Provider。但有個(gè)前提是兩個(gè)應(yīng)用之間的 React 必須為同一個(gè)實(shí)例,否則就會(huì)報(bào)錯(cuò)。

我們可以將 react、react-dom 、styled-componets 等常用模塊提前打包成 ESM 模塊,然后放到文件服務(wù)中使用。

更改 Vite 配置添加 alias:

  1. defineConfig({ 
  2.   resolve: { 
  3.     alias: { 
  4.       react: '//localhost:8000/react@17.js'
  5.       'react-dom''//localhost:8000/react-dom@17.js'
  6.     }, 
  7.   }, 
  8. }); 

這樣就能愉快地使用同一份 React 代碼了。還能抽離出主應(yīng)用和子應(yīng)用之間的公用模塊,讓應(yīng)用總體積更小。當(dāng)然如果沒(méi)上 http2 的話,就需要考慮顆粒度的問(wèn)題了。

在線 CDN 方案:https://esm.sh

還有個(gè) importmap 方案,兼容性不太好,但未來(lái)是趨勢(shì):

  1. <script type="importmap"
  2.   { 
  3.     "imports": { 
  4.       "react""//localhost:8000/react@17.js" 
  5.     } 
  6.   } 
  7. </script> 

 

父子通信

組件式微應(yīng)用,可以傳遞參數(shù)而通信,完全就是 React 組件通信的模型。

資源路徑

  1. import logo from './images/logo.svg'
  2.  
  3. <img src={logo} />; 

在 Vite 的 dev 模式中,子應(yīng)用里面靜態(tài)資源一般會(huì)這樣引入:

  1. import logo from './images/logo.svg'
  2.  
  3. <img src={logo} />; 

圖片的路徑:/basename/src/logo.svg,在主應(yīng)用顯示就會(huì) 404。因?yàn)樵撀窂街皇谴嬖谟谧討?yīng)用。我們需要配合 URL 模塊使用,這樣路徑前面會(huì)帶上 origin 前綴:

  1. const logoURL = new URL(logo, import.meta.url); 
  2.  
  3. <img src={logoURL.href} />; 

當(dāng)然這樣使用比較繁瑣,我們可以將其封裝為一個(gè) Vite 插件自動(dòng)處理該場(chǎng)景。

路由同步

項(xiàng)目使用 react-router,那么它可能會(huì)存在路由不同步的問(wèn)題,因?yàn)椴皇峭粋€(gè) react-router 實(shí)例。即路由之間出現(xiàn)不聯(lián)動(dòng)的現(xiàn)象。

在 react-router 支持自定義 history 庫(kù),我們可以創(chuàng)建:

  1. import { createBrowserHistory } from 'history'
  2.  
  3. export const history = createBrowserHistory(); 
  4.  
  5. // 主應(yīng)用:路由入口 
  6. <HistoryRouter history={history}>{children}</HistoryRouter>; 
  7.  
  8. // 主應(yīng)用:傳遞給子應(yīng)用 
  9. <Route 
  10.   path="/child-app/*" 
  11.   element={<MicroApp entry="//localhost:3002/src/main.tsx" history={history} />} 
  12. />; 
  13.  
  14. // 子應(yīng)用:路由入口 
  15. <HistoryRouter basename="/child-app" history={history}> 
  16.   {children} 
  17. </HistoryRouter>; 

最終子應(yīng)用使用同一份 history 模塊。當(dāng)然這不是唯一的實(shí)現(xiàn),也不是優(yōu)雅的方式,我們可以將路由實(shí)例 navigate 傳遞給子應(yīng)用,這樣也能實(shí)現(xiàn)路由的交互。

注意:子應(yīng)用的 basename 必須與主應(yīng)用的 path 名稱保持一致。這里還需要修改 Vite 的配置 base 字段:

  1. export default defineConfig({ 
  2.   base: '/child-app/'
  3.   server: { 
  4.     port: 3002, 
  5.   }, 
  6.   plugins: [react()], 
  7. }); 

JS 沙箱

因?yàn)樯诚湓?ESM 下不支持,因?yàn)闊o(wú)法動(dòng)態(tài)改變執(zhí)行環(huán)境中模塊 window 對(duì)象,也無(wú)法注入新的全局對(duì)象。

一般 React、Vue 項(xiàng)目也很少修改全局變量,做好代碼規(guī)范檢查才是最主要的。

CSS 樣式隔離

自動(dòng) CSS 樣式隔離是有代價(jià)的,一般我們建議子應(yīng)用使用不同的 CSS 前綴,再配合 CSS Modules 基本上能實(shí)現(xiàn)需求。

打包部署

部署可以根據(jù)子應(yīng)用的 base 放置在不同的目錄,并將名稱對(duì)應(yīng)。配置好 nginx 轉(zhuǎn)發(fā)規(guī)則就可以了。我們可以將子應(yīng)用統(tǒng)一路由前綴,便于 nginx 將主應(yīng)用區(qū)分開并配置通用規(guī)則。

比如將主應(yīng)用放置在 system 目錄,子應(yīng)用放置在 app- 開頭的目錄:

  1. location ~ ^\/app-.*(\..+)$ { 
  2.     root /usr/share/nginx/html; 
  3.  
  4. location / { 
  5.     try_files $uri $uri/ /index.html; 
  6.     root /usr/share/nginx/html/system; 
  7.     index  index.html index.htm; 

優(yōu)點(diǎn)

1. 簡(jiǎn)單 核心不足 100 行代碼,無(wú)需多余的文檔

2. 靈活 通過(guò)約定的方式接入,也可以漸進(jìn)增強(qiáng)

3. 透明 無(wú)任何劫持方案,更多邏輯透明性

4. 組件化 組件化的渲染及參數(shù)通信

5. 基于 ESM 支持 Vite,面向未來(lái)

6. 向下兼容 可選 SystemJS 方案,兼容低版本瀏覽器

有示例嗎

示例代碼在 Github,感興趣的朋友可以 clone 下來(lái)學(xué)習(xí)。由于我們的技術(shù)棧是 React,所以這里示例的主應(yīng)用的實(shí)現(xiàn)用的是 React 。

微前端組件(React):https://github.com/MinJieLiu/micro-app

微前端示例:https://github.com/MinJieLiu/micro-app-demo

結(jié)語(yǔ)

微前端的方案適合團(tuán)隊(duì)場(chǎng)景的最好,打造一個(gè)團(tuán)隊(duì)能掌控的方案尤為重要。

參考資料:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import.meta

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import

 

 

責(zé)任編輯:武曉燕 來(lái)源: 前端星辰
相關(guān)推薦

2022-01-24 12:38:58

Vite插件開發(fā)

2020-05-19 10:45:31

沙箱前端原生對(duì)象

2024-07-16 11:26:35

微前端代碼JS

2017-07-24 13:58:49

Android組件化插件化

2022-08-10 10:32:47

編程實(shí)踐

2022-07-27 22:56:45

前端應(yīng)用緩存qiankun

2022-05-09 09:28:04

Vite前端開發(fā)

2021-01-01 09:01:05

前端組件化設(shè)計(jì)

2024-03-06 11:14:13

ViteReact微前端

2021-01-28 06:11:40

導(dǎo)航組件Sidenav Javascript

2021-01-26 10:33:45

前端開發(fā)技術(shù)

2025-01-09 10:46:01

2020-05-06 09:25:10

微前端qiankun架構(gòu)

2021-06-21 15:49:39

React動(dòng)效組件

2024-09-23 00:00:10

2023-09-07 20:04:06

前后端趨勢(shì)Node.js

2021-04-11 09:00:13

Fes.js前端

2020-11-30 06:20:13

javascript

2020-04-02 09:31:49

微前端架構(gòu)系統(tǒng)

2014-05-26 16:52:29

移動(dòng)前端web組件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)