返回 登录
0

开发MeetSpace,我们为什么没有选用任何前端框架?

原文: Why We Didn’t Use A Framework (Case Study)
作者: Nick Gauthier
译者: 孙薇

刚开始构建MeetSpace这款适用于异地协作的视频会议App时,我们同样需要作出决策——该选用哪个技术栈?在收集需求并审查了团队技术配置之后,我们决定采用Vanilla JS,以便回避前端框架的问题。

通过这种方案,我们得以创建了一款处理速度极快、非常轻量级,外加后期维护省心的Web应用——MeetSpace。页面加载平均只需处理1个缓存请求,外加2KB的下载量,整个页面会在200毫秒内加载完毕。下面我们进行具体分析。

需求:较高

整个业务中最重要的需求就是构建一个更好的视频会议工具。虽然存在很多的视频会议解决方案,但它们都面临着类似的现实问题:可靠性、连通性、通话质量、速度以及易用性。根据我们初步的调查,很明显我们面临最重要的问题就是如何保证音频无损、延迟低、保真度和可靠性高。

我们还发现,“假如能够提供优秀的视频会议体验”,绝大多数用户(调查案例中有95%)都很愿意使用Chrome浏览器。这也代表着我们可以使用前沿的WebRTC技术来达成我们的目标,而无需为多个原生平台做开发,从而减少了工作量。鉴于对WebRTC的支持,我们决定将Chrome和Firefox设定为目标平台,未来还可能移植到Safari和Edge上。

根据我们的技术经验,想要达成高质量、可靠音频的最佳途径就是尽可能降低App对CPU和网络的使用。这样一来,如果何时网络传输达到峰值,也还有空间剩余,不会导致通话卡顿或断线。这意味着我们需要尽可能保持应用的轻量级及快速运行,使其只占用必要的CPU或网络流量,而且在出错时能够快速重新载入。

这也是我们采用Vanilla JS而不是大型框架的重要原因之一。我们需要对应用的量级、启动速度以及空闲CPU使用(包括改变元素、重绘等)有较高的控制能力。这也是许多应用不具有的低层次需求。如今很多应用供应商都在追求更优秀的UI和UX,更多功能以及更高的易用性。

在此案例中,我们需要对视频通话UX有100%的控制能力,以确保在UX尽可能轻量级的前提下,将其它所有的资源优先供给通话。此外,用户的笔记本也不会因为风扇速度开到最大而“成了直升机”。

不过这个话题不是本篇的重点,想要了解更多WebRTC的内容,可以点击这里查看另一篇文章《通过修改SDP负载调整WebRTC带宽》。

小型到中型的UX需求

下一步,我们针对每个页面做了粗略的设计,并列出了各个页面之间的交互清单。从小型到中等级别的交互都有涉及,包括最复杂的两个页面。下面我们来稍作了解:

小型交互:链接复制

我们的小型交互有很多,其中一个是通过按钮复制链接。

图片描述

图1 输入URL元素,点击按钮复制链接

这里我们有一个简单的<input>元素,包含一个点击后就会复制链接的“Copy”按钮。对于这类交互,我们可以使用常见的风格:使用jQuery插件的渐进增强模式。页面载入时,我们会寻找代表组件的特定选择器,并通过“点击复制”功能来高亮。由于非常简单,下面列出了整个插件的代码:

<form data-copy=true>
  <input type="text" value="{url}" data-click-select-all />
  <input type="submit" value="Copy" />
</form>
// Copy component
(function() {
 window.addEventListener("load", function() {
   var els = document.querySelectorAll("[data-copy]");
   for(var i = 0; i < els.length; i++) {
     var el = els[i];
     el.addEventListener("submit", function(event) {
       event.preventDefault();
       var text = event.target.querySelector('input[type="text"]').select();
       document.execCommand("copy");
     });
   }
 });
}());
// Select all component
(function() {
 window.addEventListener("load", function() {
   var els = document.querySelectorAll("[data-click-select-all]");
   for(var i = 0; i < els.length; i++) {
     var el = els[i];
     el.addEventListener("click", function(event) {
       event.target.select();
     });
   }
 });
}());

