返回 登录
1

Angular产品构建性能测试

原文:Angular Production Builds

作者:Torgeir “Tor” Helgevold

翻译:Vincent

译者注:作者是一位从事于JavaScript开发的工程师,本文描述了作者立足于编译和绑定这两个因素,尝试不同方案对应用程序性能的影响。以下为译文。

在本文中,将讨论Angular应用程序的一些替代方案。将介绍几种不同的方法,并重点讲述它们的优点和缺点。

希望你能认真读完下面的内容,但是如果你觉得文章很长,不想细读,下面就展示了相关结果。

下面的表格显示了所有测试方案的结果:

测试结果

在研究静态assets的时候,Angular应用程序中最重要的影响因素就是编译和绑定。

编译

Angular会将特定语法通过编译转换为浏览器可以理解的纯JavaScript。它可以将模板语法(如ngFor,ngIf等)转换为纯JavaScript。

编译有两种不同的形式:动态编译和静态编译。它们的区别不在于编译了什么,而在于什么时候开始编译的。

动态编译是在应用程序被下载以后,在浏览器端进行编译的,这不仅意味着在应用程序被渲染以前需要做大量额外的工作,还意味着在应用程序运行时需要装载另外一个编译器。

静态编译是在构建时进行编译的,所以解决了上述这些问题。此外,另一个好处就是在Angular运行时我们再也不需要包含其它的编译器了,正因为如此,bundle的大小也就明显减小了。

站在生产应用的角度来说,动态编译可能不是一个好的选择,但是由于它为我们提供了一个低端基准线可以进行比较,因此我还是会举一些相关例子。

我们使用的demo是一个中等大小的应用,是由我的一些Angular例子组成的。

Angular的性能在移动设备上是有问题的,所以我将在Chrome中使用扼制“Good 3G”来模拟一个性能差的设备,从而进行演示。

为了简单起见,所有报告的加载时间都来自Chrome浏览器的网络标签的“Finish”值。

由于我已经部署了所有示例的版本,所以可以随意尝试不同的指标。

动态编译

由于低端基准线的原因,我已经将应用程序部署成了一个标准的动态编译的项目。

应用部署到了这里

正如你看见的那样,在应用被全部渲染之前,会有一个很明显的加载过程。如果你打开了浏览器的网络标签页,你就会知道原因了。

该应用程序需要发出163个请求,大约需要6秒才能完全加载。 相对于标准而言,这太慢了。

绑定的动态编译

就像我上面提到的那样,动态编译存在的一个问题就是仅仅为了加载应用它就需要发出163个请求。

在动态编译的构建期间,添加绑定应该就可以解决这个问题。

这次的样例位于这里

我使用了SystemJS-Builder来进行绑定。SystemJS-Builder是SystemJS的一种工具,但是它是一款独立的工具,完全脱离SystemJS模块加载器。

你可以看见,效果明显提升了。动态编译再也不会发出163个请求了,而且还很明显地缩小了负载(260kb)。尽管如此,总的加载时间还是超过了2秒。

尽管前进了一些,但是渲染时的停顿感还是很明显。

静态编译

动态编译的问题已经解决的差不多了,但是性能问题还是比较棘手。

幸运的是我们并没有因此就放弃了。我们还可以通过选择静态编译来改善性能,对JavaScript进行进一步的优化应该也可以。

接下来,我们将研究更切合实际的生产方式,看看它们是如何提高性能的。

绑定

静态编译在性能方面有很多优点,但是关于绑定还有很多需要认真考虑的事。

Rollup

在动态编译的构建过程中,我尝试绑定了CommonJS模块。虽然它很灵活,但事实证明它并不适合用来绑定。相反我们应该使用ES2105模块。

ES2015模块更适合于一种名为“Tree shaking”的技术。

Tree shaking是在应用程序代码中遍历导入和导出语句的过程。它是Tree shaking的一种很好的实现,所以它成为了很多人的选择。

关于Rollup版本的应用程序可以点击这里

