基于 Vue 技术栈的微前端方案实践( 二 )

子项目 main.js 代码如下:(为了尽量减少首次主项目页面渲染时加载的资源,子项目的入口文件建议只做路由挂载)
import Vue from 'vue';import routes from './routes';const share = (Vue.__share__ = Vue.__share__ || {});const routesPool = (share.routes = share.routes || {});// 将子项目的 route list 挂载到 Vue.__share__.routes 上,以便后续主项目将其合并到总的路由中routesPool[process.env.VUE_APP_NAME] = routes;5.继续向下解析 html,解析并执行到主项目 main.js 时,从 Vue.__share__.routes 获取所有子项目的 route list,合并到总的路由表中,然后初始化一个 vue-router 实例,并传入到 new Vue 内
相关关键代码如下
// 从 Vue.__share__.routes 获取所有子项目的 route list,合并到总的路由表中const routes = Vue.__share__.routes;export default new Router({routes: Object.values(routes).reduce((acc, prev) => acc.concat(prev), [{path: '/',redirect: '/app-a'}])});到此就实现了单页面应用按照业务拆分成多个子项目,直白来说子项目的入口文件 main.js 就是将主项目和子项目联系起来的桥梁 。
另外如果需要使用 vuex,则和 vue-router 的顺序恰好相反(先主项目后子项目):
1.首先在主项目的入口文件中初始化一个 store 实例 new Vuex.Store,然后挂在到 Vue.__share__.store 上
2.然后在子项目的 App.vue 中获取到 Vue.__share__.store 并调用 store.registerModule(‘app-x', store),将子项目的 store 作为子模块注册到 store 上
懒加载路由方式async-routes
懒加载路由,顾名思义,就是说等到用户点击要进入子项目模块,通过解析即将跳转的路由确定是哪一个子项目,然后再异步去加载该子项目的入口文件 main.js(可以通过 systemjs 或者自己写一个动态创建 script 标签并插入 body 的方法) 。加载成功后就可以将子项目的路由动态添加到主项目中的路由里了 。
1.主项目 router.js 文件中定义了在 vue-router 的 beforeEach 钩子去拦截路由,并根据即将跳转的路由分析出需要哪个子项目,然后去异步加载对应子项目入口文件,下面是核心代码:
const cachedModules = new Set();router.beforeEach(async (to, from, next) => {const [, module] = to.path.split('/');if (Reflect.has(modules, module)) {// 如果已经加载过对应子项目,则无需重复加载,直接跳转即可if (!cachedModules.has(module)) {const { default: application } = await window.System.import(modules[module]);if (application && application.routes) {// 动态添加子项目的 route-listrouter.addRoutes(application.routes);}cachedModules.add(module);next(to.path);} else {next();}return;}});2.子项目的入口文件 main.js 仅需要将子项目的 routes 暴露给主项目即可,代码如下:
import routes from './routes';export default {name: 'JAVAscript',routes,beforeEach(from, to, next) {console.log('JavaScript:', from.path, to.path);next();}};注意:这里除了暴露 routes 方法外,另外又暴露了 beforeEach 方法,其实就是为了支持通过路由守卫对子项目进行页面权限限制,主项目拿到这个子项目的 beforeEach,可以在 vue-router 的 beforeEach 钩子执行,具体代码请参考 async-routes 。
除了主项目和子项目的交互方式不同,代理转发子项目资源、vuex store 注册等和上面的预加载路由完全一致 。
优缺点下面谈下这套方案的优缺点:
优点

  • 子项目可单独打包、单独部署上线,提升了开发和打包的速度
  • 子项目之间开发互相独立,互不影响,可在不同仓库进行维护,减少的单个项目的规模
  • 保持单页应用的体验,子项目之间切换不刷新
  • 改造成本低,对现有项目侵入度较低,业务线迁移成本也较低
  • 保证整体项目统一一个技术栈
缺点:
  • 主项目和子项目需要共用一个 Vue 实例,所以无法做到某个子项目单独使用最新版 Vue(例如 Vue3)或者 React
部分问题解答1.如果子项目代码更新后,除了打包部署子项目之外,还需要打包部署主项目吗?
不需要更新部署主项目 。这里有个 trick 上文忘记提及,就是子项目打包后的入口文件并没有加上 chunkhash,直接就是 main.js(子项目其他的 js 都有 chunkhash) 。也就是说主项目只需要记住子项目的名字,就可以通过 subapp-name/main.js 找到子项目的入口文件,所以子项目打包部署后,主项目并不需要更新任何东西 。
2.针对第二个问题中的项目入口文件 main.js 不使用 chunkhash 的话,如何防止该文件始终被缓存呢?
可以在静态资源服务器端针对子项目入口文件设置强制缓存为不缓存,下面是服务器为 nginx 情况的相关配置:


推荐阅读