ThoughtWorks在幾年前提出了微前端的概念,其核心理念是將前端單體應用在開發階段拆分紅多個獨立的工程,並在運行階段組合成完整的應用。不只解耦了視圖和代碼,使得應用能夠容納多種技術棧,還進一步解耦了流程和團隊,極大地提升了團隊的自主性和協做效率。javascript
智聯招聘的大前端架構Ada自己就能夠看做一個基於路由的微前端架構,圍繞URL的研發方式可以靈活地實現頁面級別的解耦。而在在視圖區域級別,Ada也引入了專門的微前端實現機制——Widget。html
Widget是一種能夠獨立開發和發佈的視圖區域,它運行於宿主頁面中,而且可以和宿主頁面進行雙向通訊。前端
在設計Widget架構時,咱們考慮到Ada的多框架支持能力,應當讓Widget在使用時不受框架的限制,也就是說,使用Knockout.js開發的Widget能夠運行在Vue.js的頁面中,反之亦然,這就決定了Widget的最終形態必須是框架無關的Plain JavaScript。出於一樣的考量,通訊機制也不該該受框架所限,而應該制定屬於Widget的通訊API規範。vue
總結一下,Widget的設計目標是:java
爲了統一開發體驗,Ada從開發、調試、發佈和運行都爲Widget進行了專門的支持。web
在開發階段,理論上任何可以編譯成Plain JavaScript的框架均可以使用,Ada在Vue.js等腳手架中內置了對Widget的支持,開發者可使用熟悉的技術開發Widget,也能夠像調試頁面同樣預覽和調試Widget。segmentfault
咱們在腳手架內核中爲Widget單獨設計了Webpack配置,使得基於各類框架開發的Widget都能輸出成一個獨立的JavaScript Bundle(樣式也會構建到同一個Bundle中),藉此來保證輸出的文件符合Widget規範。安全
就像Ada體系裏的其餘工件同樣,每一個Widget都有一個惟一URN,宿主頁面經過該URN來引用Widget,從而和Widget的JavaScript Bundle解耦。在發佈階段,Ada會爲URN關聯最新的輸出清單,這樣一來,Widget不但能夠脫離宿主頁面獨立發佈,還能進一步實現灰度發佈和A/B實驗。架構
Widget的生命週期包括四個階段:註冊、初始化、運行和銷燬,各階段之間的轉換是由Widget SDK來負責調度的。app
宿主頁面使用<script>標籤加載Widget URN時,Ada Server會負責調度並返回正確的JavaScript Bundle,加載完畢後,就會向Widget SDK註冊本身。
註冊完畢以後,宿主頁面就能夠調用Widget SDK的init方法,並傳遞Widget名稱和父元素DOM,來決定Widget的初始化時機和位置。宿主頁面還能夠初始化同一個Widget的多個實例,並和它們分別進行通訊。
Widget的消息通訊機制借用了Web Worker的API規範,宿主頁面能夠經過Widget SDK的postMessage方法向指定Widget發送消息,同時經過onmessage方法監聽Widget發來的消息。反過來,Widget也能夠用一樣的方式和宿主頁面通訊。
當宿主頁面須要銷燬組件時,能夠調用Widget SDK的destory方法,後者會指示Widge銷燬視圖、清理存儲,而後再將Widget移出事件中心。
Widget本質上是一個規範化的Class,可使用Plain JavaScript,也能夠融入任何框架,好比藉助Vue.js來開發Widget的代碼多是這樣的:
import Vue from 'vue' import Widget from './Widget.vue' // 具體業務代碼 class Widget { constructor ({ el }) { this.el = el // 當前 Widget 的父 DOM this.mount() } mount () { const app = new Vue({ render: h => h(Widget) }) app.$mount(this.el) } } export default Widget
宿主頁面經過<script>標籤導入Widget URN,而後初始化便可:
const scriptElement = document.createElement('script') scriptElement.type = 'text/javascript' scriptElement.async = true scriptElement.src = YOUR_WIDGET_URN scriptElement.onload = () => { window.zpWidget.init(this.widgetName, { el: YOUR_WIDGET_PARENT_DOM }) } document.body.appendChild(scriptElement)
爲了貼合現代Web框架組件化的研發習慣,咱們爲Vue.js、Knockout.js和Weex提供了Widget組件,藉此來簡化Widget加載和初始化步驟。例如在Vue.js中,能夠這樣加載一個Widget:
<template> <!-- 參數解釋:urn 能夠是上線後的 widget 地址也能夠當前項目的相對路徑, --> <widget url="/widgets/YOUR_WIDGET_NAME" /> </template> <script> import Widget from '@zpfe/widget-components/web' export default { name: 'YOUR_COMPONENT_NAME', components: { Widget } } </script>
初始化Widget以後,就能夠與宿主頁面進行雙向通訊了,例如:
// Widget 內部 window.zpWidget.postMessage({ widgetName: 'timeNow', eventName: 'click' }, new Date()) // 宿主頁面 window.zpWidget.onmessage({ widgetName: 'timeNow', eventName: 'click' }, (time) => { console.log(`當前時間是:${time}`) })
目前集團內已累計發佈了100餘個Widget,各條產品線都招到了Widget能發揮做用的場景,好比:
發佈於2019年初的Widget機制,是咱們在微前端領域的第一次嘗試,成效使人滿意。集團內對Widget的普遍應用帶來了更多的訴求和靈感激發,將來,咱們還會結合業務特色去探索微前端的其餘可能性,讓架構賦能業務,爲用戶帶來價值。