「微前端」- 將微服務(wù)理念擴(kuò)展到前端開發(fā)(實(shí)踐篇)

前言與大綱

本文分為理論和實(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)行加載。

image.png

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)化建議

image.png
  • 使用 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)我們依然可以將常用的依賴比如 lodashmoment.js等公共庫(kù),或者跨多個(gè)團(tuán)隊(duì)共同使用的 reactreact-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”

image.png

開源的 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)生影響。

image.png

摘自技術(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ù)思考…

image.png
  • 變幻莫測(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容