基于umijs多模块系统调试开发方案

基于umijs多模块系统调试开发方案

背景

​ 随着前端开发过程工程化的深入,前端SPA应用已经越来越多的在各种信息系统内部采用,而且随着微服务架构的深入,前端SPA应用的使用又更加普及了。如果只是一个独立应用的开发,那么前端在开发阶段,往往是以下形态存在的:

spa app产品形态.png

​ 本地npm run start,通过了devServer开启了一个web服务器,另外通过本地模拟或者proxy的方式,把向后端请求接口代理到了后端开发者提供给我们的地址。

​ 这种方式的确简单和方便,但是有个前提:一个独立的应用。如果开发的是一个大型系统的一个模块。可以参考下图:

信息系统部署形态.png

​ 那么开发以上单个模块的前端工程,往往需要和多方进行交互,甚至还可能存在前端运行的特定容器。这些交互包括:请求后端其他(非本模块)接口服务、接口请求权限认证、前端调用其他模块页面等等。如果还是采用简单的方式进行本地开发,伴随而来的就需要解决诸如:添加多个proxy、前端模拟登录、前端跨域问题等等,而这种问题的解决,同时也伴随着工程化配置的复杂化。

​ 本文就基于umijs脚手架前端应用为基础,讲解伴随着前端工程化的深入,前端应用本地开发三种解决以上问题的方式。

可行方案迭代

前端Http全局跨域

​ 前端开发相关性最强的是后端请求接口以及接口引起的权限和跨域问题。那么如果借助浏览同一个窗口,同域cookie共享的机制,自然而然可以想象把前端页面对后端接口的请求,通过全地址的方式进行处理(而不是devServer代理一次)。这样可以提前登陆多模块的平台系统,然后设置浏览器全局跨域的方式,绕过接口的授权和跨域限制。

​ 简单来说就是两步:

  • 修改前端异步请求库,添加后端接口全地址前缀,比如axiosjquery的设置

    import axios from 'axios';
    
    // 后端接口全路径前缀
    let serverHost = "http://192.168.91.66/"
    
    if (process.env.NODE_ENV !== 'development') {
      axios.defaults.baseURL = serverHost;
      jquery.ajaxSettings.baseURL = serverHost;
    }
    
  • 修改浏览器跨域限制,默认开发阶段支持接口调用全局跨域。

    chrome浏览器,右键属性,在目标栏,添加--args --disable-web-security --user-data-dir="C:/chromeDev"

chrome 跨域.png

这样处理以后,保证了后端接口的合理调用,但是这种处理方式也存在一定的限制,比如,如果是前端引用其他模块的页面,跨页面之间的交互仍旧是限制的(iframe内部window.parent)。

前端devServer代理

既然第一种方案不能那么完美的解决跨页面交互跨域的问题,那么第二种方案自然而然的产生了,深度使用devServer的代理功能。

我们通过umijs配置的proxy属性,把全部需要依赖的后端接口都添加上代理,包括前端跨页面访问的其他模块页面地址,一般存在的形式可能如下:

proxy:{
    '/xxService': {
        target: 'http://10.30.21.32:8080/',
        changeOrigin: true
    }
}

同时每次开发调试前,登录多模块系统,浏览器端获取到cookie信息(chrome F12 Appliaction),添加到proxy代理头:

const msServiceCookie = 'language=zh_CN; JSESSIONID=EBE18455DA8A776B097DE467B12E6833; GWSessionId=839AEF9FA35BCAF24466CF7A6C85063C; CASTGC=TGT-44-UUEoZ9yhQc1vNNzdeng2YQewyvsErWbjCLPqfTGAYGIFqYiaML';
const isDev = process.env.NODE_ENV === 'development';

const proxyHeaders = isDev ? {
    'Cookie': msServiceCookie,
} : {};

proxy:{
    '/xxService': {
        target: 'http://10.30.21.32:8080/',
        changeOrigin: true,
        headers: proxyHeaders
    }
}

这样,本地前端开发调试过程,无论是接口调用还是跨域问题,都可以解决了。唯一不太方便的地方就是,cookie是有有效期的,每次失效得从新赋值,后端接口如果前缀规则很多,在proxy内部需要配置多次。

Nginx同域代理

行文至此,似乎本文的话题应该结束了,但是总觉得以上两种方案还是不够完美,同时又让我想起了一句万变不离其中的真理:

