還在為「公司要從 Vue 轉 React」或「新專案用什麼框架」而頭痛嗎?如果我告訴你,現在有一種技術可以讓 React 和 Vue 在同一個專案中完美共存,你會不會覺得我在說夢話?
今天要跟大家分享的 Vite + Module Federation,就是這樣一個讓人眼睛為之一亮的黑科技。它不只解決了框架選擇的痛苦,更開啟了前端架構設計的全新可能性。
什麼是微前端?為什麼我們需要它?
想像一下,你正在開發一個大型電商平台。產品團隊負責商品展示頁面,購物車團隊處理結帳流程,用戶中心團隊維護會員系統。傳統做法是把所有功能打包成一個巨大的應用程式,結果就是:
- 一個人改了購物車邏輯,整個網站都要重新部署
- 產品團隊想用最新的 React 18,但購物車團隊還在用 Vue 2
- 測試時牽一髮動全身,誰都不敢隨便改程式碼
微前端架構就像是把一個大房子分割成多個獨立的套房,每個團隊都有自己的空間,可以自由裝潢、獨立出入,但仍然共享同一個地址。
Module Federation:前端界的樂高積木
Module Federation 最初是 Webpack 5 的旗艦功能,它的核心概念很簡單:讓不同的應用程式可以在執行時動態分享程式碼。
這就像是樂高積木一樣,每個團隊負責製作不同的積木塊,最後在瀏覽器中組裝成完整的應用程式。最神奇的是,這些積木塊可以用不同的「材料」製作 —— 有些用 React,有些用 Vue,甚至可以混用!
兩個關鍵角色
在 Module Federation 的世界裡,有兩個重要角色:
Host(主機應用程式):就像是一個容器,負責載入和整合其他應用程式。它是用戶最終看到的完整應用程式。
Remote(遠端應用程式):獨立開發、部署的微前端模組,可以把自己的組件或功能「出租」給其他應用程式使用。
Vite:讓 Module Federation 飛起來
傳統的 Module Federation 需要 Webpack,但 Webpack 的建置速度...你懂的。當專案變大時,喝杯咖啡回來可能都還在編譯。
這時候 Vite 就像是超級英雄一樣出現了!它利用瀏覽器原生的 ES Modules 特性,讓開發體驗快到飛起來。而 @originjs/vite-plugin-federation 這個插件,則是把 Module Federation 的魔法帶到了 Vite 的世界。
為什麼 React 能整合 Vue?技術原理大揭密
這可能是最讓人好奇的問題了。React 和 Vue 不是競爭對手嗎?怎麼可能在同一個應用程式中共存?
1. 執行時載入的魔法
關鍵在於 執行時載入。Module Federation 不是在建置時把所有程式碼打包在一起,而是在瀏覽器執行時動態載入遠端模組。
1 2
| const VueComponent = await import('remote-vue-app/VueComponent');
|
想像一下,你的 React 應用程式已經在瀏覽器中執行了,這時候它說:「嘿,我需要一個 Vue 組件,去遠端抓一下吧!」然後 Vue 組件就被載入進來,在指定的 DOM 節點中開始工作。
2. DOM 的和諧共處
無論是 React 還是 Vue,最終都是在操作 DOM。它們就像是兩個室友,只要各自使用不同的房間(DOM 節點),就不會互相干擾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const VueComponentWrapper: React.FC = () => { const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => { import('remote-vue-app/VueComponent').then((module) => { const VueComponent = module.default; if (containerRef.current) { const app = createApp(VueComponent); app.mount(containerRef.current); } }); }, []);
return <div ref={containerRef} />; };
|
3. 框架實例的獨立性
每個框架都有自己的實例和生命週期管理。React 管理自己的組件樹,Vue 在分配給它的 DOM 節點內運行,兩者井水不犯河水。
實戰案例:打造混合框架的電商平台
讓我們看看一個實際的例子。假設我們要建造一個電商平台:
Remote 應用程式設定(Vue 商品展示模組)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { federation } from '@originjs/vite-plugin-federation';
export default { plugins: [ federation({ name: 'product-module', filename: 'remoteEntry.js', exposes: { './ProductList': './src/components/ProductList.vue', './ProductDetail': './src/components/ProductDetail.vue' }, shared: { 'vue': { singleton: false } } }) ], server: { port: 5001 } }
|
Host 應用程式設定(React 主應用程式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { federation } from '@originjs/vite-plugin-federation';
export default { plugins: [ federation({ name: 'main-app', remotes: { productModule: "http://localhost:5001/assets/remoteEntry.js", }, shared: { 'react': { singleton: true }, 'react-dom': { singleton: true } } }) ] }
|
在 React 中使用 Vue 組件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { useEffect, useRef } from 'react';
const ProductSection: React.FC = () => { const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => { import('productModule/ProductList').then((module) => { const ProductList = module.default; if (containerRef.current) { const { createApp } = await import('vue'); const app = createApp(ProductList); app.mount(containerRef.current); } }); }, []);
return ( <div className="product-section"> <h1>我們的商品(React 標題)</h1> <div ref={containerRef} /> {/* Vue 組件會在這裡渲染 */} </div> ); };
|
跨框架通訊:讓 React 和 Vue 對話
當 React 和 Vue 需要「聊天」時,可以透過以下方式:
1. 自定義事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const handleProductClick = (product) => { window.dispatchEvent(new CustomEvent('product-selected', { detail: { product } })); };
useEffect(() => { const handleProductSelection = (event) => { console.log('用戶選擇了商品:', event.detail.product); }; window.addEventListener('product-selected', handleProductSelection); return () => window.removeEventListener('product-selected', handleProductSelection); }, []);
|
2. 共享狀態管理
使用 Zustand 或其他狀態管理工具:
1 2 3 4 5 6 7 8 9
| import { create } from 'zustand';
const useSharedStore = create((set) => ({ selectedProduct: null, setSelectedProduct: (product) => set({ selectedProduct: product }), }));
|
實務考量:什麼時候該用這招?
雖然 Module Federation 看起來很酷,但不是所有專案都適合:
適合的場景
- 大型企業級應用:多個團隊各自負責不同模組
- 漸進式遷移:從 Vue 2 升級到 Vue 3,或從 Vue 遷移到 React
- 技術棧多樣化:團隊有不同的技術專長
- 獨立部署需求:希望各模組能獨立發布更新
需要謹慎的場景
- 小型專案:複雜度可能超過效益
- 效能敏感應用:多框架載入會增加初始載入時間
- 團隊技術單一:如果大家都熟悉同一個框架,沒必要混用
效能優化秘訣
1. 智慧的依賴共享
1 2 3 4 5 6 7 8 9 10 11
| shared: { 'react': { singleton: true, eager: true, requiredVersion: '^18.0.0' }, 'lodash': { singleton: false, shareScope: 'default' } }
|
2. 懶載入策略
1 2 3 4 5 6
| const LazyVueComponent = React.lazy(() => import('productModule/ProductList').then(module => ({ default: () => <VueWrapper component={module.default} /> })) );
|
3. 預載入重要模組
1 2 3 4 5 6 7 8
| const preloadModules = async () => { await import('productModule/ProductList'); await import('cartModule/ShoppingCart'); };
preloadModules();
|
開發體驗優化
TypeScript 支援
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| declare module 'productModule/ProductList' { const ProductList: any; export default ProductList; }
interface ProductListProps { category?: string; onProductSelect?: (product: Product) => void; }
declare module 'productModule/ProductList' { const ProductList: (props: ProductListProps) => void; export default ProductList; }
|
開發環境設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const isDev = process.env.NODE_ENV === 'development';
const loadRemoteComponent = async (modulePath, fallback) => { try { return await import(modulePath); } catch (error) { if (isDev) { console.warn(`無法載入遠端模組 ${modulePath},使用 fallback`); return fallback; } throw error; } };
|
監控與除錯
載入狀態追蹤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const useRemoteComponent = (modulePath: string) => { const [component, setComponent] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { import(modulePath) .then(module => { setComponent(module.default); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, [modulePath]);
return { component, loading, error }; };
|
錯誤邊界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class RemoteComponentErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }
static getDerivedStateFromError(error) { return { hasError: true }; }
render() { if (this.state.hasError) { return <div>遠端組件載入失敗,請稍後再試</div>; }
return this.props.children; } }
|
未來展望:前端架構的新可能
Module Federation + Vite 的組合,不只是技術上的創新,更是前端架構思維的轉變。它讓我們重新思考:
- 團隊協作:不再需要統一技術棧,每個團隊都能發揮所長
- 技術演進:可以漸進式地採用新技術,而不用推倒重來
- 維護成本:模組化的架構讓維護更輕鬆,bug 影響範圍更小
當然,這個技術還在快速發展中。隨著 Module Federation Runtime 等新工具的出現,相信未來會有更多驚喜等著我們。
總結:擁抱多元,釋放創意
Vite + Module Federation 證明了一件事:技術不應該成為創意的枷鎖。
當我們不再被「只能選擇一個框架」的思維限制時,前端開發的可能性變得無限寬廣。React 的生態系統、Vue 的簡潔優雅、Angular 的企業級特性...現在我們可以在同一個專案中享受它們的優點。
下次當有人問你「我們該用 React 還是 Vue?」時,你可以自信地回答:「為什麼不能兩個都用呢?」
記住,最好的架構不是最新的技術,而是最適合你團隊和專案需求的解決方案。Module Federation 給了我們更多選擇,但選擇權還是在我們手中。
想要親自體驗 Module Federation 的魔力嗎?建議先從小型 POC 開始,感受一下跨框架開發的樂趣。記住,技術是工具,創意才是靈魂!