更新: 本文本来的标题是“为什么咱们弃用AngularJS:……”,如今把它去掉了。由于这些痛点主要是针对单页JS应用框架的。有些人认为本文是专门批判AngularJS的,这可不是个人本意。-- Quinnhtml
几个月前咱们的Sourcegraph网站向公众开放,它是一个富AngularJS应用。服务器传输原始的HTML页面和JSON端点,剩下的就交给Angular来处理。这是一个建立Sourcegraph的简易方式,当时咱们不知道Sourcegraph会变成什么样。前端
可是单页JavaScript框架并不适用于每个站点。Sourcegraph是一个内容为主的站点,咱们渐渐发现富js应用弊大于利。富js应用的好处众所周知,下面是咱们体会到的一些意料以外的困难。但愿对面临相似选择的开发人员能有一些帮助。git
咱们早知道会面临不少的困难,可是不知道会有这么难。angularjs
搜索引擎爬虫和社交网站的预览抓取器不能加载纯Javascript站点,提供替代版本又慢又复杂。github
有两种方式能够容许爬虫阅读你的站点。你能够在服务器端运行一个浏览器实例来执行你的应用里的Javascript,而后从DOM中卸下HTML(使用PlantomJS或者WebLoop)。或者你能够建立一个服务端生成的专供爬虫的替代性HTML版本。web
第一个方法须要你为每个页面加载创建一个headless浏览器(或者tab),比起直接产出HTML,这样会花费不少的时间和系统资源。取决于你使用的框架,须要很多精力来决定何时页面已经准备好了。 你能够缓存页面,可是若是页面常常改变,那么缓存只能起到很是有限的优化做用,并且会增大复杂度。这个方法会将你的页面加载速度拖慢好几秒,对搜索引擎排名也不利。gulp
第二个方法(建立一个替代性的服务器端站点)对简单站点而言足够了,可是若是页面不少,这将是一个噩梦。何况若是Google认为你的服务器版本站点跟你的主站版本有很大的不一样,那他就会狠狠的惩罚你。糟糕的是,直到你的访问量直线降低的时候你才会意识到你已通过界了。segmentfault
不少分析工具须要使用易于出错、手工集成的HTML5 history API(pushState)来导航。这是由于它们没法自动检测到你的应用使用pushState导航到了新的页面。即便能够作到,它们仍然须要等待你应用的信号来收集新页面的其余信息(例如页面标题和其余页面特定的指标)。浏览器
你如何解决这个问题?同时取决于你的客户端路由库和你集成的分析工具。用Google分析和Backbone.js?尝试一下backbone.analytics。用Heap(顺便说一下,Heap很棒)和UI-Router?设置你本身的$stateChangeSuccess
钩子而后调用heap.track缓存
还没完!你想追踪起始页面加载?也许你重复跟踪了?你会跟踪失败的页面加载吗?若是你使用replaceState代替pushState呢?即便要获知你是否错误地配置了分析钩子——或者是否依赖升级搞乱了系统——也是至关困难的,除非交叉检查分析。当你发现问题后,很难去恢复你错过的分析数据(或者消除重复数据)。
前端JavaScript构建工具,例如Grunt,须要复杂的配置并且会很慢。还好咱们有像ng-boilerplate这样出色的项目来帮忙,可是它们很慢。而且若是你想添加一个自定义的步骤的话你仍是没法避免复杂性。(我为何说Grunt复杂,看看这个配置文件就知道了。)
一旦你配置好了你的应用,包括Gruntfiles等等。你仍然要忍受漫长的JavaScript构建时间。你能够把dev和production构建通道分开来提升开发速度,可是你终将深受其苦。用AngularJS尤为如此,他须要在压缩代码前使用ngmin(若是你用了特定功能)。事实上,咱们有几回就是由于这些压缩的JavaScript和开发时的代码表现不一样而把SourceGraph搞砸了。
事情正在改善,Gulp是一个巨大的提高。
测试JavaScript-only的站点须要使用基于浏览器的测试框架,好比Selenium,PhantomJS,或者WebLoop。安装这些(除了PhantomJS)一般意味着安装WebKit和Java依赖,配置Xvfb(虽然新版的PhantomJS移除了这些依赖),也许运行一个本地的VNC客户端和服务器来测试。最后,你还须要在持续集成服务器上配置这些东西。
相反,测试服务器端生成的页面一般只须要类库来获取URL和解析HTML,安装和配置要简单许多。
一旦你开始编写浏览器测试,你必须处理异步加载。你不能在页面尚未加载的时候就测试页面上的元素,可是若是在一个特定时间段里没有加载,你的测试就会失败。浏览器测试类库提供了一些帮助函数来处理这种状况,可是对于复杂页面它们只能帮上一点小忙。
你想组合很重的浏览器测试工具(Selenium,加上Firefox或者Webkit)和很大的测试复杂度(因为浏览器测试的异步本性)?你的测试须要不少配置,很长的时间来运行,并且很不可靠。
在富JavaScript应用中,页面转换几乎是瞬间发生,而后全部的特定元素异步加载。服务器端应用偏偏相反:页面在服务器端加载完成前不会发送到客户端。
听起来彷佛是客户端应用胜利了,可是也许这不过是一个假装的诅咒。
考虑客户端JS应用,当用户点击一个连接,页面会马上加载并呈现。若是用户导航到一个侧边栏须要5秒钟才能够加载的页面,第一眼感受很快,可是若是用户须要的信息在侧边栏里,对用户来讲就太慢了。即便你须要的特定内容能当即加载,你仍须要忍受转动的加载指示器和页面填充时的抖动。
如今考虑一下这样的状况:若是开发人员想在那个页面添加新功能。很难肯定这个功能是否必须快速加载——由于一切都是异步的,因此谁会在乎页面底部过了几秒才加载呢?如此反复几回,整个站点就会让人觉察到迟缓和抖动。
在服务器端应用中,若是一个API调用很慢,整个页面就会阻塞直到页面完成。服务器端的缓慢不可能被忽视,由于这很容易被测量,而且会公平地影响每个人。可是在客户端应用中这很容易被忽略。
你能够争论说,一个好的开发团队应该避免这些错误,而且客户端 JS 框架不是罪魁祸首。这是对的,可是整体上来讲,客户端JS框架下降了迟缓的开销。这一点触动了开发团队的激励机制。
上面说的问题,自己都不算大问题。咱们能够作不少工做来减轻上述状况(事实上咱们确实作了不少)。可是,这些问题加在一块儿就是另外一回事了,能够说,客户端JS框架成为了咱们开发工做的一大负担。
同时要牢记,每个站点都是不一样的。例如,Sourcegraph是一个内容站点,这意味着页面在加载后不会有太多的变化(和富应用相比)。咱们依然喜好这些技术,可是它们不是构建咱们的主站的合适工具。
原文 5 surprisingly painful things about client-side JS
翻译 SegmentFault