前言
自从微前端框架micro-app开源后,很多小伙伴都非常感兴趣,问我是如何实现的,但这并不是几句话可以说明白的。为了讲清楚其中的原理,我会从零开始实现一个简易的微前端框架,它的核心功能包括:渲染、JS沙箱、样式隔离、数据通信。由于内容太多,会根据功能分成四篇文章进行讲解,这是系列文章的第一篇:渲染篇。
通过这些文章,你可以了解微前端框架的具体原理和实现方式,这在你以后使用微前端或者自己写一套微前端框架时会有很大的帮助。如果这篇文章对你有帮助,欢迎点赞留言。
相关推荐
micro-app源码地址:https://github.com/micro-zoe/micro-app
整体架构
和micro-app一样,我们的简易微前端框架设计思路是像使用iframe一样简单,而又可以避免iframe存在的问题,其使用方式如下:
最终效果也有点类似,整个微前端应用都被封装在自定义标签micro-app中,渲染后效果如下图:
所以我们整体架构思路为:CustomElement + HTMLEntry。
HTMLEntry就是以html文件作为入口地址进行渲染,入上图中的http://localhost:3000/
就是一个html地址。
概念图:
前置工作
在正式开始之前,我们需要搭建一个开发环境,创建一个代码仓库simple-micro-app
。
目录结构
代码仓库主要分为src主目录和examples案例目录,vue2为基座应用,react17为子应用,两个项目都是使用官方脚手架创建的,构建工具使用rollup。
两个应用页面分别如下图:
基座应用 – vue2
子应用 – react17
在vue2项目中,配置resolve.alias
,将simple-micro-app指向src目录的index.js。
// vue.config.js
...
chainWebpack: config => {
config.resolve.alias
.set("simple-micro-app", path.join(__dirname, '../../src/index.js'))
},
在react17的webpack-dev-server中配置静态资源支持跨域访问。
// config/webpackDevServer.config.js
...
headers: {
'Access-Control-Allow-Origin': '*',
},
正式开始
为了讲的更加明白,我们不会直接贴出已经完成的代码,而是从无到有,一步步实现整个过程,这样才能更加清晰,容易理解。
创建容器
微前端的渲染是将子应用的js、css等静态资源加载到基座应用中执行,所以基座应用和子应用本质是同一个页面。这不同于iframe,iframe则是创建一个新的窗口,由于每次加载都要初始化整个窗口信息,所以iframe的性能不高。
如同每个前端框架在渲染时都要指定一个根元素,微前端渲染时也需要指定一个根元素作为容器,这个根元素可以是一个div或其它元素。
这里我们使用的是通过customElements创建的自定义元素,因为它不仅提供一个元素容器,还自带了生命周期函数,我们可以在这些钩子函数中进行加载渲染等操作,从而简化步骤。
// /src/element.js
// 自定义元素
class MyElement extends HTMLElement {
// 声明需要监听的属性名,只有这些属性变化时才会触发attributeChangedCallback
static get observedAttributes () {
return ['name', 'url']
}
constructor() {
super();
}
connectedCallback() {
// 元素被插入到DOM时执行,此时去加载子应用的静态资源并渲染
console.log('micro-app is connected')
}
disconnectedCallback () {
// 元素从DOM中删除时执行,此时进行一些卸载操作
console.log('micro-app has disconnected')
}
attributeChangedCallback (attr, oldVal, newVal) {
// 元素属性发生变化时执行,可以获取name、url等属性的值
console.log(`attribute ${
attrName}: ${
newVal}`)
}
}
/**
* 注册元素
* 注册后,就可以像普通元素一样使用micro-app,当micro-app元素被插入或删除DOM时即可触发相应的生命周期函数。
*/
window.customElements.define('micro-app', MyElement)
micro-app
元素可能存在重复定义的情况,所以我们加一层判断,并放入函数中。
// /src/element.js
export function defineElement () {
// 如果已经定义过,则忽略
if (!window.customElements.get('micro-app')) {
window.customElements.define('micro-app', MyElement)
}
}
在/src/index.js
中定义默认对象SimpleMicroApp
,引入并执行defineElement
函数。
// /src/index.js
import {
defineElement } from './element'
const SimpleMicroApp = {
start () {
defineElement()
}
}
export default SimpleMicroApp
引入simple-micro-app
在vue2项目的main.js中引入simple-micro-app,执行start函数进行初始化。
// vue2/src/main.js
import SimpleMicroApp from 'simple-micro-app'
SimpleMicroApp.start()
然后就可以在vue2项目中的任何位置使用micro-app标签。
<!-- page1.vue -->
<template>
<div>
<micro-app name='app' url='http://localhost:3001/'></micro-app>
</div>
</template>
插入micro-app标签后,就可以看到控制台打印的钩子信息。
以上我们就完成了容器元素的初始化,子应用的所有元素都会放入到这个容器中。接下来我们就需要完成子应用的静态资源加载及渲染。
创建微应用实例
很显然,初始化的操作要放在connectedCallback
中执行。我们声明一个类,它的每一个实例都对应一个微应用,用于控制微应用的资源加载、渲染、卸载等。
// /src/app.js
// 创建微应用
export default class CreateApp {
constructor () {
}