实践微前端服务接入插件和加载子应用能力

上一篇利用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 作者:前端中后台

(0)
上一篇 2023年4月4日 上午10:05
下一篇 2023年4月4日 上午10:15

相关推荐

发表回复

登录后才能评论