Open yshaojun opened 4 years ago
微前端(或者叫前端微服务)以技术无关、独立部署的特点,成为前端重点关注的技术方向之一。虽然业内对微前端基本要点(比如服务加载、隔离、通信、路由等)的认识趋于一致,但是仍然没有一套完整且公认的解决方案。
本文是读阿里云 alicloud-console-os 的感受,看它是分别选择怎样的方案解决上述问题的。
直接的想法是以 js 为入口,就像个人网站接百度推广那样,但问题是不好处理 css 和 thunk js;以 html 为入口,又需要对 html 进行解析,做起来也比较麻烦。
console-os 使用 json 文件作为入口,这个文件里包含了 css、js 等资源 url 列表,配合 jsonp 的引入方式(下文介绍),显得简单可靠。
之前想到的加载办法是 fetch 到 js 代码,然后用 eval 或者 new Function 执行;fetch 到 css,用 innerHtml 插入到 DOM 里,问题是如果 js 加载其他 chunk js 时,在加载方式又比较混乱。
fetch
eval
new Function
innerHtml
console-os 使用 jsonp 的方式,在 head 里插入 script、link 标签,配合子应用打包时最外层包裹的函数:
head
script
link
window.__CONSOLE_OS_GLOBAL_HOOK__( id, function(require, module, exports, { window, location, history, document }){ // 子应用代码 } )
应用就加载即执行了。
在上述的 __CONSOLE_OS_GLOBAL_HOOK__ 函数中,传入了用 Proxy 劫持的 {window,location,history,document},以此实现了子应用 js 隔离。这个地方有个有趣的实现,相比 qiankun 的 “快照”,console-os 是为每个子应用创建一个隐藏的 iframe,然后把这个 iframe 上的全局变量比如 history 传给它!因此子应用的全局操作,一部分就直接落到各自的 iframe 上。
__CONSOLE_OS_GLOBAL_HOOK__
Proxy
{window,location,history,document}
iframe
history
官方 example 里没有实现 css 隔离,不过从示例看,子应用都被限制在各自以 id 为名称的自定义 html 标签里,加上子应用使用同一个 Document 对象,因此很容易套用常规的组件局部样式方案,比如选择器都加最外层 class、自定义属性 data-[hash] 等。
Document
console-os 的父子应用并没有完完全全的隔离开,比如它们仍然共用相同的 Window、Document(当然对子应用,以 Proxy 对部分属性做了拦截),所以它们的通信没有天然的障碍。官方在 events 中,直接使用了 EventEmitter。
Window
EventEmitter
console-os 中对子应用的路由进行了隔离,但是目前并未实现完整的路由方案。不过在 console-os 中实现应该不难,宿主获取路由信息,通过事件向子应用广播,子应用以 id 为频道订阅自己的,然后做对应的路由动作;子应用切换路由时,以同样的方式通知宿主做路由变更,应该就行了。
其他微前端框架中,都是以类似配置或 api 的形式引入子应用,在 console-os 中,提供了组件形式:
function App() { return ( <div className="App"> {/* 渲染应用 */} <div className="react"> <Application id="os-example" manifest="http://localhost:8081/os-example.manifest.json" /> </div> </div> ) }
这种方式利于在已有项目中灵活的引入微服务。
微前端(或者叫前端微服务)以技术无关、独立部署的特点,成为前端重点关注的技术方向之一。虽然业内对微前端基本要点(比如服务加载、隔离、通信、路由等)的认识趋于一致,但是仍然没有一套完整且公认的解决方案。
本文是读阿里云 alicloud-console-os 的感受,看它是分别选择怎样的方案解决上述问题的。
入口
直接的想法是以 js 为入口,就像个人网站接百度推广那样,但问题是不好处理 css 和 thunk js;以 html 为入口,又需要对 html 进行解析,做起来也比较麻烦。
console-os 使用 json 文件作为入口,这个文件里包含了 css、js 等资源 url 列表,配合 jsonp 的引入方式(下文介绍),显得简单可靠。
加载
之前想到的加载办法是
fetch
到 js 代码,然后用eval
或者new Function
执行;fetch
到 css,用innerHtml
插入到 DOM 里,问题是如果 js 加载其他 chunk js 时,在加载方式又比较混乱。console-os 使用 jsonp 的方式,在
head
里插入script
、link
标签,配合子应用打包时最外层包裹的函数:应用就加载即执行了。
隔离
在上述的
__CONSOLE_OS_GLOBAL_HOOK__
函数中,传入了用Proxy
劫持的{window,location,history,document}
,以此实现了子应用 js 隔离。这个地方有个有趣的实现,相比 qiankun 的 “快照”,console-os 是为每个子应用创建一个隐藏的iframe
,然后把这个iframe
上的全局变量比如history
传给它!因此子应用的全局操作,一部分就直接落到各自的iframe
上。官方 example 里没有实现 css 隔离,不过从示例看,子应用都被限制在各自以 id 为名称的自定义 html 标签里,加上子应用使用同一个
Document
对象,因此很容易套用常规的组件局部样式方案,比如选择器都加最外层 class、自定义属性 data-[hash] 等。通信
console-os 的父子应用并没有完完全全的隔离开,比如它们仍然共用相同的
Window
、Document
(当然对子应用,以Proxy
对部分属性做了拦截),所以它们的通信没有天然的障碍。官方在 events 中,直接使用了EventEmitter
。路由
console-os 中对子应用的路由进行了隔离,但是目前并未实现完整的路由方案。不过在 console-os 中实现应该不难,宿主获取路由信息,通过事件向子应用广播,子应用以 id 为频道订阅自己的,然后做对应的路由动作;子应用切换路由时,以同样的方式通知宿主做路由变更,应该就行了。
微组件
其他微前端框架中,都是以类似配置或 api 的形式引入子应用,在 console-os 中,提供了组件形式:
这种方式利于在已有项目中灵活的引入微服务。