全网独家 — qiankun微前端实现JSP页面逐步迁移

1,968 阅读5分钟

背景

相信JSP是所有传统企业的前端开发的必经之路,只要迭代时间稍微比较旧且规模比较大的项目,基本都是使用JSP + Jquery + AMD模块化来实现前端开发的。但到了现在SPA框架满地跑,SSR框架百花齐放的前端开发大背景下,JSP开发以及维护无疑是效率相对低下不少的,于是乎大家都想如何才能把JSP页面开发迁移到SPA或SSR框架中进行开发。

  • JSP是没有相对应路由概念的,均由Java的Servlet实现了url的拦截以及JSP的转发实现的,以至于大型项目中前端无从考究多达四五十个页面的url路径到底是什么
  • 开发资源中并没有足够人员或人力可投入到页面迁移这种纯技改项目中,大部分开发都是被业务占据的,而且大型项目如果需要所有页面一个个重新以SPA实现一遍是一个巨大无比的工作量

设计

基于上述背景条件,我计划使用qiankun作为微前端的框架,因为她能无视框架的类型,只要能对DOM进行控制,他就能使用qiankun实现微前端化,使得JSP能与SPA整合到一起,且原始路由不受影响,用户访问基本上无感知。

关于qiankun的介绍与学习强推下面的这个文章 juejin.cn/post/706956…

该设计与市场上主流的微前端实践上最大的不同是 以JSP页面作为微前端的主基座应用
目的就是无需前端重新识别和控制路由, SPA子应用仅仅作为子模块插入到主基座各个页面指定的DOM中

步骤

搭建SPA子应用

搭建一个前端标准化的的vue/react脚手架项目(包含对api的自动引入,定制化传参;组件的统一注册;request请求的封装设计,路由的自动注册,打包优化等等) —— 后续需要快速迭代的项目也均依赖该脚手架进行开发。然后使用该脚手架搭建主站所对应的副站点,接下来所有 后续的新页面 均使用副站进行开发

关于前端脚手架的搭建、项目优化、代码管理规范可参考本人文章
脚手架的搭建:juejin.cn/post/701852…
打包优化:juejin.cn/post/701890…
代码规范及其管理:juejin.cn/post/702957…

前端灰度发布方案实现(qiankun接入后可中止)

与运维同事设计并定制出前端灰度发布的方案,详细得来说,目标是在用户访问原始页面如 www.baidu.com/login 时,能根据Nginx实现流量分发,一开始把20%的流量切入到副站所在的SPA页面,把80%的流量转发到原始的JSP页面中,等运行稳定且无bug发生后,再逐步增加副站的流量直到100%

详细做法是需要主站基座应用与SPA子应用都分别需要容器化进行部署,并挂在到两个不同的ip下。然后第一个nginx 接收所有路径,在反向代理的时候,将新的路径的服务重新定向到本地的不同端口上,再将服务重定向到新容器的新接口

image.png

原 nginx 配置监听:

location = /login.html {
        proxy_pass http://controltomcat;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $http_x_forwarded_for;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        access_log  /data/logs/nginx/login.access.log  json;
}

upstream 配置:

upstream controltomcat {
        server 192.168.40.168:8089 weight=2; #旧容器,对应图中1.12地址
        server dev-new.xxx.com:89 weight=1;  #注意,这里的地址不能是同台服务器的地址和同个端口,否则会变成死循环
}

dev-new.vvic.com 监听配置:

**server {
        listen 89;
        server_name dev-new.xxx.com;
        charset utf-8;
        #uninitialized_variable_warn off;
        access_log  /data/logs/nginx/dev-new.access.log main;
        error_log /data/logs/nginx/dev-new.error.log;
        #rewrite /login.html http://192.168.2.110/main/login;
        
        location = /login.html {
          proxy_pass http://192.168.2.110/main/login;
          #新容器地址
        }
        location / {
          proxy_pass http://192.168.2.110;
        }
}**

以上配置较为复杂是由于我们一开始的设计是主基座和子应用的url是不同的,如原始url是 www.baidu.com/login ,而子应用url则是 www.baidu.com/main/login 所以就需要路径的重写,而后续接入qiankun后 主基座与子应用url保持一致后,就仅仅需要流量的转发即可,无需路径的重定向

qiankun接入

JSP主基座

