成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Vite 微前端實踐,實現一個組件化的方案

開發 前端
微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略。

本文轉載自微信公眾號「前端星辰」,作者旋律 。轉載本文請聯系前端星辰公眾號。

什么是微前端

微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略。

微前端借鑒了微服務的架構理念,將一個龐大的前端應用拆分為多個獨立靈活的小型應用,每個應用都可以獨立開發、獨立運行、獨立部署,再將這些小型應用聯合為一個完整的應用。微前端既可以將多個項目融合為一,又可以減少項目之間的耦合,提升項目擴展性,相比一整塊的前端倉庫,微前端架構下的前端倉庫傾向于更小更靈活。

特性

  • 技術棧無關 主框架不限制接入應用的技術棧,子應用可自主選擇技術棧
  • 獨立開發/部署 各個團隊之間倉庫獨立,單獨部署,互不依賴
  • 增量升級 當一個應用龐大之后,技術升級或重構相當麻煩,而微應用具備漸進式升級的特性
  • 獨立運行時 微應用之間運行時互不依賴,有獨立的狀態管理
  • 提升效率 應用越龐大,越難以維護,協作效率越低下。微應用可以很好拆分,提升效率

目前可用的微前端方案

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

基于 iframe 完全隔離的方案

作為前端開發,我們對 iframe 已經非常熟悉了,在一個應用中可以獨立運行另一個應用。它具有顯著的優點:

  • 非常簡單,無需任何改造
  • 完美隔離,JS、CSS 都是獨立的運行環境
  • 不限制使用,頁面上可以放多個 iframe 來組合業務

當然,缺點也非常突出:

  • 無法保持路由狀態,刷新后路由狀態就丟失
  • 完全的隔離導致與子應用的交互變得極其困難
  • iframe 中的彈窗無法突破其本身

整個應用全量資源加載,加載太慢

這些顯著的缺點也催生了其他方案的產生。

基于 single-spa 路由劫持方案

single-spa 通過劫持路由的方式來做子應用之間的切換,但接入方式需要融合自身的路由,有一定的局限性。

qiankun 孵化自螞蟻金融科技基于微前端架構的云產品統一接入平臺。它對 single-spa 做了一層封裝。主要解決了 single-spa 的一些痛點和不足。通過 import-html-entry 包解析 HTML 獲取資源路徑,然后對資源進行解析、加載。

通過對執行環境的修改,它實現了 JS 沙箱、樣式隔離 等特性。

京東 micro-app 方案

京東 micro-app 并沒有沿襲 single-spa 的思路,而是借鑒了 WebComponent 的思想,通過 CustomElement 結合自定義的 ShadowDom,將微前端封裝成一個類 webComponents 組件,從而實現微前端的組件化渲染。

在 Vite 上使用微前端

我們從 我們從 UmiJS 遷移到了 Vite 之后,微前端也成為了勢在必行,當時也調研了很多方案。

為什么沒用 qiankun

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

退一步實現,我們采用了 single-spa 的方式,并使用 systemjs 的方式進行了微前端加載方案,也踩了不少的坑。single-spa 沒有一個友好的教程來接入,文檔雖然多,但大多都在講概念,當時讓人覺得有一種深奧的感覺。

后來看了它的源碼發現,這都是些什么……里面大部分代碼都是圍繞路由劫持而展開的,根本沒有文檔上那種高大上的感覺。而我們又用不到它路由劫持的功能,那我們為什么要用它?

從組件化的層面來說 single-spa 這種方式實現得一點都不優雅。

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

后來琢磨著 single-spa 的缺點,我們可以自己實現一個組件化的微前端方案。

如何實現一個簡單、透明、組件化的方案

通過組件化思維實現一個微應用非常簡單:子應用導出一個方法,主應用加載子應用并調用該方法,并傳入一個 Element 節點參數,子應用得到該 Element 節點,將本身的組件 appendChild 到 Element 節點上。

類型約定

在此之前我們需要約定一個主應用與子應用之間的一個交互方式。主要通過三個鉤子來保證應用的正確執行、更新、和卸載。

類型定義:

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

子應用導出

通過類型的約定,我們可以將子應用導出:mount、render、unmount 為主要鉤子。

React 子應用實現:

  1. export default (container: HTMLElement) => { 
  2.   let handleRender: (props: AppProps) => void; 
  3.  
  4.   // 包裹一個新的組件,用作更新處理 
  5.   function Main(props: AppProps) { 
  6.     const [state, setState] = React.useState(props); 
  7.     // 將 setState 方法提取給 render 函數調用,保持父子應用觸發更新 
  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 子應用實現:

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

主應用實現

React 實現

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

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

完成,現在已經實現了主應用與子應用的裝載、更新、卸載的操作。現在,它是一個組件,可以同時渲染出多個不同的子應用,這點就比 single-spa 優雅很多。

entry 子應用地址,當然真實情況會根據 dev 和 prod 模式給出不同的地址:

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

Vue 實現

  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 傳給子應用渲染 
  12.     const config = res.default(element); 
  13.     // 調用子應用的裝載方法 
  14.     config.mount?.(props); 
  15.     config.value = config; 
  16.   }); 
  17. }); 
  18.  
  19. onUnmounted(() => { 
  20.   // 調用子應用的卸載方法 
  21.   config.value?.unmount?.(); 
  22. }); 
  23. </script> 
  24.  
  25. <template> 
  26.   <div ref="container"></div> 
  27. </template>

如何讓子應用也能獨立運行

