前后端分离和模块化

前后端分离和模块化-58到家微信首页重构之路

微信钱包内的58到家全新首页已经上线,感兴趣的同学们可以在微信中打开“我的->钱包->58到家”查看。

58到家全新首页提出重构主要是为了解决以下问题:

  1. 每个城市开通的服务项目不同,有些内容是写死在tpl中,维护非常头疼;
  2. 开通新服务或者某些UI调整(比如更换服务项的图片造成更改雪碧图)时必须走代码上线流程;
  3. 原有的前端切图、后端写逻辑的开发模式造成开发周期拉长和上线流程繁琐;
  4. 原有配置后台操作复杂,且可配置细节不完善;
  5. 首页加载速度太慢,用户体验欠佳。

58到家目前两年左右的发展期,整个技术生态还不完善。以上的问题有的是由于创业初期遗留的历史原因造成,比如代码写死和粗糙的配置后台;而有的问题是由落后的开发模式和协作模式造成的,比如前后端分工不明确、首页加载速度慢。

基于上文提到的问题,重构从以下几方面入手:

  1. 完善配置后台,细化可配置项;
  2. 数据驱动UI,轻量化tpl,内容更新无需上线流程;
  3. 前后端分离,缩短开发周期,简化上线流程;
  4. 模块化开发,提高加载速度,同事增强代码的可维护性。

配置后台可以理解为一个简易的CMS系统,配置的内容是一些量化的字段,比如图片地址、链接、价钱等等。此项目中本人并不负责配置后台的开发,所以不再班门弄斧。

下面详细描述重构过程中前端的解决方案。

1. 技术选型

根据上文提到的问题,此项目中前端的技术选型如下:

  • 客户端(浏览器)

    • 使用Vue作为渲染框架(数据驱动UI);
    • 图片懒加载使用Vue-lazyload实现;
    • 模块化方案使用CommonJS;
    • 因为首页没有很多的用户交互功能,大部分是链接跳转,所以不使用第三方的touch event工具;
  • 开发环境
    • 使用58到家前端工程框架boi作为开发和构建工具;

看到以上的技术选型,可能会有读者疑惑:不就是一个前端模板+模块化方案吗,有什么值得介绍呢?

首先,以上的技术选型的背景如下:

  1. 目前58到家FE团队统一使用vue作为开发框架,组件易复用;
  2. 此次重构后的58到家首页并非SPA,选用vue的另外一个原因是为了后续的SPA化做预备;
  3. 客户端渲染html的缺点是首次进入页面加载较慢,但利用浏览器缓存机制可以另再次进入页面的加载时间大大缩短;
  4. 选用CommonJS实现按需加载(load on demand),首屏以外的内容在首屏渲染完成之后加载;
  5. boi是58到家前端工程框架,以webpack为构建内核,选用CommonJS另一个原因是webpack的原生支持。

2. 前后端分离方案

目前58到家的前后端协作模式仍然很原始,本次重构采用的前后端分离方案并非是最优解,只能算是一种折中的过渡方案。总结有以下几点:

  1. 初始tpl中包含以下部分:

    • js、css引用;
    • 页面初始数据;
    • vue组件容器;
    • 统计用初始数据。
  2. 客户端采用vue作为渲染html;
  3. js和css更新时,FE独立部署静态文件,RD需要将url更新时间戳;

下面分别简单描述以上的几点。

2.1 轻量化tpl

tpl的内容如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
        <title>58到家</title>
        <link rel="stylesheet" href = "http://static.daojia.com/assets/project/wx_index/style/main.wx-index.css?2016082001">
    </head>
    <body>
        <div class="window">
            <app :data="data"></app>
        </div>
        <script type="text/javascript">
            // initial page data
            var pageData = {};
        </script>
        <script type="text/javascript">
            // for traker
            var bi_params = {pagetype:‘activity‘};
        </script>
        <script src="https://static.daojia.com/bi/buried_point/js/tracker.js"></script>
        <script src=‘http://static.daojia.com/assets/common/js/zepto.min.js‘></script>
        <script src=‘http://static.daojia.com/assets/common/js/vue.min.js‘></script>
        <script src = "http://static.daojia.com/assets/project/wx_index/js/main.wx-index.js?2016082001"></script>
    </body>
</html>
  • tracker.jszepto.min.jsvue.min.js是依赖的第三方文件,全局变量bi_params是bi统一用的初始字段;
  • <app :data="data"></app>是vue组件的容器;
  • tpl文件由RD维护,以上提到的两点是固定不变的,不需要维护。RD需要维护的包括:
    1. main.wx-index.jsmain.wx-index.css时间戳
    2. 全局变量pageData。这是首屏的初始数据,之所以选择以全局变量的方式暴露,而不是请求api,是为了减少一次http请求,尽快渲染首屏。

