前言與大綱
本文分為理論和實(shí)戰(zhàn)上下兩篇。本篇為微前端的實(shí)戰(zhàn)篇,共計(jì)約 5k 字,預(yù)計(jì)閱讀時(shí)間 10 mins。
在技術(shù)雷達(dá)之「微前端」- 將微服務(wù)理念擴(kuò)展到前端開發(fā)(上:理論篇)中,我們介紹了微前端在單體應(yīng)用和微服務(wù)的架構(gòu)演進(jìn)中所產(chǎn)生的緣由,將微服務(wù)理念運(yùn)用到前端開發(fā)就是為了解決臃腫前端的當(dāng)前現(xiàn)狀。與此同時(shí),合理拆分微前端也給我們的應(yīng)用開發(fā)帶來顯而易見的好處,在本篇當(dāng)中我們將逐一介紹微前端的實(shí)踐方案與可能遇到的問題和對(duì)應(yīng)的優(yōu)化建議。
文章大綱
微前端的可選實(shí)踐方案(4 種 +)
創(chuàng)建更小的 Apps(而不是 Components)
如何組合微前端的 App 模塊?
Option 1: 使用后端模板引擎插入 HTML
Option 1.1: 漸進(jìn)式從后端進(jìn)行加載
Option 2: 使用 IFrame 隔離運(yùn)行時(shí)
Option 3: 客戶端 JavaScript 異步加載
Option 4: WebComponents 整合所有功能模塊
不同 App 模塊之間如何交互?
More Options…
微前端的頁(yè)面優(yōu)化與實(shí)例
多模塊頁(yè)面加載問題與優(yōu)化建議
微前端在 AEM(CMS)項(xiàng)目的應(yīng)用
現(xiàn)成解決方案:Single-SPA “meta framework”
總結(jié)與思考:微前端的優(yōu)缺點(diǎn)
微前端的優(yōu)點(diǎn)
微前端的缺點(diǎn)
持續(xù)思考…
附:參考資料
微前端的可選實(shí)踐方案(4 種+)
創(chuàng)建更小的 Apps(而不是 Components)
首先讓我們來創(chuàng)建一個(gè)典型 Web 應(yīng)用程序的基本組件(Header、ProductList、ShoppingCart),以 Header 組件為例:
# src/App.js
export default () =>
<header>
<h1>Logo</h1>
<nav>
<ul>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
</header>;
然后需要注意的是我們會(huì)用到 Express 對(duì)剛剛創(chuàng)建的 React 組件進(jìn)行服務(wù)器端渲染,使之成為一個(gè) App 模塊:
# server.js
fs.readFile(htmlPath, 'utf8', (err, html) => {
const rootElem = '<div id="root">';
const renderedApp = renderToString(React.createElement(App, null));
res.send(html.replace(rootElem, rootElem + renderedApp));
});
再依次創(chuàng)建其他 Apps 并獨(dú)立部署:
如何組合微前端的 App 模塊?
在每個(gè)獨(dú)立團(tuán)隊(duì)創(chuàng)建好各自的 App 模塊后,我們就可以將網(wǎng)站或 Web 應(yīng)用程序視為由各種模塊的功能組合。下文將介紹多種技術(shù)實(shí)踐方案來重新組合這些模塊(有時(shí)作為頁(yè)面,有時(shí)作為組件),而前端(不管是不是 SPA)將只需要負(fù)責(zé)路由器(Router)如何選擇和決定要導(dǎo)入哪些模塊,從而為最終用戶提供一致性的用戶體驗(yàn)。
Option 1: 使用后端模板引擎插入 HTML
# server.js
Promise.all([
getContents('https://microfrontends-header.herokuapp.com/'),
getContents('https://microfrontends-products-list.herokuapp.com/'),
getContents('https://microfrontends-cart.herokuapp.com/')
]).then(responses =>
res.render('index', { header: responses[0], productsList: responses[1], cart: responses[2] })
).catch(error =>
res.send(error.message)
)
);
# views/index.ejs
<head>
<meta charset="utf-8">
<title>Microfrontends Homepage</title>
</head>
<body>
<%- header %>
<%- productsList %>
<%- cart %>
</body>
但是,這種方案也存在弊端,即某些 App 模塊可能會(huì)需要相對(duì)較長(zhǎng)的加載時(shí)間,而在前端整個(gè)頁(yè)面的渲染卻要取決于最慢的那個(gè)模塊。
比如說,可能 Header 模塊的加載速度要比其他部分快得多,而 ProductList 則因?yàn)樾枰@取更多 API 數(shù)據(jù)而需要更多時(shí)間。通常情況下我們希望盡快將網(wǎng)頁(yè)顯示給用戶,而在這種情況下后臺(tái)加載時(shí)間就會(huì)變得更長(zhǎng)。
Option 1.1: 漸進(jìn)式從后端進(jìn)行加載
當(dāng)然,我們也可以通過修改一些后端代碼來漸進(jìn)式地(Progressive)往前端發(fā)送 HTML,但與此同時(shí)卻徒增了后端復(fù)雜度,并且又將前端的渲染控制權(quán)交回了后端服務(wù)器。而且我們的優(yōu)化也取決于每個(gè)模塊加載的速度,若是進(jìn)行優(yōu)化就必須按一定順序進(jìn)行加載。
Option 2: 使用 IFrame 隔離運(yùn)行時(shí)
<body>
<iframe width="100%" height="200" src="https://microfrontends-header.herokuapp.com/"></iframe>
<iframe width="100%" height="200" src="https://microfrontends-products-list.herokuapp.com/"></iframe>
<iframe width="100%" height="200" src="https://microfrontends-cart.herokuapp.com/"></iframe>
</body>
我們也可以將每個(gè)子應(yīng)用程序嵌入到各自的 <iframe>
中,這使得每個(gè)模塊能夠使用任何他們需要的框架,而無需與其他團(tuán)隊(duì)協(xié)調(diào)工具和依賴關(guān)系,依然可以借助于一些庫(kù)或者 Window.postMessageAPI
來進(jìn)行交互。
優(yōu)點(diǎn):
最強(qiáng)大的是隔離了組件和應(yīng)用程序部分的運(yùn)行時(shí)環(huán)境,因此每個(gè)模塊都可以獨(dú)立開發(fā),并且可以與其他部分的技術(shù)無關(guān)
可以各自使用完全不同的前端框架,可以在 React 中開發(fā)一部分,在 Angular 中開發(fā)一部分,然后使用原生 JavaScript 開發(fā)其他部分或任何其他技術(shù)。
只要每個(gè) iframe 來自同一個(gè)來源,消息傳遞也就相當(dāng)直接和強(qiáng)大。參考文檔
Window.postMessageAPI
缺點(diǎn):
Bundle 的大小非常明顯,因?yàn)榭赡茏罱K會(huì)多次發(fā)送相同的庫(kù),并且由于應(yīng)用程序是分開的,所以在構(gòu)建時(shí)也不能提取公共依賴關(guān)系。
至于瀏覽器的支持,基本上不可能嵌套兩層以上的 iframe(
parent - > iframe - > iframe
)。如果任何嵌套的框架需要能夠滾動(dòng)或具有 Form 表單域,那樣的情況處理起來就會(huì)變得特別痛苦。
Option 3: 客戶端 JavaScript 異步加載
function loadPage (element) {
[].forEach.call(element.querySelectorAll('script'), function (nonExecutableScript) {
var script = document.createElement("script");
script.setAttribute("src", nonExecutableScript.src);
script.setAttribute("type", "text/javascript");
element.appendChild(script);
});
}
document.querySelectorAll('.load-app').forEach(loadPage);
<div class="load-app" data-url="header"></div>
<div class="load-app" data-url="products-list"></div>
<div class="load-app" data-url="cart"></div>
簡(jiǎn)單來說,這種方式就是在客戶端瀏覽器通過 Ajax 加載應(yīng)用程序,然后將不同模塊的內(nèi)容插入到對(duì)應(yīng)的 div
中,而且還必須手動(dòng)克隆每個(gè) script 的標(biāo)記才能使其工作。
需要注意的是,為了避免 Javascript 和 CSS 加載順序的問題,建議將其修改成類似于 Facebook bigpipe
的解決方案,返回一個(gè) JSON 對(duì)象 { html: ..., css: [...], js: [...] }
再進(jìn)行加載順序的控制。
Option 4: WebComponents 整合所有功能模塊
Web Components 是一個(gè) Web 標(biāo)準(zhǔn),所以像 Angular、React/Preact、Vue 或 Hyperapp 這樣的主流 JavaScript 框架都支持它們。你可以將 Web Components 視為使用開放 Web 技術(shù)創(chuàng)建的可重用的用戶界面小部件,也許會(huì)是 Web 組件化的未來。
Web Components 由以下四種技術(shù)組成(盡管每種技術(shù)都可以獨(dú)立使用):
自定義元素(Custom Elements)對(duì)外提供組件的標(biāo)簽,實(shí)現(xiàn)自定義標(biāo)簽:可以創(chuàng)建自己的自定義 HTML 標(biāo)簽和元素。每個(gè)元素可以有自己的腳本和 CSS 樣式。還包括生命周期回調(diào),它們?cè)试S我們定義正在加載的組件特定行為。
HTML 模板(HTML
<template>
)定義組件的 HTML 模板能力:一種用于保存客戶端內(nèi)容的機(jī)制,該內(nèi)容在頁(yè)面加載時(shí)不被渲染,但可以在運(yùn)行時(shí)使用 JavaScript 進(jìn)行實(shí)例化。可以將一個(gè)模板視為正在被存儲(chǔ)以供隨后在文檔中使用的一個(gè)內(nèi)容片段。影子 DOM(Shadow DOM)封裝組件的內(nèi)部結(jié)構(gòu),并且保持其獨(dú)立性:允許我們?cè)?Web 組件中封裝 JavaScript,CSS 和 HTML。在組件內(nèi)部時(shí),這些東西與主文檔的 DOM 分離。
HTML 導(dǎo)入(HTML Imports)解決組件組合和依賴加載:在微前端的上下文中,可以是包含我們要使用的組件在服務(wù)器上的遠(yuǎn)程位置。
# src/index.js
class Header extends HTMLElement {
attachedCallback() {
ReactDOM.render(<App />, this.createShadowRoot());
}
}
document.registerElement('microfrontends-header', Header);
<body>
<microfrontends-header></microfrontends-header>
<microfrontends-products-list></microfrontends-products-list>
<microfrontends-cart></microfrontends-cart>
</body>
在微前端的實(shí)踐當(dāng)中:
每個(gè)團(tuán)隊(duì)使用各自的技術(shù)棧創(chuàng)建他們的組件,并把它包裝到自定義元素(Custom Element)中(如
<microfrontends-header></microfrontends-header>
)。Web 組件就是應(yīng)用程序中包含的組件的本地實(shí)現(xiàn),如菜單,表單,日期選擇器等。每個(gè)組件都是獨(dú)立開發(fā)的,主應(yīng)用程序項(xiàng)目利用它們組裝成最終的應(yīng)用程序。
特定元素(標(biāo)簽名稱,屬性和事件)的 DOM 規(guī)范還可以充當(dāng)跨團(tuán)隊(duì)之間的契約或公共 API。
創(chuàng)建可被導(dǎo)入到 Web 應(yīng)用程序中的可重用組件,它們就像可以導(dǎo)入任何網(wǎng)頁(yè)的用戶界面小部件。
<link rel="import" href="/components/microfrontends/header.html">
<link rel="import" href="/components/microfrontends/products-list.html">
<link rel="import" href="/components/microfrontends/cart.html">
優(yōu)點(diǎn)
代碼的可讀性變得非常清晰,組件資源內(nèi)部高內(nèi)聚,組件資源由自身加載控制,作用域獨(dú)立。
功能團(tuán)隊(duì)可以使用組件及其功能,而不必知道實(shí)現(xiàn),他們只需要能夠與 HTML DOM 進(jìn)行交互。
使用 PubSub 機(jī)制,組件可以發(fā)布消息,其他組件可以訂閱特定的主題。幸運(yùn)的是瀏覽器內(nèi)置了這個(gè)功能。比如購(gòu)物車可以在
window
訂閱此事件并在應(yīng)該刷新其數(shù)據(jù)時(shí)得到通知。缺點(diǎn)
可惜的是,Web 組件規(guī)范跟服務(wù)器渲染無關(guān)。沒有 JavaScript,就沒有所謂的自定義元素。
瀏覽器和框架的支持不夠,需要更多的 polyfills 從而影響到用戶頁(yè)面的加載體驗(yàn)。
我們需要在整個(gè) Web 應(yīng)用程序上做出改變,把它們?nèi)哭D(zhuǎn)換成 Web Components。
社區(qū)不夠活躍,Web Components 還沒有真正流行起來,也許永遠(yuǎn)也不會(huì)。
不同 App 模塊之間如何交互?
# angularComponent.ts
const event = new CustomEvent('addToCart', { detail: item });
window.dispatchEvent(event);
# reactComponent.js
componentDidMount() {
window.addEventListener('addToCart', (event) => {
this.setState({ products: [...this.state.products, event.detail] });
}, false);
}
得益于瀏覽器的原生 API,Custom Event 可以與其他任何技術(shù)和框架一起工作。比如,我們可以將消息從 Angular 組件發(fā)送到 React 組件。其實(shí)這也是現(xiàn)在 API 之間普遍使用 JSON 進(jìn)行通信的原因,即使沒有人使用 NodeJS 作為服務(wù)器端。
但是,新的問題又出現(xiàn)了。我們?cè)撊绾螠y(cè)試這種跨模塊之間的交互?需要編寫類似于后端微服務(wù)之間的 Contract Testing 或 Integration Testing 嗎?并沒有答案。
More Options...
組件庫(kù) - 根據(jù)主 App 的技術(shù)棧,不同的組件和 App 模塊拆分作為庫(kù)的形式提供給主App,所以主 App 是由不同組件組成的。但是組件庫(kù)的升級(jí)將成為一個(gè)大麻煩,比如對(duì) Header 組件進(jìn)行了更改,那么如果已經(jīng)有 50 個(gè)頁(yè)面使用了 Header 組件該怎么辦?必須要求每一頁(yè)都升級(jí)它的 Header,而且升級(jí)過程中用戶還會(huì)在整個(gè)網(wǎng)站不同頁(yè)面上看到不一致的標(biāo)題。并且,在兩邊還必須都使用相同的技術(shù),比如 Header 組件中使用了 ClojureScript,而 Content 組件中又用了 Elm,那么該怎么辦?構(gòu)建工具就必須在編譯時(shí)處理不同的語言。
將 App 模塊作為 React 黑盒組件分發(fā)給消費(fèi)者模塊 - 應(yīng)用程序的狀態(tài)完全包含在組件中,API 只是通過 props 暴露出來。這種方式其實(shí)增加了應(yīng)用程序之間的耦合,因?yàn)樗仁姑總€(gè)人都使用 React,甚至?xí)褂孟嗤姹镜?React,但是這似乎也是一個(gè)比較好的折衷。
Edge Side Includes(ESI)/Server Side Includes(SSI) - 通過特殊的文件后綴 (shtml,inc) 或簡(jiǎn)單的標(biāo)記語言來對(duì)那些可以加速和不能加速的網(wǎng)頁(yè)中的內(nèi)容片斷進(jìn)行描述,將每個(gè)網(wǎng)頁(yè)劃分成不同的小部分分別賦予不同的緩存控制策略。SSI / ESI 方法的缺點(diǎn)是,最慢的片段決定了整個(gè)頁(yè)面的響應(yīng)時(shí)間。
微前端的頁(yè)面優(yōu)化與實(shí)例
多模塊頁(yè)面加載問題與優(yōu)化建議
使用 skeleton screen 響應(yīng)式布局:如上圖 LinkedIn 所做的那樣,首先展現(xiàn)給用戶一個(gè)頁(yè)面的空白版本,然后在這個(gè)頁(yè)面中逐漸加載和填充相應(yīng)的信息。否則中間的信息流部分的內(nèi)容最初是空白的,然后在 JavaScript 被加載和執(zhí)行過后,信息流就會(huì)因?yàn)樾枰加酶嗟目臻g而推動(dòng)整個(gè)頁(yè)面的布局。雖然我們可以控制頁(yè)面來固定中間部分的高度,但在響應(yīng)式網(wǎng)站上,確定一個(gè)確切的高度往往很難,而且不同的屏幕尺寸可能會(huì)有所不同。但更重要的問題是,這種高度尺寸的約定會(huì)讓不同團(tuán)隊(duì)之間產(chǎn)生緊密的聯(lián)系,從而違背了微前端的初衷。
使用瀏覽器異步加載加快初始渲染:對(duì)于加載成本高且難以緩存的碎片,將其從初始渲染中排除是一個(gè)好主意。比如說 LinkedIn 首頁(yè)的信息流就是一個(gè)很好的例子。
共享 UI 組件庫(kù)保證視覺體驗(yàn)一致:在前端設(shè)計(jì)中,必須向用戶呈現(xiàn)外觀和感覺一致的用戶界面。建議可以建立一個(gè)共享組件庫(kù)(包含 CSS、字體和 JavaScript)。將這些資源托管在 CDN,每個(gè)微前端就可以在其 HTML 輸出中引用它們的位置。每個(gè)組件庫(kù)的版本都正確地對(duì)資源進(jìn)行版本控制,而每個(gè)微前端都指定要使用的組件庫(kù)的版本和顯式更新依賴關(guān)系。
-
使用集中式服務(wù)(Router)來管理 URL:可以理解為前端的 Gateway,不同的 URL 對(duì)應(yīng)不同應(yīng)用程序所包含的內(nèi)容。建議通過一個(gè)集中式的 URLs Router 來為應(yīng)用程序提供一個(gè) API 來注冊(cè)他們自己的 URL,Router 將會(huì)位于 Web 應(yīng)用程序的前面,根據(jù)不同的用戶請(qǐng)求指向不同的 App 模塊組合。
image.png 提取共同依賴作為 externals 加載:雖然說不同 App 模塊之間不能直接共享相同的第三方模塊,當(dāng)我們依然可以將常用的依賴比如
lodash
、moment.js
等公共庫(kù),或者跨多個(gè)團(tuán)隊(duì)共同使用的react
和react-dom
。通過 Webpack 等構(gòu)建工具就可以把打包的時(shí)候?qū)⑦@些共同模塊排除掉,而只需要在 HTML<header>
中的<script>
中直接通過 CDN 加載 externals 依賴。
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/react.min.js"
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/react-dom.min.js"
crossorigin="anonymous"></script>
微前端在 AEM(CMS)項(xiàng)目的應(yīng)用
我們?cè)凇溉孔V」(<del>已和諧客戶名稱</del>)的 Marketplace 項(xiàng)目當(dāng)中也曾經(jīng)探索過 AEM + React 混合開發(fā)的解決方案,其中就涉及到如何在 AEM 當(dāng)中嵌入 React 組件,甚至將 AEM 組件又強(qiáng)行轉(zhuǎn)化為 React 組件進(jìn)行嵌套。現(xiàn)在回過頭來其實(shí)也算是微前端的一種實(shí)踐:
AEM 僅僅包含網(wǎng)頁(yè)內(nèi)容,不包含 domain 相關(guān)的結(jié)構(gòu)化數(shù)據(jù)。
React 組件被托管在 AEM 組件當(dāng)中,再經(jīng)由 AEM 傳遞給組件所需要的屬性,比如 IDs 或 APIs 的 URL 等等
后端微服務(wù)則包含 domain 結(jié)構(gòu)化數(shù)據(jù),由對(duì)應(yīng)的 React 組件通過 Ajax 進(jìn)行數(shù)據(jù)查詢。
<div id="cms-container-1">
<div id="react-input-container"></div>
<script>
ReactDOM.render(React.createElement(Input, { ...injectProps }), document.getElementById('react-input-container'));
</script>
</div>
<div id="cms-container-2">
<div id="react-button-container"></div>
<script>
ReactDOM.render(React.createElement(Button, {}), document.getElementById('react-button-container'));
</script>
</div>
現(xiàn)成解決方案:Single-SPA “meta framework”
開源的 single-spa
自稱為「元框架」,可以實(shí)現(xiàn)在一個(gè)頁(yè)面將多個(gè)不同的框架整合,甚至在切換的時(shí)候都不需要刷新頁(yè)面(支持 React、Vue、Angular 1、Angular 2、Ember 等等):
Build micro frontends that coexist and can each be written with their own framework.
Use multiple frameworks on the same page without refreshing the page (React, AngularJS, Angular, Ember, or whatever you're using)
Write code using a new framework, without rewriting your existing app
Lazy load code for improved initial load time.
Hot reload entire chunks of your overall application (instead of individual files).
請(qǐng)看示例代碼,所提供的 API 非常簡(jiǎn)單:
import * as singleSpa from 'single-spa';
const appName = 'app1';
const loadingFunction = () => import('./app1/app1.js');
const activityFunction = location => location.hash.startsWith('#/app1');
singleSpa.declareChildApplication(appName, loadingFunction, activityFunction);
singleSpa.start();
# single-spa-examples.js
declareChildApplication('navbar', () => import('./navbar/navbar.app.js'), () => true);
declareChildApplication('home', () => import('./home/home.app.js'), () => location.hash === "" || location.hash === "#");
declareChildApplication('angular1', () => import('./angular1/angular1.app.js'), hashPrefix('/angular1'));
declareChildApplication('react', () => import('./react/react.app.js'), hashPrefix('/react'));
declareChildApplication('angular2', () => import('./angular2/angular2.app.js'), hashPrefix('/angular2'));
declareChildApplication('vue', () => import('src/vue/vue.app.js'), hashPrefix('/vue'));
declareChildApplication('svelte', () => import('src/svelte/svelte.app.js'), hashPrefix('/svelte'));
declareChildApplication('preact', () => import('src/preact/preact.app.js'), hashPrefix('/preact'));
declareChildApplication('iframe-vanilla-js', () => import('src/vanillajs/vanilla.app.js'), hashPrefix('/vanilla'));
declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), hashPrefix('/inferno'));
declareChildApplication('ember', () => loadEmberApp("ember-app", '/build/ember-app/assets/ember-app.js', '/build/ember-app/assets/vendor.js'), hashPrefix('/ember'));
start();
值得一提的是,single-spa 已經(jīng)進(jìn)入到最新一期技術(shù)雷達(dá)的評(píng)估階段。這意味著 single-spa 會(huì)是值得研究一番的技術(shù),以確認(rèn)它將對(duì)你產(chǎn)生何種影響,你應(yīng)該投入一些精力來確定它是否會(huì)對(duì)你所在的組織產(chǎn)生影響。
摘自技術(shù)雷達(dá):
SINGLE-SPA是一個(gè) JavaScript 元框架,它允許我們使用不同的框架構(gòu)建微前端,而這些框架可以共存于單個(gè)應(yīng)用中。一般來說,我們不建議在單個(gè)應(yīng)用中使用多個(gè)框架,但有時(shí)卻不得不這么做。例如當(dāng)你在開發(fā)遺留系統(tǒng)時(shí),你希望使用現(xiàn)有框架的新版本或完全不同的框架來開發(fā)新功能,single-spa 就能派上用場(chǎng)了。鑒于很多 JavaScript框架 都曇花一現(xiàn),我們需要一個(gè)解決方案來應(yīng)對(duì)未來框架的變化,以及在不影響整個(gè)應(yīng)用的前提下進(jìn)行局部嘗試。在這個(gè)方向上,single-spa 是一個(gè)不錯(cuò)的開始。
總結(jié)與思考:微前端的優(yōu)缺點(diǎn)
微前端的優(yōu)點(diǎn)
敏捷性 - 獨(dú)立開發(fā)和更快的部署周期:
開發(fā)團(tuán)隊(duì)可以選擇自己的技術(shù)并及時(shí)更新技術(shù)棧。
一旦完成其中一項(xiàng)就可以部署,而不必等待所有事情完畢。
降低錯(cuò)誤和回歸問題的風(fēng)險(xiǎn),相互之間的依賴性急劇下降。
更簡(jiǎn)單快捷的測(cè)試,每一個(gè)小的變化不必再觸碰整個(gè)應(yīng)用程序。
更快交付客戶價(jià)值,有助于持續(xù)集成、持續(xù)部署以及持續(xù)交付。
維護(hù)和 bugfix 非常簡(jiǎn)單,每個(gè)團(tuán)隊(duì)都熟悉所維護(hù)特定的區(qū)域。
微前端的缺點(diǎn)
開發(fā)與部署環(huán)境分離
本地需要一個(gè)更為復(fù)雜的開發(fā)環(huán)境。
每個(gè) App 模塊有一個(gè)孤立的部署周期。
最終應(yīng)用程序需要在同一個(gè)孤立的環(huán)境中運(yùn)行。
復(fù)雜的集成
需要考慮隔離 JS,避免 CSS 沖突,并考慮按需加載資源
處理數(shù)據(jù)獲取并考慮用戶的初始化加載狀態(tài)
如何有效測(cè)試,微前端模塊之間的 Contract Testing?
第三方模塊重疊
依賴冗余增加了管理的復(fù)雜性
在團(tuán)隊(duì)之間共享公共資源的機(jī)制
影響最終用戶的體驗(yàn)
初始 Loading 時(shí)間可能會(huì)增加
HTML 會(huì)需要服務(wù)器端的渲染
持續(xù)思考…
變幻莫測(cè))前端的技術(shù)選型?
前端 JavaScript 框架工具窮出不窮,過幾個(gè)月就要重寫前端項(xiàng)目?比如最近又出來了聲稱要取代 Webpack(Parcel)和 Yarn(Turbo)的工具。伴隨著前端框架的更新?lián)Q代,如果整個(gè)項(xiàng)目一起升級(jí)/重構(gòu)的話壓力大、風(fēng)險(xiǎn)高,那不如拆分微前端直接支持多 framework,或者同一 framework 的不同版本?`</pre>
在 Mobile/Mobile Web 上的悖論
受限于 Mobile 尺寸大小,單一頁(yè)面所能展現(xiàn)的內(nèi)容本就有限。
既然已經(jīng)分出了不同的子頁(yè)面,那何不如直接 Route 即可?
合理劃分的邊界:DDD(領(lǐng)域驅(qū)動(dòng)開發(fā))
最大的挑戰(zhàn)是搞清楚如何合理拆分應(yīng)用程序。
糟糕的設(shè)計(jì)可能成為開發(fā)和維護(hù)的噩夢(mèng)。
Don't use any of this if you don't need it
Do not use the ideas described here until it is needed, it will make things more complex.
If you are in a big company, those ideas could help you.
軟件架構(gòu)到底在解決什么問題?—— 跨團(tuán)隊(duì)溝通的問題
在正常情況下,每個(gè)團(tuán)隊(duì)擁有開發(fā)和維護(hù)其特性所需的一切,都應(yīng)該有自己的能力來完成自己的特性,并最大限度地減少團(tuán)隊(duì)要求其他部門獲得許可和/或幫助。
當(dāng)引入 library 或 framework 時(shí)的好處是只需要少數(shù)人討論,而不用涉及超過 100 人的決策和他們的各種需求。這樣一場(chǎng)大討論不僅會(huì)耗費(fèi)時(shí)間和精力,而且會(huì)迫使我們采用最不起眼的方法來選擇 library,而不是選擇專門針對(duì)每個(gè) team 的問題領(lǐng)域的方案。
所謂架構(gòu),其實(shí)是解決人的問題;所謂敏捷,其實(shí)是解決溝通的問題;
附:參考資料
本次技術(shù)雷達(dá)「微前端」主題的宣講 Slides 可以在我的博客找到:[「技術(shù)雷達(dá)」之 Micro Frontends:微前端 - 將微服務(wù)理念擴(kuò)展到前端開發(fā) - 呂立青的博客]
image.png
在做 Slides 之前所整理的 XMind 思維導(dǎo)圖:https://www.xmind.net/m/e3dv
本人所整理的所有關(guān)于「微前端」的資料:https://www.diigo.com/user/jimmylv?query=%23microfrontends
https://medium.com/@rchaves/building-microfrontends-part-i-creating-small-apps-710d709b48b7
http://www.agilechamps.com/microservices-to-micro-frontends/
http://allegro.tech/2016/03/Managing-Frontend-in-the-microservices-architecture.html