Nginx实现微前端多泳道发布

1,246 阅读7分钟

Nginx 实现前端多泳道发布 - 掘金 (juejin.cn) 这篇文章中,我们已经从0到1探讨了普通前端项目支持多泳道发布的原理。今天我们基于之前的认知,继续探讨微前端实现前端多用到发布的一些其他细节。

1. 初始化微前端架构:

所谓微前端项目,本质上就是由一个基座项目和多个子应用共同组成一个前端项目。我们访问最终的微前端项目,实际上只需要访问基座项目的域名就可以了,具体的子项目,是通过基座项目中配置的路由来进行控制和访问的。因此,最终的微前端项目在进行部署的时候,一般只需要给基项目申请一个独立的域名, 然后给所有的子项目申请一个公共的静态资源服务域名就可以了。 我们举一个基于qiankun搭建的微前端项目的例子(具体的微前端项目如何搭建,我在这里就不再赘述了):

  1. 我们准备好一个基座项目以及两个子项目:

image.png

image.png

在基座项目中利用qiankun配置子项目这里面最核心的配置,就是配置两个子项目的入口地址:

image.png

在本地将进行开发的时候配置比较简单,将它们配置为本地启动的子应用服务就可以了。

image.png

但是如果当前的项目已经部署到了线上的话,需要通过 nginx 服务来访问基座应用和子应用的静态资源,那么就需要进行注意了。

2. 利用nginx代理基座应用资源

假设我们现在已经构建并且已经将静态资源部署到了静态资源服务器:

image.png

静态资源服务的地址是:

http://localhost:3000

假如我们已经为整个前端项目申请了一个访问的域名,并且该域名已经映射到了nginx 服务器的地址,我们期望通过如下地址就可以访问整个前端项目:

http://main-project/

我们在nginx中加入如下配置:

    server {
        listen 80;
        server_name main-project;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;
        location / {
            proxy_pass http://localhost:3000/main/;
        }
    }

加上如上配置之后,我们就可以通过

http://main-project/

来访问主应用了:

image.png 至此,我们就基于nginx完成了对于基座项目的访问。

3. 利用 nginx 代理子应用的资源:

前文说过,对于子应用,一般不会设置专门的域名,而是会申请一个公共的域名来统一处理子应用资源的反向代理: sub-project。 我们期望通过访问:http://sub-project/sub1/xxxx 就可以访问到子应用1;通过访问:http://sub-project/sub2/xxxx就可以访问到子应用2。 为了达成这个效果,我们需要按照以下步骤去进行处理:

  1. 在主应用中根据构建模式来动态设置引用的子应用的路口:

// 从环境变量中读取构建模式

const isProd = process.env.NODE_ENV === 'production';

registerMicroApps([
  {
    name: 'app-vue2-demo', // 子应用的唯一名称
    // 根据构建模式动态设置子应用的入口
    entry: isProd ? '//sub-project' : '//localhost:4001', //子应用index.html地址
    container: '#subapp-container', //挂载子应用的容器div
    activeRule: '/app-vue2-demo', //子应用的激活规则
    props:{
      routerBase:'/app-vue2-demo',
    }
  },
  {
    name: 'app-vue2-demo2', // 子应用的唯一名称
    // 根据模式动态设置子应用的入口
    entry: isProd ? '//sub-project' : '//localhost:4002', //子应用index.html地址
    container: '#subapp-container', //挂载子应用的容器div
    activeRule: '/app-vue2-demo2', //子应用的激活规则
  }
  ])

核心就是如果当前构建模式是生产模式,那么就子应用入口统一设置为sub-projec 服务;如果当前构建模式是开发模式,那么就设置为本地服务。改造完毕之后,重新构建主应用并且部署到静态资源服务上去。

  1. 在 nginx 中配置sub-project 服务对应的代理规则,将特定的子应用流量转发到特定的子应用文件夹上去:

    server {
        listen 80;
        server_name sub-project;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;
        # 处理 demo 子应用的请求
        location /app-vue2-demo {
            proxy_pass  http://localhost:3000;
        }
        # 处理 demo2 子应用的请求
        location /app-vue2-demo2 {
            proxy_pass  http://localhost:3000;
        }
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root html;
        }
    }

