QBao-Web架构详解

qbao-web2.0

概述

前后端分离是这两年很热门的提法。

随着HTML5/CSS3广泛推广,前端框架不断进化,前端开发越来越专业化,自然想要摆脱后端的束缚,NodeJS的出现,也让前端工程师看到更多的可能。同时后端工程师也希望更多地专注于后台业务逻辑,而不是页面性能或是多浏览器适配。所以前后端分离,就是让“专业的人做专业的事”。

前后端分离不是一定要采用NodeJS做服务器,也不是一定要采用AngularJS,相反的,盲目引入新技术很容易让项目陷入困境,让开发人员不知所措,最好能采用一些温和的方式一步步来。

方案

  1. 采用Ajax模式,应用服务器只提供数据,由静态服务器或CDN承载HTML/CSS/JS文件,使用JS进行Ajax请求数据,动态生成最终页面。

    这里对网站架构师提出了新的要求,需要定义完备的Restful接口。

  2. 不使用AngularJS重型前端框架,只使用前端工程师最熟悉的jQuery类库,同时引用HandlerBars模板语言辅助生成页面。

  3. 模块划分上使用NodeJS进行html暴力插入,可以考虑换成Jade模板语言。

  4. 只做多页面方案,尽量不做页面内跳转,避免引入前端路由。

详解

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title></title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- build:css ../components/components.min.css -->
  <!-- components:css -->
  <!-- endinject -->
  <link rel="stylesheet" href="../common/styles/base.css">
  <link rel="stylesheet" href="styles/index.css">
</head>
<body>
  @@include('../components/site-top/site-top.html')
  @@include('../components/site-header/site-header.html')
  <div id="banner" class="container"></div>
  <div class="main container clearfix">
    <div id="auction-list" class="list"></div>
    <div class="right-side">...</div>
  </div>
  ...
  @@include('../components/site-footer/site-footer.html')
  <script id="banner-template" type="text/x-handlebars-template">
    { {#each list} }
      <div>
        <a href="{ {target} }">
          <img src="{ {path} }" />
        </a>
      </div>
    { {/each} }
  </script>
  <script id="auction-list-template" type="text/x-handlebars-template">
    <ul>
      { {#each list} }
        <li class="item">
          <p class="name">{ {product.name} }</p>
          <section class="content">
            <img class="picture" src="{ {picture} }" />
              <div class="price-bar">
                <div class="value">
                  <img src="images/rmb-icon-gray.png" />
                  <p class="number">{ {value} }</p>
                  <p class="note">市场价</p>
                </div>
                ...
          </section>
          <footer>
            <ul>
              <li>
                <p><span>{ {register} }</span>人</p>
                <p class="note">报名人数</p>
              </li>
              ...
            </ul>
          </footer>
        </li>
      { {/each} }
    </ul>
  </script>
  <script src="../bower_components/jquery/dist/jquery.min.js"></script>
  <script src="../bower_components/handlebars/handlebars.min.js"></script>
  <!-- components:js -->
  <!-- endinject -->
  <script src="scripts/index.js"></script>
</body>
</html>

这是一个很典型的页面,雷拍列表。

  1. 公用元素

    由于没有后台服务器为我们组装页面,我在这里采用了自定义语法来引入公共控件,比如页头和页尾。

     @@include('../components/site-top/site-top.html')
     @@include('../components/site-footer/site-footer.html')
    

    这不是标准的html语法,所以这个页面也无法直接在浏览器中加载,需要使用NodeJS预编译成HTML发布。

    这里的NodeJS作为编译工具出现,而不是服务器,理论上也可以使用NodeJS原配的Jade模板语言来完成,不过需要引入一个不那么像HTML语法的生成器,不是很爽。

    有了这种设计,我们就不需要将公用元素在每个页面中重复,可以最大化地复用,也能方便地统一修改。

  2. 动态元素

    动态元素由HandlerBars模板生成,比如这个Banner:

     <script id="banner-template" type="text/x-handlebars-template">
       { {#each list} }
         <div>
           <a href="{ {target} }">
             <img src="{ {path} }" />
           </a>
         </div>
       { {/each} }
     </script>
    

    选用HandlerBars,一是因为它的语法很接近HTML,二是因为它非常快。

    数据获取通过jQuery的ajax函数:

     var loadBanner = function() {
       var source = $('#banner-template').html();
       var template = Handlebars.compile(source);
       $.get('http://localhost:3000/banners/54c98efa2d2c4b7517e8c63f').done(function(resp) {
         var urlList = $.map(resp.pictures, function(picture) {
             picture.path = 'http://localhost:3000/uploads/' + picture.name;
              return picture;
         });
         var html = template({
             list: urlList
         });
         $('#banner').append(html);
       });
     };
    
  3. 目录结构

    模块化地组织目录结构,可以方便地分人员开发和负责。

    • 顶级目录

      • app 存放页面模板
      • dist 存放发布的最终页面,由自动化构建系统生成
      • test 自动化测试用例
    • 二级目录

      • components 公共模块,页头、页尾、通用控件等
      • common 公共资源,如网站的基础css
      • 其他 各子模块,如雷拍、宝筹、任务中心等
    • 三级目录

      • index.tpl.html 子模块的首页模板,配有同名的js和css文件
      • detail.tpl.html 子模块的其他页面模板,配有同名的js和css文件
  4. 自动化构建

    使用Gulp搭建自动化构建环境,除了前述公用元素插入,自动化构建还支持:

    • 静态代码检查
    • JS和CSS文件的连接和压缩
    • 图片的压缩优化
    • 雪碧图和相应的CSS生成
    • 浏览器自动重载等

      细节在之后统一写一篇专题介绍。

  5. 改进点

    • 需要考虑根据团队接受能力引入SASS,提升CSS架构能力
    • 需要考虑把HandleBars模板预编译成JS再发布,可以大幅提升渲染速度

小结

优点

  1. 最容易落地的技术方案,避开AngularJS的学习曲线,只需要学习HandlerBars模板语言

  2. 简单粗暴的前后端分离,为之后的架构转型做准备,之后的进化就是前后端分别进行的了

缺点

  1. 首页加载速度不如服务器渲染,用户会看到数据加载过程

qbao-web2.1

随着项目越来越大,页面越来越多,web2.0的构建系统慢慢吃力起来,动辄要10分钟才可以构建一遍,而且要上传到构建服务器构建,成功后再从服务器上下载下来,构建周期太长,已经违反了架构的初衷。

针对这一情况,我们推出了web2.1框架,主要优化如下:

  1. 加速构建过程,在开发者本机就可以一键构建,自动生成Dist和CDN文件,并且在半分钟内构建完毕;

  2. 使用Reset.css替换Normalize.css;

  3. 控件支持按项目增删配置,而不是默认全部包含;

优化仍然是围绕着"自动化构建"开展的。