Vue-微前端从singleSpa到qiankun

837 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战

前言

微前端解决什么问题? 近几年前端的工程化知识开发愈演愈烈,后端解耦,前端聚合,兴起微前端的技术主要是因为SPA项目工程,得到了长足的发展,所有的微前端都是为了解决工程与工程之间的粘合问题即是 从所有收集的部分组成并返回一个无缝的HTML页面

首先先明白一个概念,什么是SPA?什么是MPA?什么是PWA?

  • SPA:单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
  • MPA:多页面应用(multi page web application,MPA),绝大多数的网站都属于多页面应用 见下图:

1.png

  • PWA: 是Progressive Web App的英文缩写, 翻译过来就是渐进式增强WEB应用, 是Google 在2016年提出的概念,2017年落地的web技术。目的就是在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验的渐进式网页应用。(详细看:www.jianshu.com/p/098af61bb… 见下图:

2.png 综合对比之后,我们看到

2016年, PWA在google正式落地,基于 Chromium 的浏览器 Chrome 和 Opera 已经完全支持 PWA 了

随着 iOS 11.3 的发布,iOS正式开始支持PWA
Windows Edge 支持PWA
随着越来越多的浏览器大厂,相继的对PWA做出了支持和优化,想必PWA的时代即将到来。
比较有意思的是VUE-CLI 官方早已经支持构建PWA应用了,大家可以去尝试尝试。@vue/cli-plugin-pwa

前面的都是多余的话,后面重点来了,多个SPA工程如何能够聚合在一个页面里面呢?(该话题仅限在前端的知识范畴里面讨论,不能开历史的倒车)
这个问题在前端领域一直是一个非常重要的话题
看看别人怎么说的吧!

3.png 同样的,一些复杂概念的解释如下:

  • 应用微服务化,即每个前端应用一个独立的服务化前端应用,并配套一套统一的应用管理和启动机制,诸如微前端框架 Single-SPA 或者 mooa 。

  • 微件化,即通过对构建系统的 hack,使不同的前端应用可以使用同一套依赖。它在应用微服务化的基本上,改进了重复加载依赖文件的问题。

  • 微应用化,又可以称之为组合式集成,即通过软件工程的方式,在开发环境对单体应用进行拆分,在构建环境将应用组合在一起构建成一个应用。 即我们采用的方案是Single-SPA来完成的我们的一体化前端大项目方案落地
    什么是 single-spa?
    single-spa是一个在前端应用程序中将多个javascript microfrontend集合在一起的框架。使用单spa构建前端可以带来很多好处,如:

  • 在同一页面上使用多个框架而无需刷新页面 (ReactAngularJSAngularEmber或您使用的任何东西)

  • 独立部署您的微前端。

  • 使用新框架编写代码,而无需重写现有应用程序

  • 延迟加载代码可缩短初始加载时间。

正文

一、single-spa 案例

1、父级项目

1.1 配置single-spa-config.js
// singleSpa.registerApplication:这是注册子项目的方法。参数如下:

// appName: 子项目名称
// applicationOrLoadingFn: 子项目注册函数,用户需要返回 single-spa 的生命周期对象。后面我们会介绍single-spa的生命周期机制
// activityFn: 回调函数入参 location 对象,可以写自定义匹配路由加载规则。
// singleSpa.start:这是启动函数。
// single-spa-config.js

import * as singleSpa from 'single-spa'; //导入single-spa
/**
* runScript:一个promise同步方法。可以代替创建一个script标签,然后加载服务
*/
const runScript = async(url) => {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        const firstScript = document.getElementsByTagName('script')[0];
        firstScript.parentNode.insertBefore(script, firstScript);
    })
};
singleSpa.registerApplication( //注册微前端服务
  'singDemo',
  async() => {
      await runScript('http://127.0.0.1:3000/js/chunk-vendors.js');
      await runScript('http://127.0.0.1:3000/js/app.js');
      return window.singleVue;
  },
  location => (location.pathname.startsWith('/uni-ems') || location.pathname.startsWith('/landingPageWeb/uni-ems')) // 配置微前端模块前缀
);
singleSpa.start(); // 启动
1.2 router路由信息
{
   name: 'uni-ems',
   path: '/uni-ems',
   component:() => import('@/views/portal/Portal'),// 加载器 加载模块
},