我们针对 /app-vue2-demo, /app-vue2-demo2 两个子应用的请求 base 路径,分别设置了两个 location 规则,分别将其转发到对应的资源目录。

  1. 在这之后,似乎万事具备了,我们可以访问

http://main-project/ 并且切换到子应用对应的路由,此时会出现一个跨域错误:

image.png 为什么访问子应用的静态资源会出现跨域的错误呢?我们可以再来直接访问主应用的资源:

image.png

主应用依然正常访问了。如果我们对比主应用和子应用资源的请求类型就明白了:

image.png

主应用的请求是一个标准的文档类型的请求。

image.png

子应用的请求是由qiankun框架发出的 ajax 请求,而 ajax 请求是有跨域的限制的。所以我们需要再nginx层允许跨域:

        location /app-vue2-demo {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            add_header 'Access-Control-Max-Age' 1728000;
            if ($request_method = 'OPTIONS') {
                return 204;
            }
            proxy_pass  http://localhost:3000;
        }
        # 处理 demo2 子应用的请求
        location /app-vue2-demo2 {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            add_header 'Access-Control-Max-Age' 1728000;
            if ($request_method = 'OPTIONS') {
                return 204;
            }
            proxy_pass  http://localhost:3000;
        }

当nginx层允许跨域之后,我们再来看一下此时子应用的渲染情况:

image.png

