Single-Spa微前端实践

背景

在前端,每每由一个前端团队建立并维护一个 Web 应用程序,使用 REST API 从后端服务获取数据。这种方式若是作得好的话,它可以提供优秀的用户体验。但主要的缺点是单页面应用(SPA)不能很好地扩展和部署。中后台应用因为其应用生命周期长 (动辄 3+ 年) ,因为参与的人员、团队的增多、变迁,等特色从一个普通应用演变成一个巨石应用 ( Frontend Monolith ) 后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤为常见。javascript

在一个大公司里,单前端团队可能成为一个发展瓶颈。前端

什么是微前端架构?

首先,必须先了解什么是微前端架构。 微前端架构是一种相似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。vue

Single spa什么?

single spa是一个javascript库,容许许多小应用程序在一个页面应用程序中共存。这个https://single-spa.surge.sh/ 网站是一个演示应用程序,展现了什么单一应用。 spa的理念是让独立的、独立的应用程序组成一个完整的页面。单spa并无长期依赖于单个框架和每一个特性,而是帮助您在开发新框架时采用它们。 简单来讲就是一个万能的粘合剂,使用这个库可让你的应用可使用多个不一样的技术栈(vue、react、angular等等)进行同步开发,最后使用一个公用的路由便可实现完美切换。 固然了,也可使用同样的技术栈,分不一样的团队进行开发,只须要最后使用这个库将其整合在一块儿,设置不用的路由名称便可。java

如何开始一个Single spa 项建立项目parent

第一步、新建项目

修改app.vue 文件 添加node

<div id=“vue”></div> 容器react

引入安装包文件webpack

npm install single-spa --save –dios

Main.js 引入配置文件git

import './single-spa-config.js’github

  1. 建立子项目vue-child

引入装包文件

npm install single-spa-vue --save -d

第二步、 父组件single-spa-config 文件

首先了解一下singleSpa 主要的API

  • registerApplication 定义:

registerApplication is the most important api your root config will use. Use this function to register any application within single-spa. --- 注册子项目的方法

appName: 子项目名称

applicationOrLoadingFn: 子项目注册函数,用户须要返回 single-spa 的生命周期对象。后面咱们会介绍single-spa的生命周期机制

activityFn: 回调函数入参 location 对象,能够写自定义匹配路由加载规则。 调用方法

singleSpa.registerApplication('appName’,
 () => System.import('appName’), 
location => location.pathname.startsWith('appName’)
)


复制代码
  • singleSpa.start:启动函数

修改父容器的single-spa-config 文件

// single-spa-config.js
  import * as singleSpa from 'single-spa'; //导入single-spa
  import axios from 'axios’
  
  /*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( //注册微前端服务
      'singleDemo', 
  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('/vue-antd') // 配置微前端模块前缀
  );
  singleSpa.start(); // 启动

复制代码

第三步、注册子组件

修改父项目的main.js

import Vue from 'vue'
  import App from './App.vue'
  import singleSpaVue from "single-spa-vue"
  
  const vueOptions = {
  el: "#vue",
  render: h => h(App),
  }
  // new Vue().$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;

复制代码

single-spa生命周期

生命周期函数共有4个:bootstrap、mount、unmount、update。 生命周期能够传入 返回Promise的函数也能够传入 返回Promise函数的数组。

export default { 
    // app启动 
    Bootstrap: [() => Promise.resolve()],
     // app挂载 
    Mount: [() => Promise.resolve()],
     // app卸载 
    Unmount: [() => Promise.resolve()], 
    // service更新,只有service才可用
     update: [() => Promise.resolve()]
  }

复制代码

第五步、自动加载 bundle和chunk.vendor

在上面父项目加载子项目的代码中,咱们能够看到。咱们要注册一个子服务,须要一次性加载2个JS文件。若是须要加载的JS更多,甚至生产环境的 bundle 有惟一hash, 那咱们还能写死文件名和列表吗? 咱们的实现思路,就是让子项目使用 stats-webpack-plugin 插件,每次打包后都输出一个 只包含重要信息的manifest.json文件。父项目先ajax 请求 这个json文件,从中读取出须要加载的js目录,而后同步加载。

子项目添加vue.config.js ,并进行如下修改,生成manifest.json 文件

const StatsPlugin = require('stats-webpack-plugin');
  module.exports = {
  ….
    output: {
    library: "singleVue", // 导出名称
    libraryTarget: "window", //挂载目标
    },
    /**** 添加开头 ****/
    plugins: [
    new StatsPlugin('manifest.json', {
    chunkModules: false,
    entrypoints: true,
    source: false,
    chunks: false,
    modules: false,
    assets: false,
    children: false,
    exclude: [/node_modules/]
    })
    ]
    /**** 添加结尾 ****/
  ….
  };
  

复制代码

固然,父项目中的单runScript已经没法支持使用了,写个getManifest方法,处理一下。

const getManifest = (url, bundle) => new Promise(async (resolve) => {
    const { data } = await axios.get(url);
    // eslint-disable-next-line no-console
    const { entrypoints, publicPath } = data;
    const assets = entrypoints[bundle].assets;
    for (let i = 0; i < assets.length; i++) {
    await runScript(publicPath + assets[i]).then(() => {
    if (i === assets.length - 1) {
    	resolve()
    }
    })
    }
  });

复制代码

咱们首先ajax到 manifest.json 文件,解构出里面的 entrypoints publicPath字段,遍历出真实的js路径,而后按照顺序加载。

async () => {
  let singleVue = null;
  await getManifest('http://127.0.0.1:3000/manifest.json', 'app').then(() => {
  	singleVue = window.singleVue;
  });
  return singleVue; 
},

复制代码

第六步、添加另外一个新项目

Vue-child 引入 ant UI Vue-child-two 引入 element

一样可使用多个不一样的技术栈(vue、react、angular等等)进行同步开发

Single spa 优势

  • 敏捷性 - 独立开发和更快的部署周期: 开发团队能够选择本身的技术并及时更新技术栈。 一旦完成其中一项就能够部署,而没必要等待全部事情完毕。
  • 下降错误和回归问题的风险,相互之间的依赖性急剧降低。
  • 更简单快捷的测试,每个小的变化没必要再触碰整个应用程序。
  • 更快交付客户价值,有助于持续集成、持续部署以及持续交付。

参考文章

single-spa 文档

微前端 single-spa

这多是你见过最完善的微前端解决方案!

single-spa微前端

Single-Spa + Vue Cli 微前端落地指南 (项目隔离远程加载,自动引入)

项目完整地址:github.com/zhaiyy/sing…

相关文章
相关标签/搜索