tpl轻量化是为了减少FE和RD的耦合,当然最佳的方式是tpl交由FE维护,但是目前58到家的开发模式并不适合。所以采用了折中的过渡方案。

使用url query作为js和css文件的缓存策略也并非最优解,理想的方案是使用hash指纹。但是hash指纹需要FE在编译完成之后将hash值告知RD,而时间戳可以任意修改成与当前不同的值即可,减轻了沟通成本和失误率。

2.2 客户端渲染

选择客户端渲染有以下几个优点:

  1. tpl轻量化,前后端解耦;
  2. 初始html数据量非常小,能够快速到达客户端。采用一些loading交互尽快给用户视觉反馈;
  3. js文件初次请求之后缓存到本地,只要不更新版本,后续每次进入页面后初次请求的数据就只有少量的html数据;
  4. 减小服务器(解析模板)压力;

当然客户端渲染也有一些缺点,比如:

  1. 性能比较差的设备执行渲染过程吃力,不过按照目前手机的迭代速度,这一点基本可以忽略不计;
  2. SEO不友好。但是这个项目是微信钱包的服务,并不直接提供外部浏览器入口,SEO可以不考虑。

具体到客户端渲染的技术选型,其实从实现功能上来讲随意选用一种js模板工具即可,比如artTemplate。最终选择vue的原因有以下几点:

  1. 数据驱动UI的方式利于编写清晰的逻辑;
  2. 为后续迭代做预备。后期有计划将整站SPA化,vue+vuex是比较不错的技术选型;
  3. 58到家FE团队统一使用vue,部分业务组件可复用;

2.3 更新和缓存策略

此次重构采用的缓存策略仍然比较原始,比如前文提到的url加query的方式。这也是后续有待优化的一个重点。

3. 模块化方案

3.1 客户端模块化方案

58到家首页的内容非常多,大部分尺寸的手机需要三屏才能加载完成。一次性加载的用户体验肯定不太顺畅,按照主流的手机尺寸,将整站分成三部分:首屏+次屏+尾屏。基本的加载流程如下图:

简单概括如下:

  1. 用户进入页面后,客户端发起第一次请求,服务端返回包含首屏json数据的html文档;
  2. main.wx-index.js根据首屏json渲染首屏;
  3. 首屏渲染完毕之后或者用户scroll到页面底部触发次屏js文件wx-index.themes.js的加载;
  4. wx-index.themes.js加载成功后发起jsonp请求次屏和尾屏数据;
  5. 渲染次屏;
  6. 次屏渲染完成之后或者用户scroll到页面底部触发尾屏js文件wx-index.tail.js的加载;
  7. wx-index.tail.js加载成功后渲染尾屏;
  8. 至此,流程结束。

次屏的wx-index.themes.js和尾屏的wx-index.tail.js是按需加载的,是为了减少首屏的请求数和数据体积。

按需加载功能使用require.ensureAPI实现。之所以选择使用它有以下几点考虑:

  1. 58到家前端工程框架boi构建内核基于webpack,webpack runtime内置require.ensureAPI的支持,不需要额外的模块化工具
  2. AMD方案(比如require.js)的按需加载模块不能定义模块名称(wx-index.themes.jsthemeswx-index.tail.jstail),而require.ensure可以定义模块名称,使文件名更语义化;

下面简单介绍一下如何结合vue和require.ensure实现按需加载和动态组件。

3.1.1 vue结合require.ensure实现按需加载和动态组件

回顾上文的tpl代码可以看出,页面整体是一个vue组件。顶层组件是<app></app>。首屏组件是第一级子组件,次屏是第二级子组件,尾屏是第三级子组件。整体结构如下图:

vue实现按需加载动态组件要考虑以下几点:

  1. 组件容器位置;
  2. 组件数据如何传递。

对比上图可以看出子组件容器的位置:

  1. Themes组件作为第一级子组件App的一个子组件,容器位置如下代码:

    <div class="main">
        <!-- activity banner -->
        <wx-activity v-if="data_activity" :data="data_activity"></wx-activity>
        <!-- nav -->
        <wx-nav v-if="data_nav&&data_nav.length!==0" :data="data_nav"></wx-nav>
        <!-- headline -->
        <wx-headline v-if="data_headline&&data_headline.length!==0" :data="data_headline"></wx-headline>
        <!-- service -->
        <wx-service v-if="data_service" :data="data_service" :test="test"></wx-service>
        <!-- fresh -->
        <wx-fresh v-if="data_fresh" :data="data_fresh"></wx-fresh>
        <!-- banner -->
        <wx-banner v-if="data_banner&&data_banner.length!==0" :data="data_banner"></wx-banner>
        <!-- themes -->
        <div class="wx-index__themes">
            <themes></themes>
        </div>
        <wx-footer :notice="data.showReddot"></wx-footer>
    </div>
  2. Tails组件作为第二级子组件Themes的一个子组件,容器位置如下代码:
    <template>
        <template v-for="theme in data.themes">
            <slider v-if="theme.type===‘slider‘" :data="theme"></slider>
            <single-slider v-if="theme.type===‘singleSlider‘" :data="theme"></single-slider>
            <list v-if="theme.type===‘list‘" :data="theme"></list>
            <grid v-if="theme.type===‘grid‘" :data="theme"></grid>
        </template>
        <div class="tail">
            <tail></tail>
        </div>
    </template>

