推迟加载和parsingPrimeFaces JavaScript文件
在分析使用Google PageSpeed的JSF 2.1 + PrimeFaces 4.0 web应用程序的性能时,build议除其他外推迟parsingJavaScript文件。 在带有<p:layout>
的testing页面上,以及带有<p:watermark>
和<p:fileUpload>
,如下所示:
<p:layout> <p:layoutUnit position="west" size="100">Test</p:layoutUnit> <p:layoutUnit position="center"> <h:form enctype="multipart/form-data"> <p:inputText id="input" /> <p:watermark for="input" value="watermark" /> <p:focus for="input" /> <p:fileUpload/> <p:commandButton value="submit" /> </h:form> </p:layoutUnit> </p:layout>
它列出了以下可能被推迟的JavaScript文件:
-
primefaces.js
(219.5KiB) -
jquery-plugins.js
(191.8KiB) -
jquery.js
(95.3KiB) -
layout.js
(76.4KiB) -
fileupload.js
(23.8KiB) -
watermark.js
(4.7KiB)
它链接到这个Google Developers文章,其中介绍了延迟加载以及如何实现它。 您基本上需要在window
的onload
事件期间dynamic创build所需的<script>
。 在最简单的forms下,旧的和bug的浏览器完全被忽略,看起来像这样:
<script> window.addEventListener("load", function() { var script = document.createElement("script"); script.src = "filename.js"; document.head.appendChild(script); }, false); </script>
好的,如果你能控制这些脚本,这是可行的,但是列出的脚本都是被JSF强制自动包含的。 另外,PrimeFaces将一些内联脚本呈现给HTML输出,它们直接从jquery.js
调用$(xxx)
,从primefaces.js
调用PrimeFaces.xxx()
。 这意味着它不可能真正推迟到onload
事件,因为你只会以$ is undefined
和PrimeFaces is undefined
错误结束。
但是,这在技术上应该是可能的。 鉴于只有jQuery不需要延期,因为许多网站的自定义脚本也依赖于它,我怎么能阻止JSF强制自动 – 包括PrimeFaces脚本,以便我可以推迟他们,我怎么能处理这些内联PrimeFaces.xxx()
调用?
使用<o:deferredScript>
是的,可以使用自OmniFaces 1.8.1以来新增的<o:deferredScript>
组件。 对于技术上感兴趣的,这里涉及到的源代码:
- UI组件:
DeferredScript
- HTML渲染器:
DeferredScriptRenderer
- JS帮手:
deferred.unminified.js
基本上,组件会在postAddToView
事件期间(因此,在视图构build期间)通过UIViewRoot#addComponentResource()
将自身添加为<body>
结尾的新脚本资源,并通过Hacks#setScriptResourceRendered()
通知JSF脚本资源已经被渲染了(使用Hacks
类,因为没有标准的JSF API方法),所以JSF不会强行自动包含/渲染脚本资源。 在Mojarra和PrimeFaces的情况下,为了禁用资源的自动包含,必须设置具有name+library
键和值为true
的上下文属性。
渲染器将使用OmniFaces.DeferredScript.add()
编写一个<script>
元素,从而传递JSF生成的资源URL。 这个JS帮助程序将依次收集资源URL,并在onload
事件中为它们中的每一个dynamic创build新的<script>
元素。
用法相当简单,只需使用<o:deferredScript>
与<h:outputScript>
相同的方式,使用library
和name
。 放置组件的位置并不重要,但大多数自我logging将会在<h:head>
的末尾 ,如下所示:
<h:head> ... <o:deferredScript library="libraryname" name="resourcename.js" /> </h:head>
你可以有多个,他们最终将被加载在他们宣布的相同的顺序。
如何在PrimeFaces中使用<o:deferredScript>
?
这实际上是因为所有由PrimeFaces生成的内联脚本,但仍然可以通过帮助脚本来实现,并且接受jquery.js
不会被延迟(它可以通过CDN提供,请参阅后面的内容)。 为了覆盖PrimeFaces.xxx()
调用的primefaces.js
文件, primefaces.js
文件几乎是220KiB,需要创build一个小于0.5KiB的帮助脚本:
DeferredPrimeFaces = function() { var deferredPrimeFaces = {}; var calls = []; var settings = {}; var primeFacesLoaded = !!window.PrimeFaces; function defer(name, args) { calls.push({ name: name, args: args }); } deferredPrimeFaces.begin = function() { if (!primeFacesLoaded) { settings = window.PrimeFaces.settings; delete window.PrimeFaces; } }; deferredPrimeFaces.apply = function() { if (window.PrimeFaces) { for (var i = 0; i < calls.length; i++) { window.PrimeFaces[calls[i].name].apply(window.PrimeFaces, calls[i].args); } window.PrimeFaces.settings = settings; } delete window.DeferredPrimeFaces; }; if (!primeFacesLoaded) { window.PrimeFaces = { ab: function() { defer("ab", arguments); }, cw: function() { defer("cw", arguments); }, focus: function() { defer("focus", arguments); }, settings: {} }; } return deferredPrimeFaces; }();
将其另存为/resources/yourapp/scripts/primefaces.deferred.js
。 基本上,它所做的就是捕获PrimeFaces.ab()
, cw()
和focus()
调用(如脚本底部所示),并将它们推迟到DeferredPrimeFaces.apply()
调用find一半的脚本)。 请注意,可能还有更多的PrimeFaces.xxx()
函数需要延期,如果您的应用程序是这种情况,那么您可以自己添加它们在window.PrimeFaces = {}
(不,它是不可能的涵盖未确定function的“全面覆盖”方法)。
在使用这个脚本和<o:deferredScript>
,我们首先需要在生成的HTML输出中确定自动包含的脚本。 对于问题中显示的testing页面,以下脚本将自动包含在生成的HTML <head>
(您可以通过右键单击webbrowser中的页面并selectView Source来查找):
<script type="text/javascript" src="/playground/javax.faces.resource/jquery/jquery.js.xhtml?ln=primefaces&v=4.0"></script> <script type="text/javascript" src="/playground/javax.faces.resource/jquery/jquery-plugins.js.xhtml?ln=primefaces&v=4.0"></script> <script type="text/javascript" src="/playground/javax.faces.resource/primefaces.js.xhtml?ln=primefaces&v=4.0"></script> <script type="text/javascript" src="/playground/javax.faces.resource/layout/layout.js.xhtml?ln=primefaces&v=4.0"></script> <script type="text/javascript" src="/playground/javax.faces.resource/watermark/watermark.js.xhtml?ln=primefaces&v=4.0"></script> <script type="text/javascript" src="/playground/javax.faces.resource/fileupload/fileupload.js.xhtml?ln=primefaces&v=4.0"></script>
您需要跳过jquery.js
文件,并以与剩余脚本完全相同的顺序创build<o:deferredScripts>
。 资源名称是/javax.faces.resource/
之后的部分, 不包括 JSF映射(在我的情况下为.xhtml
)。 库名由ln
请求参数表示。
因此,这应该做到:
<h:head> ... <h:outputScript library="yourapp" name="scripts/primefaces.deferred.js" target="head" /> <o:deferredScript library="primefaces" name="jquery/jquery-plugins.js" /> <o:deferredScript library="primefaces" name="primefaces.js" onbegin="DeferredPrimeFaces.begin()" /> <o:deferredScript library="primefaces" name="layout/layout.js" /> <o:deferredScript library="primefaces" name="watermark/watermark.js" /> <o:deferredScript library="primefaces" name="fileupload/fileupload.js" onsuccess="DeferredPrimeFaces.apply()" /> </h:head>
现在所有总大小约为516KiB的脚本被推迟到onload
事件。 请注意,必须在<o:deferredScript name="primefaces.js">
onsuccess
中调用DeferredPrimeFaces.apply()
必须在最后一个 <o:deferredScript library="primefaces">
onsuccess
中调用DeferredPrimeFaces.apply()
。
关于性能改进,重要的测量点是DOMContentLoaded
时间,您可以在Chrome开发人员工具的“ networking”选项卡底部find。 在3年前的笔记本电脑上,由Tomcat提供的问题中显示的testing页面从〜500ms减less到〜270ms。 这是相当巨大的(差不多一半!),并在移动设备/平板电脑上带来最大的差异,因为它们渲染HTML相对较慢,触摸事件被完全阻止,直到DOM内容被加载。
值得注意的是,如果(自定义)组件库依赖于它们是否遵从JSF资源pipe理规则/指导原则,那么应该是这样。 例如,RichFaces没有自制另一个自定义图层,因此无法在其上使用<o:deferredScript>
。 另请参阅什么是资源库,以及如何使用它?
警告:如果您之后在同一视图中添加新的PrimeFaces组件,并且面临JavaScript undefined
错误,那么新组件也带有自己的JS文件,这个JS文件也应该被延迟,因为它取决于primefaces.js
。 确定正确脚本的一种快速方法是检查为新脚本生成的HTML <head>
,然后根据上述说明为其添加另一个<o:deferredScript>
。
奖金: CombinedResourceHandler
识别<o:deferredScript>
如果碰巧使用OmniFaces CombinedResourceHandler
,那么最好知道它是透明地识别<o:deferredScript>
并将具有相同group
属性的所有延迟脚本合并为一个延迟资源。 比如这个…
<o:deferredScript group="essential" ... /> <o:deferredScript group="essential" ... /> <o:deferredScript group="essential" ... /> ... <o:deferredScript group="non-essential" ... /> <o:deferredScript group="non-essential" ... />
…将以两个相互同步加载的组合的延迟脚本结束。 注意: group
属性是可选的。 如果你没有,那么他们将被合并成一个延期资源。
作为一个实例,检查ZEEF站点的<body>
的底部。 所有基本的PrimeFaces相关的脚本和一些站点特定的脚本被合并到第一个延迟脚本中,所有非必需的社交媒体相关的脚本被合并到第二个延迟脚本中。 关于ZEEF的性能改进,在现代硬件上testingJBoss EAP服务器时, DOMContentLoaded
的时间从大约3秒缩短到大约1秒。
奖金#2:委托PrimeFaces jQuery到CDN
在任何情况下,如果您已经在使用OmniFaces,那么您总是可以使用CDNResourceHandler
通过web.xml
的以下上下文参数将PrimeFaces jQuery资源委托给一个真正的CDN:
<context-param> <param-name>org.omnifaces.CDN_RESOURCE_HANDLER_URLS</param-name> <param-value>primefaces:jquery/jquery.js=http://code.jquery.com/jquery-1.11.0.min.js</param-value> </context-param>
请注意,jQuery 1.11在PrimeFaces 4.0的内部使用上有一些主要的性能改进,超过了1.10,并且完全向后兼容。 在初始化ZEEF时拖动了几百毫秒。
最初发布为推迟primefaces.js加载的答案
为遇到同样问题的其他人添加另一个解决scheme。
您将需要定制HeadRenderer的HeadRenderer
以实现pagespeedbuild议的订购。 尽pipe这是PrimeFaces可以实现的东西,但我在v5.2.RC2中没有看到它。 这些是encodeBegin
中需要更改的行:
96 //Registered Resources 97 UIViewRoot viewRoot = context.getViewRoot(); 98 for (UIComponent resource : viewRoot.getComponentResources(context, "head")) { 99 resource.encodeAll(context); 100 }
只需为head
标签编写一个自定义组件,然后将其绑定到覆盖上述行为的渲染器。
现在,您不希望为了这个改变而复制整个方法,添加一个名为“last”的方面可能会更清晰,并且将脚本资源作为新的deferredScript
组件移动到您的渲染器的开头。 让我知道是否有兴趣,我会创build一个分叉来演示如何。
这种方法是“未来的certificate”,因为当新的资源依赖被添加到组件或者新的组件被添加到视图时,它不会中断。