计算机科学体系内,没有加一层解决不了的问题

对,下面描述的就是基于多一层(Nginx),来实现更合适这种场景的解决方案。

Nginx的特点就不再赘述,我们今天用到的就是其最普通的功能:代理。最终我们的目标就是,把我们本地的调试站点和多模块的平台系统实现同域化。当然还要适当的应用umijs非根目录部署特性。大概原理如下:

架构流程图-产品形态的代理.png

我们在本地开发环境运行一个Nginx服务(http://127.0.0.1:9528),配置其代理,使得路径/app-local//sockjs-node/代理到本地开发启动的nodejs服务(本地代码http://127.0.0.1:9527),其他路径直接代理到目标多模块平台系统(http://10.30.5.36:8080/)。这样借助nginx代理自动携带cookie的特色,我们可以直接访问本地http://127.0.0.1:9528,既可以做到同访问http://10.30.5.36:8080的效果了。而只有/app-local/和/sockjs-node/代理到了我们的本地,这也正是我们需要达到的目标。参考nginx示例如下:


    # 开发代理
    server {
    
        listen 9528;
        server_name_in_redirect off;

        # 本地开发启动的站点
        location ^~/app-local/{
            proxy_pass http://127.0.0.1:9527;
        }
        
        # 本地调试webpack hot功能的websockt
        location ^~/sockjs-node/{
            proxy_set_header X-Real_IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X_Forward_For $proxy_add_x_forwarded_for;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_pass http://127.0.0.1:9527;
        }

        # BAP远程环境
        location /{
            proxy_pass http://10.30.5.36:8080/;
        }
            
                
    }

介绍到这里,熟悉umijs路由的同学会发现,原先我们的应用的产品态的路由,现在必须添加/app-local前缀了,否则前端的路由是没有办法定位到我们之前的功能的。直白点讲,之前我们的功能可能设置的路由为/appx/emp/list,那么我们现在开发阶段,必须修改为/app-local/appx/emp/list才能正常访问。同时,本地应用的所有静态资源,也必须添加/app-local前缀。幸好,umijs已经提供了合适的机制来实现这个功能。还是umijs的配置:

import routes from './routes';

let buildDevBase = `/app-local/`;
const isDev = process.env.NODE_ENV === 'development';

// ++ 功能点添加
const chainWebpack = (config) => {
  // 添加静态资源非根目录部署路径(开发阶段(yarn start:custom)umi的publicPath没有生效)
  if (process.env.NODE_ENV === 'development') {
    config.merge({
      output: {
        publicPath: buildDevBase,
      },
    });
  }
}

let appConfig = {
  treeShaking: true,
  routes,
  targets: {
    ie: 11,
  },
  plugins: [
    [
      'umi-plugin-react',
      {
        dva: {
          immer: true
        },
        dynamicImport: {
          webpackChunkName: true,
          loadingComponent: './platform/loading',
        },
        title: 'MES-APP',
        dll: false,
        routes: {
          exclude: [
            /models\//,
            /services\//,
            /model\.(t|j)sx?$/,
            /service\.(t|j)sx?$/,
            /components\//
          ],
        },
      },
    ]
  ],
  chainWebpack: chainWebpack
};

// ++ 功能点添加
if(isDev){
  appConfig = {...appConfig,base: buildDevBase,publicPath: buildDevBase}
}

export default appConfig;

其实以上的修改,说到底就是实现umijs应用非根目录部署过程。

那么本地开发过程中,第一步当然是通过本地9528端口,登录平台系统,然后切换到本地应用地址进行正常打开调试,此时已经是同域场景,接口调用也会直接代理到了平台,对其他模块页面访问也不再造成跨域问题了,似乎我们需要实现的功能都实现了。当然这个方案还是有待真实场景试验,比如sockjs-node/这样的路径也是在不断尝试过程中发现的。

后记

前端模块化、组件化和工程化,已经把前端开发过程变的不再那么简单,各种基础环境配置也被引入了前端开发过程,本文的http代理,就是个例子。但是我相信,本质还是不变的,无论是前端路由,还是React的虚拟Dom,还是nodejsdevServer都离不开之前的技术体系,话说回来了,前端基础还是那三把剑:html,css,javascript

你可能感兴趣的:(基于umijs多模块系统调试开发方案)