上一篇利用tapable实现一个微前端架构实现了一个基础的微服务架构,但是还没法加载拆分的子应用,这一篇在此基础上,继续为基础架构添加能力
- 实现Plugin
- 加载子应用
一、插件设计
这里的实现逻辑和Webpack插件的实现逻辑基本是一样的,如果你了解Webpack的话,会很快明白这个实现原理
实现要点:
- 1、通过给microApp传入plugin数组参数,接收插件
- 2、插件中要可以传入初始化参数
- 3、插件中要可以获取app实例和生命周期勾子
1、入口配置插件
demo.js
给微服务引入插件,给插件传入参数{type: 1}
import Plugin1 from './plugin1.js'
// 对不存在的变量有疑问,可以查看上一篇文章
const app = microApp({
config,
mountDOM,
history,
plugin: [
new Plugin1({
type: 1 // 传递参数,在插件内部使用
})
]
})
2、插件的开发要求
plugin1.js
插件实现,声明一个类且包含call方法,在微服务读取配置后,提前执行加载js的任务
class Plugin1 {
constructor(options){
this.options = options;
}
options: {
type: 1
},
// 必须包含该方法,并且接收两个参数,hooks和app实例
call({ hooks, instance }){
// 在特定的钩子,执行对应的插件逻辑
hooks.afterConfig.tap("do something", () => {
console.log("load js prefetch")
})
}
}
3、在微服务中实现插件的能力
在microApp接收参数,并调用参数能力
const microApp = ({plugins=[], ...config}) => {
const hooks = new Hooks()
const instance = {
register,
hooks,
refresh
}
// 注册传递进来的插件
plugins.forEach(plugin => {
plugin.call({hooks, instance})
})
const config = _config;
// 加载后,执行声明周期事件
hooks.afterConfig.call()
}
插件的实现很简单,在微服务开发的hooks注册事件就可以了,然后等待微服务执行到节点就会执行插件的Call方法。
二、加载子应用
上篇分享了如何构建一个微服务,然后本地演示了挂载子应用,放在一个文件目录中测试,这肯定是不符合微服务的使用场景的。
我们需要将微服务放在不同的项目中单独开发,然后和微服务交互。
那么如何实现这一能力呢?可选择的方案很多,这里我们选择通过script
标签插入构建的子应用
1、实现一个简单的函数,加载js文件
这里增加了简单的去重逻辑,防止同一个js多次加载,后面可以实现更规范的加载方式。
const head = document.head || document.getElementsByTagName('head')[0]
export function loadResource(path){
// 判断path是否有值
// 判断path是否被加载过
const hasScript = Array.from(document.getElementsByTagName("script")).find(item => {
return path === item.src;
})
if(!hasScript){
const el = document.createElement("script");
el.async = true;
el.src = path;
head.insertBefore(el, head.lastChild)
}
}
这里仅仅实现了js的加载,当然还有css和其他的文件,都可以实现
2、单独启动一个子应用,这里我们用React实现,然后使用webpack打包构建JS
目录结构如下,只有一个文件:index.js,注册一个app,通过共享的window._app来读取微服务
在微服务中实现
// 我们在上一篇demo.js文件将app共享出来
const app = microApp({
...
})
window._app = app
单独的子应用
import React from 'react'
import ReactDOM from 'react-dom'
export const Component = () => {
return <div>this is a react component</div>
}
if(window._app){
window._app.register(
"demo1",
mountDOM => {
console.log("demo1 mountDOM", mountDOM)
ReactDOM.render(<Component />, mountDOM);
},
unmountDOM => {
ReactDOM.unmountComponentAtNode(unmountDOM)
}
)
}else{
ReactDOM.render(<Component />, mountDOM);
}
子应用micro-app1
的目录结构
构建生成/dist/app1.js
3、启动一个本地服务,将服务部署
在上篇测试微服务的项目中,用webpack启动devServer
devServer: {
static: 'dist',
port: 9000,
historyApiFallback: true
}
访问APP1的地址为:http://localhost:9000/app1.js
4、微服务读取子应用,并加载子应用
1、修改app的config
const config = {
home: {...},
demo: {...},
app1: {
prefix: '/app1/',
href: '/app1/',
// 增加字段,写明子应用地址,如果多个可以改成数组
file: 'http://localhost:9000/app1.js'
},
demo2: {
prefix: '/demo2/',
href: '/demo2/'
}
}
这样app1子应用就可以,加载远程file地址的资源
2、修改加载子应用的函数
现在子应用资源变成了通过远程服务加载,我们要改造下enterProject
这个函数,判断下app1
中的子应用是否注册,如果没有注册就加载js后,刷新应用再次进入
const enterProject = () => {
if(!projectRegisterConfig){
const { file } = configProject;
console.log("file", file)
if(!file) return result;
try{
await loadResource(file)
// 调用加载完资源的生命周期函数
// hooks.after.call(projectKey)
}catch(err){
console.error(err.message)
return result
}
}
}
我们可以看到,此时app1应用作为远程加载的子应用,已经渲染成功。
后续完善一下这个微服务,会将代码开放出来,欢迎提意见。
原文链接:https://juejin.cn/post/7217991690703077435 作者:前端中后台