此时跨域的问题确实解决了,但是,页面访问 404 了。为什么会404呢?其实很简单,因为我们请求的地址:(http://main-project/app-vue2-demo2)始终请求到的是主应用,因此 /app-vue2-demo2 这个路由会交给主应用的 router 系统去处理,但是主应用中并没有注册这个路由,因此自然就 404 了。要解决这个问题,思路其实很简单,就是当路由访问 404 的时候,让nginx继续请求主应用index.html,让主应用中的qiankun框架去处理子应用的请求。我们可以尝试以下两种方案:

  1. 设置 try-files 指令:

    server {
        listen 80;
        server_name main-project;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;
        location / {
            proxy_pass http://localhost:3000/main/;
            # 添加或修正 try_files 指令以处理 404 和重定向到 index.html
            try_files $uri $uri/ /index.html;
        }
    }

使用上述配置之后,我们可以看一下页面访问的结果:

image.png

子应用的请求不再是 404 了,但是不管请求任何文件,结果都是主应用的index.html文件: image.png出现这个错误的原因是因为 try_files 它本质上是基于当前 nginx 服务所处的的文件系统来进行路径查找的,任何的 $uri 必须能够映射到nginx系统中的文件。如果映射不到,就通通返回最终指定的 /index.html。

  1. 设置 error_page:

要从处理 http 请求的 404 错误,最好的方案还是进行如下配置:


    server {
        listen 80;
        server_name main-project;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;
        location / {
            proxy_pass http://localhost:3000/main/;
            proxy_intercept_errors on;
            error_page 404 =200 /index.html;
            # 添加或修正 try_files 指令以处理 404 和重定向到 index.html
            # try_files $uri $uri/ /index.html;
        }
    }

error_page 是当 http 请求出现 404 的时候才会进行重定向操作(注意这里一定要强制将其状态码设置为 200):

image.png

经过上述处理之后子应用就被成功渲染出来了。

4. 支持微前端多泳道部署:

  1. 改造文件夹静态资源服务器文件结构:

为了更方便的支持前端多泳道发布,我们从整体上将文件目录划分为两类型:

default文件夹。

多个泳道文件夹,每当存在前端应用发布到新的泳道,那么就会创建出新泳道对应的文件夹。

  1. 主应用支持多用多部署: 需要主应用支持多泳道部署,首先我们需要改造上传之后主应用的资源文件夹结构:
  2. 创建出指定的泳道。
  3. 当用户在发布系统上选择指定功能泳道进行发布之后,需要创建出对应的泳道文件夹以及主应用资源文件夹,并且将主应用的资源文件上传对应的泳道资源文件夹中。假如现在主应用已经成功的将文件夹上传到了 default、lane1、lane2三个泳道,那么此时静态资源文件夹的结构就会形成类似于这样的结构:

image.png

要正常的通过 nginx 能够访问到对应泳道的资源,实际上方案和 Nginx 实现前端多泳道发布 - 掘金 (juejin.cn)是类似的,我们改造主应用的nginx配置:


    server {
        listen 80;
        server_name main-project;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;
        location / {
            # set $gray $http_X_Lane;
            set $lane 'default';
            if ($http_X_Lane) {
                set $lane $http_X_Lane;
            }
            proxy_pass http://localhost:3000/$lane/main/$uri;
            proxy_intercept_errors on;
            error_page 404 =200 /index.html;
            # 添加或修正 try_files 指令以处理 404 和重定向到 index.html
            # try_files $uri $uri/ /index.html;
        }
    }

但是如果我们这样配置,niginx转发有可能会出现一个错误:

image.png

我们查看日志,会得到这样的一个错误:

image.png 错误显示主机名解析不了。这是一个很奇怪的错误,目前我还没有找到原因。并且如果在这里的主机是一个可以正常访问的公网域名或者内网可以访问的域名一般都不会有问题。如果需要快速解决这个问题的话,需要将 localhost 替换为服务器的 ip 地址:

        location / {
            # set $gray $http_X_Lane;
            set $lane 'default';
            if ($http_X_Lane) {
                set $lane $http_X_Lane;
            }
            set $host_path http://127.0.0.1:3000/$lane/main/$uri;
            proxy_pass $host_path;
            proxy_intercept_errors on;
            error_page 404 =200 /index.html;
            # 添加或修正 try_files 指令以处理 404 和重定向到 index.html
            # try_files $uri $uri/ /index.html;
        }

完成上述改造之后,我们还是使用之前提到的 Header Editor 工具来测试泳道,我们新建如下两条规则:

image.png

image.png

规则添加完毕之后,我们启用泳道1,然后我们访问主应用域名:

image.png

然后我们启动泳道2,测试主应用泳道2的内容:

image.png

关闭泳道,我们直接访问主应用默认泳道的内容:

image.png

至此,基于nginx支持主应用泳道发布成功。

3, 子应用支持泳道发布:

  1. 首先还是先将两个测试子应用的内容上传到default泳道以及指定的功能泳道中:

image.png

  1. 改造子应用 nginx 转发配置:

注意,子应用的配置中的转发主机host,一定也要设置为服务器的ip地址。除此之外,因为目前我们这里测试的子应用的转发逻辑是类似的,所以我们可以把两个转发配置合并成为一个配置:

        location ~ ^/(app-vue2-demo|app-vue2-demo2) {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            add_header 'Access-Control-Max-Age' 1728000;
            if ($request_method = 'OPTIONS') {
                return 204;
            }
            set $lane 'default';
            if ($http_X_Lane) {
                set $lane $http_X_Lane;
            }

            proxy_pass http://127.0.0.1:3000/$lane$uri;
            proxy_intercept_errors on;
            error_page 404 =200 /index.html;
        }

配置完毕之后,我们就可以测试子应用的的泳道发布状况了。 测试一下泳道1的访问情况:

image.png

image.png

至此我们就完成了基于nginx发布多泳道发布微前端的原理了。

简单聊一下node.js配合多泳道的实现:

一般来说,node.js 在大部分情况下面都是充当一个 bff 层的角色,不需要提供独立的接口,只需要提供接口的转发:

前端 ---> bff ---> 后端服务

所以bff层简单支持多泳道的话,只需要将前端传递过来的泳道标记,转发的时候带到下游的后端服务就可以了。 当然,这一种方案,并不能够创建出泳道对应的node服务实例。无法实现真正的多用到微服务,要真正的实现微服务,需要实现nodejs层的微服务注册,比如nacos注册,这个又是一块很大的内容了,绝大多数node应用并不需要做到这么复杂,这个我们后续有机会再聊。