AMD结构的JS如何引入qiankun

由于AMD结构无法使用import,研究了多种方法,如gulp中引入webpack、改造打包方式等等后,发现还是直接CDN引入最为直接,大家直接在这里下载脚本然后挂载到自己可信的CDN服务器中即可 github.com/Raj6666/qia…

页面中如何插入子应用

首先qiankun有自动接入与手动接入两种,由于我们需要利用主基座与子应用相同url的原理,我们需要使用的是自动接入方式

以下面代码举例,我们需要在主基座的商品详情页 www.baidu.com/item/:id 中重构图片与广告两个模块
由于我们使用的是 registerMicroApps,所以只要url命中了activeRule,子应用就会加载他内部路由为 http://localhost:2040/item/:id 对应的组件出来, 然后组件渲染哪个模块,则通过主基座中传入的microModule 来进行决定

qiankun.registerMicroApps([{
      name: 'hello-micro', // 应用的名字 必填 唯一
      entry: '//localhost:2040', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
      container: '#picMicroTemplate', // 挂载具体容器 ID
      activeRule: '/item/:id',
      props: {
        microModule: 'picMicro'
      }
    },{
      name: 'hello-micro2', // 应用的名字 必填 唯一
      entry: '//localhost:2040', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
      container: '#adsMicroTemplate', // 挂载具体容器 ID
      activeRule: '/item/:id',
      props: {
        microModule: 'adsMicro'
      }
    }]);
    qiankun.start();

SPA子应用

子应用如何打包与导出

1:vue.config.js 中进行配置 其中最为重点的是libraryTarget 的配置必须是 window,否则无法和主基座中的AMD语法相关联

引用自官网的QA的第8点:qiankun.umijs.org/zh/faq#appl…

configureWebpack: (config) => {
    ...,
    config.output.library = `vueMicroApp`; // 微应用的包名,这里与主应用中注册的微应用名称一致
    config.output.libraryTarget = 'window'; // 这里设置为umd意思是在 AMD 或 CommonJS 的 require 之后可访问。
    config.output.jsonpFunction = `webpackJsonp_${name}`; // webpack用来异步加载chunk的JSONP 函数。
 },
devServer: {
    ...,
    disableHostCheck: true, // 关闭主机检查,使微应用可以被 fetch
    headers: {
      'Access-Control-Allow-Origin': '*', //因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域
    },
}

2:入口文件 main.js中导出qiankun所需的生命周期函数

// 微应用注册
let instance = null;

// 1. 将注册方法用函数包裹,供后续主应用与独立运行调用
function render(props = {}) {
  // 解析主应用传入的值并注册vue实例
  const {container, microModule} = props;
  console.log('开始渲染', microModule);
  instance = new Vue({
    router,
    store,
    authGuard,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
  if (microModule) {
    console.log('更新state', {microModule});
    store.commit('setMicroModule', {microModule});
  }
}

// 判断是否在乾坤环境下,非乾坤环境下独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}

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

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

子应用组件判断该挂载哪个模块

首先我们可以在页面的初始化 render 函数中,获取主机中中传进来的 microModule 属性,然后把他保存到store

image.png

然后在 item/:id 路由下对应的组件中进行 state 的判断然后加载就可以了,当然实际开发中肯定是要把这两个模块封装成业务组件切出去的

image.png

踩坑

子应用外部加载的脚本记得添加 ignore 属性

image.png

小结

根据上文的配置,我们就已经实现了一个最基础的把Vue作为子应用挂在到JSP页面中的最核心实践了,当然这只是起步,还有后续的子应用预加载、主基座与子应用间的样式隔离、资源共享等等的优化属性还等着我们去完善,但这个最核心的坎,肯定是已经跨过去了,恭喜大家。。。再也不用受JSP的气了

总结

按照上文的步骤来实现,我们就已经从理论上摆脱JSP项目的开发了。
首先,假如我们需要开发一个全新的页面,则直接在子应用也就是SPA站点中实现即可;
然后,假如我们接到业务需求需要重构JSP页面中的某个模块,则同样是在子应用中进行开发,开发完成后使用qiankun子应用挂载的方式挂载到 JSP 的指定 DOM 中即可
最后,在某个页面的vue化重构程度达到80% 左右时,再把整个页面迁移到子应用中即可