wx-index.themes.js加载成功,在渲染Themes组件之前需要请求次屏的数据,jsonp请求放在vue组件的activate钩子函数内:

activate: function(done) {
        let _this = this;
        let _url = ‘/home/ajaxGetSecondIndexPage‘;
        let _cityId = pageData.cityId||$.fn.cookie(‘comm_cityid‘);
        let _openId = pageData.openId||‘‘;

        $.ajax({
            url: _url,
            data: {
                cityId: _cityId,
                openId: _openId
            },
            dataType: ‘jsonp‘,
            success: function(res){
                if(!res||!res.data){
                    return;
                }
                _this.data = Object.assign({},res.data);
                // 将底部固定模块的数据写入全局变量,以便懒加载所需
                window.dj_index_data_tail = Object.assign({},{
                    layidle: _this.data.layidle,
                    recommend: _this.data.recommend
                });
            },
            complete: function(){
                done();
            }
        });
    }

vue组件在activate钩子函数返回done()之后才会继续执行后续工作。

请求成功之后将返回的数据赋值给vue组件的data,然后vue根据data渲染UI。

上述代码有一点需要注意。大家看到代码将一些数据赋值给了全局变量window.dj_index_data_tail,这些数据是尾屏的数据。由于尾屏的数据量比较小,所以与次屏的数据合并成一个API。这个全局变量是为了尾屏的Tail组件渲染使用。这就是上文提到的“组件数据如何传递”。

使用全局变量传递数据的方式固然不是很优雅,但是不失为一个适合快速开发的方案。这也是后续迭代的优化点之一。

次屏渲染完毕之后触发尾屏的加载,这个行为实在Themes组件的ready钩子函数内进行,如下:

ready: function(){
    let loadTail = function() {
        if(!window.isTailLoaded){
            // 主题推荐位渲染完成之后加载底部模块
            require.ensure([], function(require) {
                require("../../_tail.js");
            }, ‘tail‘);
            window.isTailLoaded = true;
        }
    };
    loadTail();
}

由于之前将Tail组件的数据储存在全局变量中,Tail组件的activate钩子函数内可以直接读取次全局变量:

activate: function(done){
    this.data_layidle = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.layidle);
    this.data_rec = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.recommend);
    done();
}

以上,便完成了整个页面的按需加载流程。当然,每种方案都不是最优解,但都是适用于目前状态的一种比较好的方案,后续迭代中持续优化。

3.2 开发环境模块化方案

开发环境的模块化方案比较随意,借助于boi框架中的babel模块,可以将新规范的语法编译为浏览器适用的语法。

此次重构的开发环境的模块化开发使用的是ES6 Modules,语法简洁易懂,并且开发环境没有加载动态模块的需求,静态的ES6 Modules完全适合。

插播广告:58到家前端工程框架boi支持多种模块化方案,包括ES6 Modules、CommenJS和AMD。

4. 后续迭代需求

依前文所述,本次重构中的仍然有很多问题,这些问题是后续迭代中急需解决的。总结如下:

  • 工作流程优化

    1. 进一步解耦tpl层,实现前后端完全分离;
  • 代码优化
    1. 优化缓存策略,使用hash指纹代替url query;
    2. 优化vue组件间数据传递;
    3. 后台可配置化引起的零散图片太多的问题;
  • 用户体验优化
    1. 添加初始loading效果,增强用户体验;

5. 总结

58到家微信钱包重构项目告一段落,其中采用的诸多解决方案中有好的也有不好的。不过总体来说,此次重构中58到家技术团队向前端工程化、前后端分离迈出了标志性的一步。后续需要做的事情还很多,不论从项目本身,还是从团队整体架构的角度,都有很大的进步空间。也欢迎各位同行提出意见和建议。

时间: 2024-10-24 04:37:46

前后端分离和模块化的相关文章

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十六 ║ Vue前篇:ES6初体验 &amp; 模块化

