微前端框架single-spa源码解析、项目实战、手写简版single-spa

4,547 阅读3分钟

前言


阅读本文前,需要您对single-spa有简单的了解。

single-spa是一个非常棒的微前端框架,我们都知道另外一款非常优秀的前端框架qiankun就是基于single-spa实现的,在其基础上,增加了沙箱机制、css Model样式隔离。

本文分为三部分:

  • single-spa源码解析
  • 项目实战
  • 手写简版single-spa

一、single-spa源码解析


single-spa源码不多,但是写的非常好,感觉把异步用到了极致。非常推荐大家阅读一下~

源码地址

这是我拷贝下来源码,添加了注释,并用中文标注了错误提示,可以帮助理解源码:仓库地址

git clone xxx
npm install
npm run dev  # 默认启动3000端口

此时,就可以在本地debugger源码了。

目录结构

├─src
|  ├─single-spa.js                  # 入口
|  ├─start.js                       # start方法
|  ├─utils                          # 工具
|  |   ├─assign.js
|  |   ├─find.js
|  |   └runtime-environment.js
|  ├─navigation                      # 导航相关
|  |     ├─navigation-events.js      # 导航事件
|  |     └reroute.js                 # 核心方法,重新路由函数
|  ├─lifecycles                      # 生命周期
|  |     ├─bootstrap.js
|  |     ├─lifecycle.helpers.js
|  |     ├─load.js
|  |     ├─mount.js
|  |     ├─prop.helpers.js
|  |     ├─unload.js
|  |     ├─unmount.js
|  |     └update.js
|  ├─applications                     # 应用程序相关
|  |      ├─app-errors.js
|  |      ├─app.helpers.js            # 状态管理
|  |      ├─apps.js                   # 注册应用等
|  |      └timeouts.js

我们主要关注avigation、lifecycles、applications这三个文件夹即可,是整个源码的核心。

文字不如图示

  • single-spa是一个状态机,维护着应用的状态,他的主要工作是路由劫持和应用加载: 主要分为四个过程:加载、激活启用、挂载、卸载销毁。

image.png

  • 核心方法脑图

single-spa核心方法就三个registerApplication,reroute,start,脑图描述了这三个方法都具体做了什么:核心方法逻辑图示

image.png

  • 初始化加载流程

image.png

  • 应用启动和挂载流程

image.png

二、项目实战


项目是用vue来做的,主应用代码地址子应用代码地址

项目启动方式:

git clone xxx
yarn install
npm run serve

1.主应用做了什么

主应用的主要工作就是拦截路由,加载子应用。两个核心方法registerApplicationstart

npm install single-spa
// main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js'

// 导入注册应用,和启动应用的方法
import { registerApplication, start } from 'single-spa';

// 手动加载子应用打包出来的文件
const loadScript = async (url) => {
  await new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script)
  });
}
registerApplication(
  'singleVue',
  async () => {
    // 加载模块
    await loadScript('http://localhost:10000/js/chunk-vendors.js'); // vue打包出来的公共模块
    await loadScript('http://localhost:10000/js/app.js'); // vue打包出来的应用核心
    // 生命周期  singleVue上有bootstrap,mount,unmount方法,打包的时候打到singleVue上去的
    return window.singleVue
  },
  location => location.pathname.startsWith('/vue') // 激活标识
)
start();

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

vue通过umd规范打包完成后,有两个重要模块,chunk-vendors.js是打包出来的公共模块,app.js是打包出来的应用核心模块。把这两个模块手动加载进来,待应用加载时就可以显示了。

这也暴露了single-spa的一个短板,有多少文件需要引入是不确定的,手动加载文件不方便,如果有很多文件,总不能用这种方式一个一个加载吧。

window.singleVue是啥?是一个全局变量,子应用打包时用umd格式进行打包,会将singleVue挂载到全局。singleVue上都有啥呢?有我们暴露的一些方法,如:bootstrap,mount,unmount,这些恰巧是single-spa需要的方法。

// app.vue

<template>
  <div id="app">
    <router-link to="/vue">加载子应用</router-link> 
    <!-- 子应用挂载在这里 -->
    <div id="vue"></div>
</div>
</template>

当我们加载子应用时,让子应用挂载在idvue这里。

2.子应用需要做什么

  • 暴露single-spa需要的bootstrap,mount,unmount方法,single-spa是协议接入,这几个方法都是必须要提供的,而且必须是异步的。
  • 进行打包,并将singleVue挂载到window上,以便父应用使用。
  • 兼容不使用single-spa时,子应用也可以独立运行。

先安装依赖:

npm install single-spa-react

子应用是vue应用用single-spa-vue,是react应用用single-spa-react

// main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js'

Vue.config.productionTip = false

import singleSpaVue from 'single-spa-vue';

const appOptions = {
  el: '#vue', // 挂在父应用id为vue的标签位置
  router,
  render: h => h(App)
}

// 在非子应用中正常挂载应用
if (!window.singleSpaNavigate) {
  delete appOptions.el;
  new Vue(appOptions).$mount('#app');
}

const vueLifeCycle = singleSpaVue({
  Vue,
  appOptions
});

// 协议接入
// 子应用必须导出 以下生命周期 bootstrap、mount、unmount
// 子应用还需要打包成一个个的lab给父应用使用
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;

export default vueLifeCycle;
// vue.config.js

const path = require('path');
const resolvePath = dir => path.resolve(__dirname, dir);

module.exports = {
    configureWebpack: {
        output: {
            library: 'singleVue',
            libraryTarget: 'umd' // umd打包,会把bootstrap,mount,unmount都打包到singleVue上,挂在window上
        },
        devServer:{
            port:10000
        }
    },
    chainWebpack: config => {
        // 配置解析别名
        config.resolve.alias
            .set('@', resolvePath('src'))
            .set('@components', resolvePath('src/components'))
            .set('@views', resolvePath('src/views'))
    }
}

三、手写简版single-spa


项目地址:简版single-spa.

简版single-spa麻雀虽小,但五脏俱全。基本还原了源码的核心逻辑,阅读上更友好~

参考资料