在前端,每每由一个前端团队建立并维护一个 Web 应用程序,使用 REST API 从后端服务获取数据。这种方式若是作得好的话,它可以提供优秀的用户体验。但主要的缺点是单页面应用(SPA)不能很好地扩展和部署。中后台应用因为其应用生命周期长 (动辄 3+ 年) ,因为参与的人员、团队的增多、变迁,等特色从一个普通应用演变成一个巨石应用 ( Frontend Monolith ) 后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤为常见。javascript
在一个大公司里,单前端团队可能成为一个发展瓶颈。前端
首先,必须先了解什么是微前端架构。 微前端架构是一种相似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。vue
single spa是一个javascript库,容许许多小应用程序在一个页面应用程序中共存。这个https://single-spa.surge.sh/ 网站是一个演示应用程序,展现了什么单一应用。spa的理念是让独立的、独立的应用程序组成一个完整的页面。单spa并无长期依赖于单个框架和每一个特性,而是帮助您在开发新框架时采用它们。 简单来讲就是一个万能的粘合剂,使用这个库可让你的应用可使用多个不一样的技术栈(vue、react、angular等等)进行同步开发,最后使用一个公用的路由便可实现完美切换。固然了,也可使用同样的技术栈,分不一样的团队进行开发,只须要最后使用这个库将其整合在一块儿,设置不用的路由名称便可。java
修改app.vue 文件 添加node
<div id=“vue”></div>
容器react
引入安装包文件webpack
npm install single-spa --save –d
ios
Main.js 引入配置文件git
import './single-spa-config.js’
github
引入装包文件
npm install single-spa-vue --save -d
首先了解一下singleSpa 主要的API
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’)
)
复制代码
修改父容器的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;
复制代码
生命周期函数共有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()]
}
复制代码
在上面父项目加载子项目的代码中,咱们能够看到。咱们要注册一个子服务,须要一次性加载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 + Vue Cli 微前端落地指南 (项目隔离远程加载,自动引入)
项目完整地址:github.com/zhaiyy/sing…