微前端框架 之 single-spa

缘由

给客户做的项目,一期二期三期地做着。做了三四个后台管理,为了方便管理,把几个项目合并处理。所以在网上搜索资料。原博主地址链接。

single-spa

Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。 使用 single-spa 进行前端架构设计可以带来很多好处,例如:

  • 在同一页面上使用多个前端框架 而不用刷新页面 (React, AngularJS, Angular, Ember, 你正在使用的框架)
    独立部署每一个单页面应用
  • 新功能使用新框架,旧的单页应用不用重写可以共存
  • 改善初始加载时间,迟加载代码
新建项目

新建项目,文件名micro-frontend,在该目录下生成app1,app2和layout项目

mkdir micro-frontend && cd micro-frontend
vue create project-name

选项如下,最简约版只要配个路由就行了
微前端框架 之 single-spa_第1张图片
微前端框架 之 single-spa_第2张图片

app1子项目初始化

其中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'
    }
  }
}

安装single-spa-vue
npm i single-spa-vue -S

single-spa-vue负责为vue应用生成通用的生命周期钩子,在子应用注册到single-spa的基座应用时需要用到

改造入口文件 main.js
// /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
})
修改package.json中的script
{
  "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

微前端框架 之 single-spa_第3张图片

app2配置与app1一致,端口改为8082,app1的文字改为app2即可
基座layout

在/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

微前端框架 之 single-spa_第4张图片

微前端框架 之 single-spa_第5张图片
微前端框架 之 single-spa_第6张图片

收工

你可能感兴趣的:(笔记,single-spa,微前端框架)