2、子项目

2.1 配置main.js
const vueOptions = {
    el: '#uni-ems',
    router,
    store,
    render: h => h(App)
}

// 判断是否为single-spa模式,如果不是,就独立渲染
if (!window.singleSpaNavigate) { // 如果不是single-spa模式
    delete vueOptions.el;
    new Vue(vueOptions).$mount('#app')
}

// singleSpaVue包装一个vue微前端服务对象
const vueLifecycles = singleSpaVue({
    Vue,
    appOptions: vueOptions
})

// 导出生命周期对象
export const bootstrap = vueLifecycles.bootstrap // 启动时
export const mount = vueLifecycles.mount // 挂载时
export const unmount = vueLifecycles.unmount // 卸载时

export default vueLifecycles
2.2 配置vue.config.js

重点配置: publicPathoutputdevServer

publicPath: '//localhost:3000',
pluginOptions: {
    "style-resources-loader": {
        preProcessor: "less",
        patterns: [path.resolve(__dirname, "./src/style/base.less")]
    }
},
configureWebpack: {
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "windows.jQuery": "jquery"
        })
    ],
    devtool: 'none', // 不打包sourcemap
    output: {
        library: "singleVue", // 导出名称
        libraryTarget: "window", //挂载目标
    }
},
devServer: {
    contentBase: './',
    port: 3000, // 端口
    compress: true,
}

二、微前端的进阶 qiankun

qiankun 是由蚂蚁金服推出的比较成熟的微前端框架,基于 single-spa 进行二次开发,用于将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。

1、父级项目

1.1 安装 qiankun

$ yarn add qiankun # 或者 npm i qiankun -S

1.2. 父项目 基座
<div class="main-app">
    <header class="main-app-header">
        <h1>QianKun</h1>
    </header>
    <div class="main-app-main">
        <ul class="main-app-menu">
            <li><a href="/vue">vue子项目</a></li>
        </ul>
        <main id="sub-app-container">
            <div id="vue"></div>
        </main>
    </div>
</div>
1.3. 在主应用中注册子应用
import { registerMicroApps, setDefaultMountApp, start } from 'qiankun';

/**
 *  注册子应用
 */
registerMicroApps(
    [
        {
            name: 'vueApp',
            entry: '//localhost:10000',
            container: '#vue',
            activeRule: '/vue',//浏览器url变化 插入到指定的container中 ,依次调用微应用的生命周期钩子
        },
    ],
);
/**
 * 设置默认进入的子应用
 */
setDefaultMountApp('/vue');
/**
 * 启动应用
 */
start();

当子应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 方法返回 true 的子应用对应的 render 方法就会被调用,同时依次调用子应用暴露出的生命周期钩子

2、子项目

子应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

2.1 导出相应的生命周期钩子

子应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap 、 mount 、 unmount 三个生命周期钩子,以供主应用在适当的时机调用。

let router = null;
let instance = null;

function render(props = {}) {
    const { container } = props;
    router = new VueRouter({
        base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
        mode: 'history',
        routes,
    });

    instance = new Vue({
        router,
        render: h => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
}

if (!window.__POWERED_BY_QIANKUN__) {
    render();
}
//子组件协议
export async function bootstrap(props){
    console.log(props);
}

export async function mount(props){
    render(props)//装载
}
export async function unmount(props){
    console.log(props);
    instance.$destroy(); //卸载
    instance.$el.innerHTML = '';
    instance = null;
    router = null;
}
2.2 配置子应用的打包工具

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别子应用暴露出来的一些信息,子应用的打包工具需要增加如下配置:

module.exports = {
    configureWebpack:{
        output:{
            library: 'vueApp',
            libraryTarget:'umd'
        }
    },
    devServer: {
        port: 10000,
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    }
};

以上就是项目中用到的微前端的解决方案,谢谢观看!