缘起 昨天说到了<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║ Vue前篇:JS对象&字面量&this>,通过总体来看,好像大家对这一块不是很感兴趣,嗯~~这一块确实挺枯燥的,不能直接拿来代码跑一下那种,不过还是得说下去,继续加油吧!如果大家对昨天的小demo练习的话,相信现在已经对JS的面向对象写法很熟悉了,如果嵌套字面量定义函数,如何使用this关键字指向.今天呢,主要说一下ES6中的一些特性技巧,然后简单说一下模块化的问题,好啦,开始今天的讲

ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路

源码 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:http://129.211.40.240:6220 账户:admin 密码:admin 前端框架演示地址(临时) 地址:http://progqx5cu.bkt.clouddn.com/skins/index.html#/ 账户:admin 密码:admin 目录 1.开篇 2.快速创建一个业务模块 3.数据访问模块介绍 4.模块化实现思路 获取官方源码 为了方便查看源码,我们先获

架构设计:前后端分离之Web前端架构设计

在前面的文章里我谈到了前后端分离的一些看法,这个看法是从宏观的角度来思考的,没有具体的落地实现,今天我将延续上篇文章的主题,从纯前端的架构设计角度谈谈前后端分离的一种具体实现方案,该方案和我原来设想有了很大的变化,但是核心思想没变,就是控制层是属于Web前端的. 在以前文章里我说道前后端分离的核心在于把mvc的控制层归为前端的一部分,原方案的构想在实际的生产开发里很难做到,我觉得核心还是控制层和视图层的技术异构性,这样后果使得系统改造牵涉面太大,导致在项目团队里,沟通.协调以及管理成本相对较高,

[转] 前后端分离开发模式的 mock 平台预研

引入 mock(模拟): 是在项目测试中,对项目外部或不容易获取的对象/接口,用一个虚拟的对象/接口来模拟,以便测试. 背景 前后端分离 前后端仅仅通过异步接口(AJAX/JSONP)来编程 前后端都各自有自己的开发流程,构建工具,测试集合 关注点分离,前后端变得相对独立并松耦合 开发流程 后台编写和维护接口文档,在 API 变化时更新接口文档 后台根据接口文档进行接口开发 前端根据接口文档进行开发 开发完成后联调和提交测试 面临问题 没有统一的文档编写规范,导致文档越来越乱,无法维护和阅读 开

分享一个前后端分离方案源码-前端angularjs+requirejs+dhtmlx 后端asp.net webapi

一.前言 半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家.关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面. 二.技术架构 这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它火,而是因它的模块化.双向数据绑定.注入.指令等都是非常适合架构较复杂的前端应用,而且文档是相当的全,碰到问题基本上可以在网上都找到答案.所以前端基本思路就以angularjs为主.代码模块化,通过requir

nodeJS(express4.x)+vue(vue-cli)构建前后端分离详细教程

好想再回到大学宿舍,当时床虽小,房随小,但是心确是满的 ----致  西安工程大学a-114舍友们 转载请注明出处:水车:http://www.cnblogs.com/xuange306/p/6185453.html 没图片的教程都是耍流氓 准备工作: 安装nodejs ---还用我教了? 安装依赖包express4.x  点这里>>>nodeJS搭建本地服务器 安装vue-cli脚手架 点这里>>>vue-cli构建vue项目 这里强调一下,express是后端服务器

[开源] angularjs + Asp.net 前后端分离解决方案

本文版权归 博客园 萧秦 所有,此处为技术收藏,如有再转,请于篇头明显位置标明原创作者及出处,以示尊重! 作者:萧秦 原文:http://www.cnblogs.com/xqin/p/4862849.html 一.前言 半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家.关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面. 二.技术架构 这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它

前后端分离后的前端时代

本文从前端开发的视角,聊一聊前后端分离之后的前端开发的那些事儿.阅读全文,大约需要8分钟. 什么是前后端分离 除了前端之外都属于后端了. 你负责貌美如花,我负责赚钱养家 在传统的像ASP,JSP和PHP等开发模式中,前端是处在一个混沌的状态中,可以说是没有独立的"人格"可言. 前端负责切图和编写静态页面模板,后端将数据渲染到前端提供的页面模板中,最后将页面渲染到浏览器展示. 这个过程中,前端只提供页面模板或者写一些JavaScript脚本,有的甚至JS脚本都是后端来写,前端的作用只局限

关于大型网站技术演进的思考(十六)--网站静态化处理—前后端分离—下(8)

我第一次听说nodejs技术大概是在2009年年末,不过我真正认真在网络上进一步了解nodejs还是在2010年年中,当时对nodejs的认识和我现在对nodejs的认识有着天壤的区别,开始想了解nodejs我只是为了感慨谷歌公司开发的V8引擎居然如此强大,它不仅仅可以作为chrome浏览器的javascript内核运行平台,居然还能为服务端使用javascript语言作为平台,通过对nodejs的了解让我认识到chrome浏览器是如此的优秀,但是如此相对的是我并不认为javascript作为服