single-spa 等眾多方案,都是將一個變量掛載到 window 上,通過判斷該變量是否處于微前端環境,這樣很不優雅。在 ESM 中,我們可以通過 import.meta.url 傳入參數來判斷:

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

入口導入修改:

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

瀏覽器兼容性

IE 瀏覽器已經逐步退出我們的視野,基于 Vite,我們只需要支持 import 的特性瀏覽器就夠了。當然,如果考慮 IE 瀏覽器的話也不是不可以,很簡單:將上面代碼的 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 模式嗎?答案是不一定,如果我們的技術棧都是 React 的話。我們的子應用只導出一個 render 就夠了。這樣用的就是同一個 React 來渲染,好處是子應用可以消費父應用的 Provider。但有個前提是兩個應用之間的 React 必須為同一個實例,否則就會報錯。

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

更改 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 代碼了。還能抽離出主應用和子應用之間的公用模塊,讓應用總體積更小。當然如果沒上 http2 的話,就需要考慮顆粒度的問題了。

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

還有個 importmap 方案,兼容性不太好,但未來是趨勢:

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

 

父子通信

組件式微應用,可以傳遞參數而通信,完全就是 React 組件通信的模型。

資源路徑

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

在 Vite 的 dev 模式中,子應用里面靜態資源一般會這樣引入:

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

圖片的路徑:/basename/src/logo.svg,在主應用顯示就會 404。因為該路徑只是存在于子應用。我們需要配合 URL 模塊使用,這樣路徑前面會帶上 origin 前綴:

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

當然這樣使用比較繁瑣,我們可以將其封裝為一個 Vite 插件自動處理該場景。

路由同步

項目使用 react-router,那么它可能會存在路由不同步的問題,因為不是同一個 react-router 實例。即路由之間出現不聯動的現象。

在 react-router 支持自定義 history 庫,我們可以創建:

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

最終子應用使用同一份 history 模塊。當然這不是唯一的實現,也不是優雅的方式,我們可以將路由實例 navigate 傳遞給子應用,這樣也能實現路由的交互。

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

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

JS 沙箱

因為沙箱在 ESM 下不支持,因為無法動態改變執行環境中模塊 window 對象,也無法注入新的全局對象。

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

CSS 樣式隔離

自動 CSS 樣式隔離是有代價的,一般我們建議子應用使用不同的 CSS 前綴,再配合 CSS Modules 基本上能實現需求。

打包部署

部署可以根據子應用的 base 放置在不同的目錄,并將名稱對應。配置好 nginx 轉發規則就可以了。我們可以將子應用統一路由前綴,便于 nginx 將主應用區分開并配置通用規則。

比如將主應用放置在 system 目錄,子應用放置在 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; 

優點

1. 簡單 核心不足 100 行代碼,無需多余的文檔

2. 靈活 通過約定的方式接入,也可以漸進增強

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

4. 組件化 組件化的渲染及參數通信

5. 基于 ESM 支持 Vite,面向未來

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

有示例嗎

示例代碼在 Github,感興趣的朋友可以 clone 下來學習。由于我們的技術棧是 React,所以這里示例的主應用的實現用的是 React 。

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

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

結語

微前端的方案適合團隊場景的最好,打造一個團隊能掌控的方案尤為重要。

參考資料:

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

 

 

責任編輯:武曉燕 來源: 前端星辰
相關推薦

2022-01-24 12:38:58

Vite插件開發

2020-05-19 10:45:31

沙箱前端原生對象

2024-07-16 11:26:35

微前端代碼JS

2017-07-24 13:58:49

Android組件化插件化

2022-08-10 10:32:47

編程實踐

2022-07-27 22:56:45

前端應用緩存qiankun

2022-05-09 09:28:04

Vite前端開發

2021-01-01 09:01:05

前端組件化設計

2024-03-06 11:14:13

ViteReact微前端

2020-05-06 09:25:10

微前端qiankun架構

2021-01-26 10:33:45

前端開發技術

2023-09-07 20:04:06

前后端趨勢Node.js

2025-01-09 10:46:01

2021-04-11 09:00:13

Fes.js前端

2021-01-28 06:11:40

導航組件Sidenav Javascript

2024-09-23 00:00:10

2021-06-21 15:49:39

React動效組件

2020-04-02 09:31:49

微前端架構系統

2020-11-30 06:20:13

javascript

2015-01-08 10:08:03

前后端分離
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: www.99re| 成人av影院 | 国内精品久久久久久 | 欧美国产日韩在线 | 欧美激情一区二区三区 | 成人欧美一区二区三区黑人孕妇 | 国产一区二区三区 | 中文字幕第一页在线 | 日韩中文字幕在线免费 | 国产亚洲二区 | 最新中文字幕在线 | 欧美日韩一区二区三区四区五区 | 亚洲国产第一页 | 欧美日韩一区二区在线 | 97精品国产一区二区三区 | 夜久久 | 一区二区三区高清 | 黄色亚洲| 99视频久| 久久久精品日本 | 中文字幕 欧美 日韩 | 亚洲精品一区二区二区 | 91在线看片| 黄色一级免费 | 国产精品嫩草影院精东 | 国产精品一区在线观看 | 中文字幕日韩欧美一区二区三区 | 九一视频在线播放 | 国产美女一区二区 | 一级做受毛片免费大片 | 日韩一区二区三区在线观看 | 久久精品国产亚洲 | 久久久久国产一区二区三区 | 久久久蜜臀国产一区二区 | 中文字幕在线免费 | 成人欧美一区二区三区在线观看 | 一区二区中文字幕 | 亚洲一区二区国产 | 欧美成人一区二区 | 色黄网站 | 成人自拍视频 |