正如你所看到的那样,Rollup这个版本明显比动态编译构建的版本更快。加载时间减少到大概只有1.3秒,大小只有147kb,比之前大概减少了43%,原因就是由于去掉了编译器和使用了Tree shaking。

Webpack

接下来我们将重复实验使用Webpack作为绑定。Webpack是另一种比较受欢迎的框架,但它的方法与Rollup是有着天壤的区别。

与Rollup相比,Webpack总是会产生一些较大的绑定。这是由Webpack会把每一个包含的模块整合成一个模块系统的方式决定的,这就导致了额外的函数包装器的绑定需要更多的开销。

Webpack不支持Tree shaking,这也就意味着没有任何机会去使用Tree shake了。Webpack与Tree shaking会有些令人混淆的内容,但是这篇文章会帮你理清楚的。

Webpack的相关程序在这里

你可以看见,使用Webpack的性能跟Rollup差不多,但是体积明显增大了(151k ~增长了2.7%)。

这4K的区别不值得引起我们的注意,但是由于额外的开销,Webpack总是会更大一点。

同样不值得注意的是Angular中的NgModule架构阻碍了Tree shaking。这可能是我们在Rollup和Webpack之间看不到更大差异的原因之一。

如果你的代码从同一个文件导出多个类,您还会看到Webpack大小的增加。这是Webpack最糟糕的用例,因为它不能摆脱未使用的导出类。

Closure 编译器

目前为止所有的结果看上去都挺好的,但是可改善的空间还是很大的。

Webpack和Rollup都是传统的绑定。除了Tree shaking(Rollup)和minfication之外,它们提供的代码优化很少。

为了更好的优化,我们可以添加Closure编译器。

与它们相比,Closure编译器的主要区别在于它将对应用程序进行更深入的分析。 它可以通过功能内联,函数扁平化和删除部分代码等方法从而进行压缩。 这比minifcation更有效,从Closure编译的过程中就可以体验到。

这次的样例位于这里

Closure仅仅只有97.4kb,比Webpack小了35%,太不可思议了!总的加载时间也就大概1秒钟。

Closure编译器的结果真是吓到宝宝了,但是这种优化是要付出一定代价的。编译器对您的代码做了几个假设。 除非您确保您的代码与Closure兼容,否则您的应用程序可能会中断。

Angular和其自定义的Typescript编译器使得编译变得容易一些了。 通过Angular的Typescript编译器,确保某些约定使得代码与Closure更加兼容。

Webpack

目前我们只讨论了单一捆绑应用。随着应用程序的发展,将整个应用程序作为单个JavaScript软件包进行服务可能行不通了。

Webpack跟其它的相比,提供了更多的灵活性。Webpack可以将应用程序分解成多个文件。这在您使用路由器的情况下就会更加突出了的,因为您可以在每个路由上创建一个包。这就给了你真正意义上的懒加载。

这就是Webpack提供的灵活性比其他选择都要多的地方。Webpack支持将应用程序分割成多个文件。当你在使用路由器的情况下为每个路由上创建绑定,就能看到它的优点了。这就给了你真正意义上的懒加载。

在最后一个例子中,我将演示应用程序转换为每个路由的绑定。

应用部署到了这里

您可以看出,当您浏览应用程序时,单独的软件包将按需加载。除了路由特定的捆绑包,还有一个“共享”捆绑包。 共享捆绑是101kb。 默认路由将一个3.1kb的捆绑包添加到初始支付负载。

由于初始负载的总数只有104kb,所以我们非常接近于Closure编译器示例的结果。加载时间也很相似,只是一秒多一点。

这里的关键是将负载分散到多个请求中,而不是单个母版负载。所有bundle的总和都超过了单个的大小,这并不重要。通过按需加载小的和快速的,应用程序将是非常快的。

我想提一下,理论上也可以使用Closure编译器,但是在编写的时候设置它并不简单。一旦我们将延迟加载添加到一个Closure 构建中,我们可以期望得到更好的结果。

本文所有的例子都可以在这里找到。

评论