组件实际上由两个不同的组件构成:copy组件和select-all组件。在点击时,select-all组件会选中并高亮全部输入的内容。这样一来,用户可以点击输入区域,并通过按下Ctrl/Command+C来复制。或者通过copy组件,在点击“Copy”按钮时触发表格提交操作,系统拦截提交操作,并复制所提交的内容。

这些交互都是小型交互,因此我们认为小型的Vanilla组件是实现该功能最简单、最快速也最明确的方式。在编写时,我们在应用中植入了15个类似的小型组件。

中型:操作面板和聊天页面

经过确认,应用中有两个页面有较高的UX需求,我们难以确定是使用Vanilla JS、小型数据库还是大型框架比较好。首先,我们研究了操作面板页面:

图片描述

图2 Meetspace的操作面板

在这个页面中,主要的动态部分是聊天室中的人像。我们希望实时反馈各个聊天室的参与者信息,因此每当有人进入或离开房间时,会通过WebSocket向所有参与者推送消息,并在聊天室中添加新进用户的肖像。我们决定采用简单的办法,在房间出现变化时,将所有参与者的信息推送给socket,然后清空所有人像,并重新渲染所有参与者的肖像。使用HTML非常简单,因此无需模板,只要少许div和img标签就可以了。最复杂的部分是动态的尺寸变更。最初我们就确定这里无需Vanilla JS。而不采用框架的情况下,WebSocket就成了部署起来最简单的办法。

下面是我们WebSocket交互的基础代码:

Dashboard.prototype = {
   connect: function() {
     this.socket = new WebSocket("/path/to/endpoint");
     this.socket.onmessage = this.onSocketMessage.bind(this);
     this.socket.onclose = this.onSocketClose.bind(this);
   },
   onSocketMessage: function(event) {
     var data = JSON.parse(event.data);
     switch (data.type) {
       case "heartbeat": console.log("heartbeat", this.roomID); break
       case "participants": this.participants(JSON.parse(data.data)); break
       default: console.log("unknown message", this.roomID, data);
     }
   },
   onSocketClose: function() {
     console.log("close", this.roomID);
     setTimeout(this.connect.bind(this), 1000);
   },
  // …
}

在构建Dashboard实例时,我们采用了socket,通过链接与后端相连。所传输的信息涵盖所有格式,{type: "type", data: { /* dynamic */ }}。在收到消息时,通过解析JSON切换类型,再调用合适的方法传输数据。如果socket关闭,就在一秒后尝试重连,这样可以保证在用户网络卡顿或服务器重启(因为部署)的情况下,也能保持网络连接了。

下面我们要来处理应用中最复杂的页面——视频聊天室页面了:

图片描述

图3 MeetSpace的视频聊天页面

在这里我们遇到了无数挑战:

  • 用户加入/离开聊天室;
  • 用户开启/关闭静音;
  • 用户打开/关闭视频;
  • 为不同数量的参与者定制“无边界”的布局(纯粹的CSS无法做到);
  • WebRTC视频采集,以及P2P流媒体;
  • 通过WebSocket同步所有客户端的以上状态。

其中DOM操作不是很多,只需给每个参与者添加元素,但WebSocket和WebRTC的同步工作量非常大。DOM和WebSocket事件的操作都没什么问题,但在WebRTC方面,由于浏览器存在差异,而WebRTC并未完全解决这个问题,这里还需要一些其它协助。最终我们选用了WebRTC adapter.js正式版,原因在于:我们对于自身的解决方案很有信心,再加上该技术无需很多时间学习,部署也非常简单。

假设:代码越少=工作量越少

除了之前做过的所有研究之外,我们还作出了假设,想要测试一下能否通过减少代码量——包括来自第三方的和我们自己的代码,将从头开始部署的整体工作量减少?在面对较小或者中等工作量负荷时,这一假设是正确的。由于我们只有几个页面和少量UX,最终我们决定冒险尝试一下在只有少量依赖的情况下开工。以前我们都曾使用过框架,也乐于使用许多不同的框架,但这样做总是有代价的。

在使用框架时,会有以下代价:

  • 学习框架;
  • 根据需求定制(即使用框架);
  • 之后的维护(升级);
  • 如果存在Bug,需要深入研究(最后甚至成了专家)。

