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的普遍应用带来了更多的诉求和灵感激发,将来,咱们还会结合业务特色去探索微前端的其余可能性,让架构赋能业务,为用户带来价值。