在使用Google PageSpeed分析JSF 2.1 + PrimeFaces 4.0 webapp的性能时,它尤其建议推迟JavaScript文件的解析。在一个测试页面上,带有<p:layout>
和的表单<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文章,其中解释了延迟加载及其实现方法。基本上,您需要<script>
在的onload
事件期间动态创建所需的对象window
。在最简单的形式下,旧有漏洞的浏览器被完全忽略,它看起来像这样:
<script>
window.addEventListener("load", function() {
var script = document.createElement("script");
script.src = "filename.js";
document.head.appendChild(script);
}, false);
</script>
好的,如果您可以控制这些脚本,那么这是可行的,但是JSF会强制自动包含列出的脚本。此外,PrimeFaces还将大量内联脚本呈现到HTML输出,这些脚本直接$(xxx)
从jquery.js
和PrimeFaces.xxx()
从中调用primefaces.js
。这将意味着将它们真正推迟到onload
事件上是不容易的,因为您最终只会遇到诸如$ is undefined
和的错误PrimeFaces is undefined
。
但是,这在技术上应该是可能的。鉴于只需要推迟jQuery,因为许多站点的自定义脚本也都依赖它,我如何才能阻止JSF强制自动包含PrimeFaces脚本,以便我可以推迟它们,以及如何处理这些问题内联PrimeFaces.xxx()
电话?
<o:deferredScript>
是的,OmniFaces 1.8.1 <o:deferredScript>
以后的新组件有可能。对于技术上感兴趣的人,这里是相关的源代码:
DeferredScript
DeferredScriptRenderer
deferred.unminified.js
基本上,该组件将在postAddToView
事件发生期间(因此,在视图构建期间)通过UIViewRoot#addComponentResource()
将其自身添加为新的脚本资源,<body>
然后通过Hacks#setScriptResourceRendered()
通知JSF脚本资源已被渲染(使用Hacks
类,因为没有标准的JSF API方法)。 (还?)),以便JSF不再强行自动包含/渲染脚本资源。在Mojarra和PrimeFaces的情况下,必须设置键为name+library
和值为的上下文属性,true
以禁用自动包含资源。
呈现器将编写一个<script>
元素,通过该元素OmniFaces.DeferredScript.add()
传递JSF生成的资源URL。该JS帮助程序将依次收集资源URL <script>
,并在onload
事件期间为每个URL动态创建新元素。
用法相当简单,只需使用<o:deferredScript>
方法一样<h:outputScript>
,具有library
和name
。不要紧,你放置元件,但大多数自文档将在月底的<h:head>
是这样的:
<h:head>
...
<o:deferredScript library="libraryname" name="resourcename.js" />
</h:head>
您可以有多个它们,它们最终将按照声明时的相同顺序加载。
<o:deferredScript>
与PrimeFaces 一起使用?这是一个有点棘手,因为PrimeFaces产生的所有内嵌脚本确实,但一个脚本仍然可行并接受jquery.js
将不会被推迟(它可以但通过CDN提供服务,见下文)。为了掩盖这些内嵌PrimeFaces.xxx()
调用primefaces.js
文件,这几乎是220KiB大,一个脚本需要创建小于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()
通话(你可以中途找到脚本)。请注意,可能还有更多PrimeFaces.xxx()
功能需要推迟,如果在您的应用程序中是这种情况,那么您可以将它们自己添加到内部window.PrimeFaces = {}
(不,在JavaScript中,不可能使用“包罗万象”方法来覆盖未确定的功能)功能)。
在使用和之前<o:deferredScript>
,我们首先需要确定生成的HTML输出中自动包含的脚本。对于问题中显示的测试页面,以下脚本会自动包含在生成的HTML中<head>
(您可以通过在Webbrowser中右键单击页面并选择View 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
文件,并<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
事件中。请注意,DeferredPrimeFaces.begin()
必须在中调用onbegin
,<o:deferredScript name="primefaces.js">
并且DeferredPrimeFaces.apply()
必须onsuccess
在last中 调用<o:deferredScript library="primefaces">
。
如果您使用的是PrimeFaces 6.0或更高版本,其中的primefaces.js
替换为core.js
和components.js
,请改用以下代码:
<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="core.js" onbegin="DeferredPrimeFaces.begin()" />
<o:deferredScript library="primefaces" name="components.js" />
<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>
至于性能改进,重要的衡量点是DOMContentLoaded
时间,您可以在Chrome开发人员工具的“ 网络”标签的底部找到该时间。Tomcat在一台使用了3年的笔记本电脑上提供的问题中显示了测试页,该页面从〜500ms减少到〜270ms。这相对较大(几乎是一半!),并且在移动设备/平板电脑上产生的差异最大,因为它们使HTML相对较慢并且触摸事件被完全阻止,直到加载DOM内容为止。
应该注意的是,您是否需要(自定义)组件库,取决于它们是否遵守JSF资源管理规则/准则。例如,RichFaces并没有在其上进行自制酿造另一层,因此无法<o:deferredScript>
在其上使用。另请参阅什么是资源库以及如何使用它?
警告:如果之后在同一视图上添加新的PrimeFaces组件,并且undefined
遇到JavaScript 错误,则新组件还带有自己的JS文件的可能性很大,这也应推迟,因为它取决于primefaces.js
。确定正确脚本的快速方法是检查<head>
为新脚本生成的HTML ,然后<o:deferredScript>
根据上述说明为其添加另一个HTML 。
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
属性是可选的。如果您没有任何资源,那么它们将全部合并为一个延迟资源。
作为一个活生生的例子,检查底部<body>
的的ZEEF网站。所有基本的与PrimeFaces相关的脚本和某些特定于站点的脚本都合并在第一个延迟脚本中,而所有与基本社交媒体无关的脚本都合并在第二个延迟脚本中。关于ZEEF的性能改进,在现代硬件上测试的JBoss EAP服务器上,时间DOMContentLoaded
从〜3s缩短到〜1s 。
无论如何,如果您已经在使用OmniFaces,那么您始终可以CDNResourceHandler
通过以下上下文参数将PrimeFaces jQuery资源委托给真正的CDN web.xml
:
<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>
请注意,PrimeFaces 4.0内部使用的jQuery 1.11在1.10上有一些重大的性能改进,并且它向后完全兼容。在ZEEF上初始化拖放操作时,它节省了几百毫秒的时间。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句