与此相反,从头开始则有另外一些代价:

  • 构建应用——相对学习和定制框架;
  • 重构代码——相对定制框架;
  • 重复工作——解决别人在框架中已经找到并修复过的Bug。

根据推测,对于我们来说,从头开始可能工作量会更小一些。一开始的工作量较多,但学习成本较低,也不会有太多困惑产生;自己写的代码可能会多一些,但是考虑到依赖的话,我们总体的代码量还是更少的;无需深入研究,所以Bug易于修复;而且一般来说维护的工作量也更少。

结果

到现在MeetSpace已经有一年历史了,而它的稳定性和可靠性让我们感到惊讶。虽然我们不该用“惊讶”这个词,但是凭心而论,我们本以为会有更多问题产生的。事实上,保有完整的错误追踪体系,而且不用研究框架,会让Debug过程更为简单,而且总体代码量越少,问题也越少。另一个“有意思的”好处在于,如果有问题,我们马上能知道是我们自身的问题,而无需费劲定位问题产生的责任方,反正都是我们自己的代码。修复起来也很迅速,因为我们对这些代码非常熟悉。

如果看一下我们的提交曲线图,就知道最开始我们的工作量很大,但是之后只有提交功能时会有大的波峰产生:

图片描述

图4 随着时间推移,MeetSpace的代码提交曲线

我们没有大型的重构或升级工作,寥寥几次升级数据库也没出过错,因为都是小型升级,不影响数据库的API。想要对比“假设使用框架完成项目”的工作量是很困难的,因此我们将其与之前的项目进行了对比。根据我的直觉,如果我们使用框架,由于花费在学习框架、在整个框架的代码中跟踪Bug、跨框架版本进行升级上的时间会更多,总体来说所花费的时间会更长。在这个项目中,之后的Bug追踪还有升级都没什么工作量。

现在我们看一下真实的数据:速度和大小这里我会讨论到三个页面:登入、控制面板和聊天室。登入页面绝对是最轻量级的页面,只是一张表格,没有用户上下文。控制面板是我们静态页面中量级最高的页面,聊天室是我们动态页面中量级最高的页面。

图片描述

图5 “冷启动”指代初次访问,缓存为空的时候;“热启动”指代之后有缓存的时候

由于初次冷启动后,会缓存所有JS、CSS和图片,之后的启动只需请求并发送页面的HTML即可,没有任何其它请求,包括数据库、模板、分析、指标……这些都没有。每个页面从点击到加载之间的耗时大约是100毫秒,整个网站的速度很快,就像是单页应用的运行速度。我们将Cache-Control header设定在以后,以完全缓存,关于HTTP缓存还有另一篇英文文章可以参考。

页面的主要加载就是等待服务器处理请求、渲染并发送HTML。而且我们的后端也基本上是全vanilla,没什么框架;使用Go语言构建,平均响应时间是8.7毫秒。

请注意: 本文数据及这里的基准取自MeetSpace的内部应用,而不是真正的公众页面,在公开页面中,可能会有一些改进。

还会再这样做吗?

对于MeetSpace项目来说,回答是肯定的。但对于其它项目而言,在我看来,我们提前所做的一切研究,大多关于成本及结果的假设都指向一个非常简单的权衡:复杂性。

应用本身越小越简单、量级越轻,从头开始编写代码就越划算也越简单,随着应用、团队/公司的复杂度和大小增长,所需要的地基就可能越大。在大型应用中,使用标准框架所获得的好处可能更多。同样,在大型团队中使用该框架的通用语言也会带来更大的益处。到了最后,作为开发者的我们需要作出权衡,才能作出决定。


2017年7月8日(星期六),「“前端开发创新实践”线上峰会」将在 CSDN 学院召开。本次峰会集结来自Smashing Magazine、美国Hulu、美团、广发证券、去哪儿网、百度的多位国内外知名前端开发专家、资深架构师,主题涵盖响应式布局、Redux、Mobx、状态管理、构建方案、代码复用、个性化图表定制度等前端开发重难点技术话题。技术解析加项目实战,帮你开拓解决问题的思路,增强技术探索实践能力。全天六场深度技术分享,现在仅需169元,限时优惠中,详情点击峰会官网

图片描述

评论