# 旧应用改造手册

本教程针对旧项目改造微应用使用,主要目的将现有旧项目改造为微应用模式。
📌tips:

  • 除了将旧项目改造为微应用外,也可以进行反向操作。下载平台子应用脚手架,将旧项目业务代码进行迁移以完成改造。

# 步骤

# 环境准备

  • 平台集成开发工具包: @adam/micro
  • 平台提供组件库可选:@adam/glink

# 安装开发工具库

使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。
@adam/micro 提供了微应用数据共享,样式隔离,路由转化等功能。
注意:安装之前要确保已把 npm registry 设置为公司内网地址,如: registry=http://nexus.gosp.glkyun.com/repository/npm-all/。另外此包需要结 合webpack配置使用 npm install @adam/micro

# 使用说明

微前端全局状态管理类由原生JS编写,技术栈无关,因此可与任何前端框架配合使用 针对Vue应用提供了一些实用的插件,其它框架须自行实现,下面的介绍也会以Vue应用为主

# 一、微应用集成说明

主要包括:

  1. 在微应用的入口文件中集成
  2. 打包输出UMD模块
  3. 平台路由
  4. 样式隔离支持
  5. 统一数据管理

# 1. 在微应用中的入口文件中集成

环境变量配置

# 系统名称,package.json中的name尽量与其保持一致
VUE_APP_NAME='glink-system-manage' 
# 部署路径名称,同时为ngxin代理路径名称
VUE_APP_PUBLIC_PATH = 'glink-system-manage'
# 路由统一前缀,用于匹配当前微应用
VUE_APP_ROUTER_BASE = '/glink-system-manage'
1
2
3
4
5
6

微前端全局状态集成,根据状态在XHR请求头中设置上下文信息、动态设置当前语言等
导出微前端生命周期勾子
平台门户重要路径参数保持,如:当前组织
新建public-path.js文件

if (window.__POWERED_BY_QIANKUN__) {
  // 动态设置 webpack publicPath,防止资源加载出错
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
1
2
3
4
5

更改入口文件

import Vue from 'vue'
import App from './App.vue'
import routesModule from './router'
import store from '@common/store'
import '@/plugins'
import '@/directives'
import '@/utils/jsonpCallBack'
import '@/style'
import init from '@common/utils/init'
import '@/utils/error'
import * as request from '@business/utils/request'
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import Es6Promise from 'es6-promise'
import config from './config.json'
Es6Promise.polyfill()
// 以上引入为示例,可选部分需要添加
// 微前端
import VueRouter from "vue-router";
import "./public-path";
import { resetRouter } from '@adam/micro/src/utils/reset-router'
import { globalRegister } from '@adam/micro'

Vue.config.productionTip = false
// 初始化信息
init({
  store,
  config,
  request,
  routesModule
})

let instance = null;
let router = null;
let routes = routesModule.options.routes //为当前项目下所有路由,hash模式需要进行路由前缀统一处理

/**
 * 渲染函数
 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
 */
function render(props = {}) {
  let { container, routerBase, mode } = props
  routerBase = process.env.VUE_APP_ROUTER_BASE
  // hash模式需要重新设置路由地址,增加前缀
  if (mode === 'hash') {
    resetRouter(routes,routerBase ,mode)
  }
  // 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
  router = new VueRouter({
    // 运行在主应用中时,添加路由命名空间
    base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.VUE_APP_ROUTER_BASE,
    mode: mode?mode:'hash',
    routes
  });

  // 监听路由
  router.beforeEach((to, from, next) => {
    if (window.__POWERED_BY_QIANKUN__) {
      // 判断basePath,存在则是hash模式,需要变更路径
      if (from.meta.basePath && !to.path.includes(from.meta.basePath) && to.path.indexOf('/') === 0) {
        next({
          path: from.meta.basePath + to.path,
          query: to.query,
          params: to.params
        })
        return
      }
    }
    next()
  })

  // 挂载应用
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector(`#app`) : `#app`)
}

// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  globalRegister(store)
  render()
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log("brokenSubApp bootstraped");
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  globalRegister(store,props)
  console.log("brokenSubApp mount", props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log("brokenSubApp unmount");
  instance.$destroy();
  instance = null;
  router = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

# 2. 打包输出UMD模块

如果使用 webpack 进行构建打包,可参考以下vue-cli配置片段

// vue.config.js
const packageName = require('./package.json').name
module.exports = { 
// ... 
publicPath: process.env.VUE_APP_PUBLIC_PATH ? `/${process.env.VUE_APP_PUBLIC_PATH}/` : '/',//生产环境取VUE_APP_PUBLIC_PATH
configureWebpack: { 
 output: { 
   library: `${packageName}-[name]`,
   libraryTarget: 'umd',
   jsonpFunction: `webpackJsonp_${packageName}` 
  } 
 } 
}
if(process.env.NODE_ENV === 'development'){
    vueConfigs.publicPath = '/',
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 3.样式隔离

基于postcss开发的样式隔离插件,用于隔离各系统之间的样式
项目根目录下新建postcss.config.js

const postcssiso = require('@adam/micro/src/utils/postcssiso')
module.exports = () => {
  return {
    plugins: [
      require('autoprefixer')(),
      postcssiso(`.${process.env.VUE_APP_NAME}`, {
        ignore: ['button', 'input', 'span'] //可自定义忽略标签
      })
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11

在App.vue添加隔离标签:class="id"

<template>
  <div id="app" :class="id" >
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      id: process.env.VUE_APP_NAME
    }
  }
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4.数据共享

一部分动态唯一参数会存储到sessionStorage中,如当前产品编码,productCode:PRODUCT_CODE 主应用在注册时会统一将一部分通用参数,存储到子应用的store/global模块中 目前支持参数包括

   * @param {boolean} invalidToken // token是否失效了
1

使用示例,如子应用token过期需要通知主应用: this.$store.dispatch('global/setGlobalState', {invalidToken:true}) 其他参数均可按vuex规范取出使用。

# 二、其他对接调整

旧项目改造还需要一些其他适配调整

# 子路由跳转

修改utils/router.js

const original = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  // 统一拼接子应用前缀(此处添加)
  location.path = process.env.VUE_APP_ROUTER_BASE + location.path
  return original.call(this, location).catch(err => err)
}
...
1
2
3
4
5
6
7

# basis服务移除

如果使用了原平台的basis服务请进行移除;由于服务已全部迁移至saas,原basis服务不可用,需进行移除 查看common/global.const.js 使用到basis的url请求服务全部进行移除

# 修改request函数的请求头

打开文件utils/request.js

...
  config.headers = {
    ...headers,
    'x-nepoch-org': store.state.auth.userInfo.deptId,
    'x-nepoch-system': 12305,
    'x-nepoch-application': system,
    'x-nepoch-tenant':  tenant?tenant.id: '1527589324236275712',
  }
...
1
2
3
4
5
6
7
8
9

# 微应用模式路由修改

路由需去除嵌套路layout,layout由主应用提供 eg:

export default [
  {
    path: '/home',
    component: () => import('@business/views/home'),
    meta: {
      title: '首页'
    }
  }
]
1
2
3
4
5
6
7
8
9