• 再谈前后端分离开发和部署


    再谈前后端分离开发和部署

    前后端分离开发已成为业界的共识,但分离的同时也带来了部署的问题。传统web模式下,前端和后端同属一个项目,模板的渲染理所当然由后端渲染。然而随着node的流行,以及webpack的模块化打包方案,让前端在开发阶段完全有能力脱离后端环境:通过本地node启动一个服务器,搭配Mock数据,马上就可以进行业务开发了。

    但是到了部署阶段,问题也就显现出来:前端最后打包出来的js,css以及index.html,到底放在哪里?静态文件js,css或者图片,我们还可以在CI阶段上传到cdn服务器上,但是最后的html模板index.html一定需要放在一个服务器上,然而这个服务器到底由前端还是后端维护?

    前端维护HTML

    如果html模板由前端维护,那么前端就需要自己使用一个静态服务器:提供HTML的渲染和API接口的转发。常见的单页应用,也是推荐使用Nginx进行部署。

    使用Nginx部署,这里又分两种情况:

    • 静态资源完全由Nginx托管,也就是js,css和index.html放在同一个dist目录里。在这种情况下,webpack的publicPath一般不用特别设置,使用默认的/即可
    • 静态资源上传CDN,Nginx只提供index.html。在这种情况下,webpack的publicPath要设置成cdn的地址,例如://static.demo.cn/activity/share/。但这又会引发一个问题,由于qa环境,验证环境,生产环境的cdn地址通常不同,为了让index.html可以引入正确的静态文件路径,你需要打包三次,仅仅为了生成三份引用了不同路径的html(即使三次打包的js内容完全一样)

    nginx配置

    server {
        listen       80;
        server_name  localhost;
    
        location / {
            root   /app/dist; # 打包的路径
            index  index.html index.htm;
            try_files $uri $uri/ /index.html; # 单页应用防止重刷新返回404,如果是多页应用则无需这条命令
        }
    
        location /api {
            proxy_pass https://anata.me; #后台转发地址
            proxy_set_header   X-Forwarded-Proto $scheme;
            proxy_set_header   X-Real-IP         $remote_addr;
        }
    }

    理论上qa,yz,prod环境的接口转发地址也不同,因此你还需要三份nginx.conf配置

    后端维护HTML

    很多情况下,我们需要渲染的页面带上后端注入的动态数据,又或者页面需要支持SEO,这种情况下,我们只能把模板交给后端渲染。那么后端维护的html模板怎么获取打包后的hash值呢?

    • 前端打包后的index.html直接发给后端(简单粗暴,并不推荐)
    • 前端打包时通过插件webpack-manifest-plugin后生成一个manifest.json文件,该文件其实是个key-value的键值对,key代表了资源名称,value记录了资源的hash
    {
      "common.css": "/css/common/common-bundle.804a717f.css",
      "common.js": "/js/common/common-bundle.fcb76db9.js",
      "manifest.js": "/js/manifest/manifest-bundle.551ff423.js",
      "vendor.js": "/js/vendor/vendor-bundle.d99dc0e4.js",
      "page1.css": "/css/demo/demo-bundle.795bbee4.css",
      "page1.js": "/js/demo/demo-bundle.e382801f.js",
    }
    

    后端的index.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>demo</title>
      <link href="<%= manifest['page1.css']%>" rel="stylesheet">
    </head>
    
    <body>
      <h1>demo</h1>
      <script src="<%= manifest['page1.js'] %>"></script>
    </body>
    </html>

    后端通过读取这个json文件,就可以动态渲染出文件的引用路径。

    如果你曾经用过百度的打包工具FIS,它最后打包产出的map.json就是类似的资源文件列表。

    使用这种方法还有一个好处:前面我们说过,如果文件上传至cdn,那么前端维护的html可能需要打包三次,因为不同环境的cdn地址不同。现在html交给后端维护了,那么这个问题就很好解决,前端只需要打包一次,不同环境的cdn地址可以让后端动态拼接生成。

    当然,使用这种方法也会带来一个问题,这个json文件,后端怎么获取?

    1. 把这个json文件和其他静态资源一起打包上传到cdn上,后端服务器每次启动时,先到cdn上获取这个json文件,然后存到内存里
    wget --tries=3 --quiet -O manifest.json http://static.demo.cn/demo/manifest.json?`date +%s` ## 防止缓存
    • 方案的优点:简单方便,每次前端打包,manifest.json就会自动更新,上传到cdn同时覆盖前一个版本。
    • 方案的缺点:如果manifest.json更新了,后端则需要重启服务以便获取新的配置,当集群多的时候,重启耗费的代价可能很大。

    2. 将manifest.json的内容放在配置中心里,后端则需要接入配置中心。每次CI打包后,调用配置中心更新接口,后端就能自动获取最新的配置。

    在我平时工作项目中,这两种方案均有实现。

    Node中间层

    使用Nginx部署时,为了解决跨域问题,我们一般需要配置proxy_pass指向提供api的后端服务。

    后端采用了SOA,微服务的架构时,proxy_pass指向的api服务器,其实本质也是一个转发服务。

    前端ajax请求

    // 获取商品列表
    ajax.get('/api/queryProductList')
    
    // 获取价格列表
    ajax.get('/api/queryPriceList')
    

    Nginx转发

    location /api {
        proxy_pass https://demo.com; #后台转发地址
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   X-Real-IP         $remote_addr;
    }

    接口转发到  和 

    查询商品列表和查询价格列表其实是由两个不同的soa服务提供:

    • 查询商品: product.soa.neko.com
    • 查询价格: price.soa.neko.com

    因此,本质上https://demo.com这个服务也只是用来转发接口,同时对数据做部分的组装。那么这个服务,就可以用Node中间层来替代。使用了Node中间层,模板的渲染也可以从Nginx转移到Node了。

    当然,多了一层Node,对于前端的综合要求也随之提高,后端的部署,监控,日志,性能等等问题也随之而来,全(干)工程师应运而生。

    工作现状

    我司大部分to C的前端项目,都是采用Node层渲染模板加转发接口的开发模式,还有少量项目采用Java tomcat渲染html模板。

    大部分页面都是多页应用,并不是典型的单页应用。

    Node层渲染模板,又分两种情况:

    1. 需要支持SEO,则采用传统的模板渲染,填充展示数据。但是JS的业务代码,依旧前后端分离,并不在Node项目里。这类页面,一般都是采用Jquery+webpack模块化打包。
    2. 不需要支持SEO,则Node只渲染一个空html模板,页面内容完全由JS生成。这类页面,一般采用最新的前端MVC框架,比如Vue和React。

    当然近几年比较流行的SSR方案,让Node渲染模板时可以直接使用Vue和React的同构组件,直出页面后,用户的交互体验又如单页应用般流畅,只能说:历史总是惊人的相似。

    从某种程度上说,SSR是一种向传统模式的回归,不过这种回归并不是倒退,而是一种螺旋式的发展。

    实战

    理论知识讲了那么多,现在我们来实战一下。在上一篇

    深红:webpack多页面打包实践​zhuanlan.zhihu.com图标

    里,我介绍了webpack多页打包的原理,同时搭建了一个简单的webpack4-boilerplate。这个模板只是一个前端开发模板,其实它还对应着一个node后端模板koa2-multipage-boilerplate

    这个node项目最重要的就是实现了前面说的:如何读取manifest.json文件,动态渲染静态文件的引用路径,从而前后端分离开发和部署。

    详情见chunkmap.js这个koa2中间件的源码。

    const chunkmap = require('./chunkmap');
    app.use(chunkmap({
      staticServer: '//0.0.0.0:9001',
      staticResourceMappingPath: './mainfest.json'
    }));
    

    这个中间件接受两个参数

    1. staticServer:静态资源服务器地址,本地开发时,填写的就是webpack4-boilerplate这个前端项目启动的服务器。到了qa,产线时,则填写真正的cdn地址
    2. staticResourceMappingPath: 资源映射文件路径,也就是manifest.json文件

    本地开发时的manifest.json,不带hash值

    {
      "home.css": "/css/home/home-bundle.css",
      "home.js": "/js/home/home-bundle.js",
    }
    

    打包后的manifest.json,带hash值

    {
      "home.css": "/css/home/home-bundle.d2378378.css",
      "home.js": "/js/home/home-bundle.cb49dfaf.js",
    }
    

    使用了这个中间件后,koa的ctx.state全局变量上就带有一个bundle属性,里面的内容是:

    {
      "home.css": "//0.0.0.0:9001/css/home/home-bundle.d2378378.css",
      "home.js": "//0.0.0.0:9001/js/home/home-bundle.cb49dfaf.js",
    }
    

    然后通过模板引擎,动态渲染出实际页面。当然你也可以在页面中动态生成展示内容,从而支持SEO。

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
      <title><%= title %></title>
      <link href="<%= bundle['home.css']%>" rel="stylesheet">
    </head>
    
    <body>
      <div id="app"></div>
      <script src="<%= bundle['home.js']%>"></script>
    </body>
    </html>

    总结

    前后端分离带来了工作效率上的提高,Node中间层则给前端打开了一条走进后端的道路。当然机遇总是与挑战并存,在前端技术日新月异的今天,真想说一句:老子学不动了!

    转发:https://zhuanlan.zhihu.com/p/110038951

  • 相关阅读:
    csharp: mappings using Dapper-Extensions+Dapper.net.
    SQL Anywhere5.5: Metadata
    Csharp: read Sybase SQL anywhere5.5 using c#
    Sybase SQL anywhere5.5
    Spark基本概念
    Spark之RDD(含Java运行环境配置)
    Spark简介及安装
    Scala编程进阶
    Scala面向对象
    Scala基础
  • 原文地址:https://www.cnblogs.com/linyinmobayu/p/14074999.html
Copyright © 2020-2023  润新知