给客户做的项目,一期二期三期地做着。做了三四个后台管理,为了方便管理,把几个项目合并处理。所以在网上搜索资料。原博主地址链接。
Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。 使用 single-spa 进行前端架构设计可以带来很多好处,例如:
新建项目,文件名micro-frontend,在该目录下生成app1,app2和layout项目
mkdir micro-frontend && cd micro-frontend
vue create project-name
其中app2和app1的对于单页面的配置是一样的。
以app1为例
在micro-frontend/app1下新建vue.config.js
const package = require('./package.json')
module.exports = {
// 告诉子应用在这个地址加载静态资源,否则会去基座应用的域名下加载
publicPath: '//localhost:8081',
// 开发服务器
devServer: {
port: 8081
},
configureWebpack: {
// 导出umd格式的包,在全局对象上挂载属性package.name,基座应用需要通过这个全局对象获取一些信息,比如子应用导出的生命周期函数
output: {
// library的值在所有子应用中需要唯一
library: package.name,
libraryTarget: 'umd'
}
}
}
npm i single-spa-vue -S
single-spa-vue负责为vue应用生成通用的生命周期钩子,在子应用注册到single-spa的基座应用时需要用到
// /src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import singleSpaVue from 'single-spa-vue'
Vue.config.productionTip = false
const appOptions = {
el: '#microApp',
router,
render: h => h(App)
}
// 支持应用独立运行、部署,不依赖于基座应用。通俗来讲,就是在原有的项目基础上扩展,不侵入
if (!window.singleSpaNavigate) {
delete appOptions.el
new Vue(appOptions).$mount('#app')
}
// 基于基座应用,导出生命周期函数
const vueLifecycle = singleSpaVue({
Vue,
appOptions
})
export function bootstrap (props) {
console.log('app1 bootstrap')
return vueLifecycle.bootstrap(() => {})
}
export function mount (props) {
console.log('app1 mount')
return vueLifecycle.mount(() => {})
}
export function unmount (props) {
console.log('app1 unmount')
return vueLifecycle.unmount(() => {})
}
// home
<div class="home">
+ <h1>app1 home pageh1>
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
div>
// about
<div class="about">
+ <h1>app1 about pageh1>
<h1>This is an about pageh1>
div>
.env
应用独立运行时的开发环境配置
NODE_ENV=development
VUE_APP_BASE_URL=/
.env.micro
作为子应用运行时的开发环境配置
NODE_ENV=development
VUE_APP_BASE_URL=/app1
.env.buildMicro
作为子应用构建生产环境bundle时的环境配置,但这里的NODE_ENV为development,而不是production,是为了方便,这个方便其实single-spa带来的弊端(js entry的弊端)
NODE_ENV=development
VUE_APP_BASE_URL=/app1
const router = new VueRouter({
mode: 'history',
// 通过环境变量来配置路由的 base url
base: process.env.VUE_APP_BASE_URL,
routes
})
{
"name": "app1",
// ...
"scripts": {
// 独立运行
"serve": "vue-cli-service serve",
// 作为子应用运行
"serve:micro": "vue-cli-service serve --mode micro",
// 构建子应用
"build": "vue-cli-service build --mode buildMicro"
},
// ...
}
作为独立项目
npm run serve
作为子项目 (路径补全 /app1)
npm run serve:micro
在/micro-frontend/layout目录下进行
安装依赖
npm i single-spa -S
改造基座项目
main.js
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'
Vue.config.productionTip = false
// 远程加载子应用
function createScript(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)
})
}
// 记载函数,返回一个 promise
function loadApp(url, globalVar) {
// 支持远程加载子应用
return async () => {
await createScript(url + '/js/chunk-vendors.js')
await createScript(url + '/js/app.js')
// 这里的return很重要,需要从这个全局对象中拿到子应用暴露出来的生命周期函数
return window[globalVar]
}
}
// 子应用列表
const apps = [
{
// 子应用名称
name: 'app1',
// 子应用加载函数,是一个promise
app: loadApp('http://localhost:8081', 'app1'),
// 当路由满足条件时(返回true),激活(挂载)子应用
activeWhen: location => location.pathname.startsWith('/app1'),
// 传递给子应用的对象
customProps: {}
},
{
name: 'app2',
app: loadApp('http://localhost:8082', 'app2'),
activeWhen: location => location.pathname.startsWith('/app2'),
customProps: {}
},
]
// 注册子应用
for (let i = apps.length - 1; i >= 0; i--) {
registerApplication(apps[i])
}
new Vue({
router,
mounted() {
// 启动
start()
},
render: h => h(App)
}).$mount('#app')
App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/app1">app1router-link> |
<router-link to="/app2">app2router-link>
div>
<div id = "microApp">
<router-view/>
div>
div>
template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
style>
路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = []
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
启动基座运行,前提app1与app2都处于运行状态
npm run serve
收工