在使用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)它链接到this Google Developers article,其中解释了延迟加载以及如何实现它。您基本上需要在<script>
的{{1}}事件期间动态创建所需的onload
。最简单的形式是完全忽略旧的和错误的浏览器,它看起来像这样:
window
好的,如果您可以控制这些脚本,这是可行的,但所列出的脚本都被JSF强行自动包含。此外,PrimeFaces将一堆内联脚本呈现为HTML输出,这些脚本直接从<script>
window.addEventListener("load", function() {
var script = document.createElement("script");
script.src = "filename.js";
document.head.appendChild(script);
}, false);
</script>
$(xxx)
和jquery.js
PrimeFaces.xxx()
调用primefaces.js
。这意味着很难将它们真正推迟到onload
事件,因为您最终会遇到$ is undefined
和PrimeFaces is undefined
等错误。
但是,它应该在技术上可行。鉴于只有jQuery不需要延迟,因为许多网站的自定义脚本也依赖它,我怎么能阻止JSF强行自动包括PrimeFaces脚本以便我可以推迟它们,我怎么能处理那些内联PrimeFaces.xxx()
来电?
答案 0 :(得分:32)
<o:deferredScript>
是的,自OmniFaces 1.8.1之后的<o:deferredScript>
组件可能是新的。对于技术上感兴趣的,这里涉及的源代码:
DeferredScript
DeferredScriptRenderer
deferred.unminified.js
基本上,组件将在postAddToView
事件期间(因此,在视图构建期间)通过UIViewRoot#addComponentResource()
将自身添加为<body>
末尾的新脚本资源并通过{{ 3}}通知JSF脚本资源已经已经呈现(使用Hacks
类,因为没有标准的JSF API方法(但是?)),所以JSF赢了& #39; t强制自动包含/渲染脚本资源。对于Mojarra和PrimeFaces,必须设置密钥为name+library
且值为true
的上下文属性,以禁用资源的自动包含。
渲染器将使用<script>
编写OmniFaces.DeferredScript.add()
元素,从而传递JSF生成的资源URL。此JS帮助程序将依次收集资源URL,并在<script>
事件期间为每个URL动态创建新的onload
元素。
用法非常简单,只需使用<o:deferredScript>
与<h:outputScript>
相同的方式,library
和name
。放置组件的位置并不重要,但大多数自我记录都在<h:head>
的 end 中,如下所示:
<h:head>
...
<o:deferredScript library="libraryname" name="resourcename.js" />
</h:head>
您可以拥有其中的多个,并且最终将按照与其声明相同的顺序加载它们。
<o:deferredScript>
?这有点棘手,确实是因为所有那些由PrimeFaces生成的内联脚本,但仍然可以使用帮助脚本并接受jquery.js
不会被推迟(它可以然而,通过CDN提供服务,请参阅后面的内容)。为了覆盖那些大约220KiB大的PrimeFaces.xxx()
文件的内联primefaces.js
调用,需要创建一个小于0.5KiB Hacks#setScriptResourceRendered()
的帮助脚本:
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中)可能有一个&#34; catch-all&#34;方法来覆盖未确定的功能。)
在使用此脚本和<o:deferredScript>
之前,我们首先需要在生成的HTML输出中确定自动包含的脚本。对于问题中显示的测试页面,以下脚本会自动包含在生成的HTML <head>
中(您可以通过右键单击webbrowser中的页面并选择查看源来查找):< / p>
<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()
的{{1}}中调用onbegin
,并且必须在最后 {<o:deferredScript name="primefaces.js">
中调用DeferredPrimeFaces.apply()
onsuccess
1}}。
如果您使用的是PrimeFaces 6.0或更新版本,<o:deferredScript library="primefaces">
已被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>
时间,您可以在Chrome开发人员工具的网络标签的底部找到。 Tomcat在一台3岁的笔记本电脑上提供的测试页面中显示的测试页面从~500ms减少到~270ms。这是相对巨大的(几乎是一半!)并且在移动设备/平板电脑上产生最大的差异,因为它们使HTML相对较慢并且触摸事件被完全阻止,直到加载DOM内容。
注意,如果(自定义)组件库依赖于它们是否遵守JSF资源管理规则/指南,则应该这样做。例如,RichFaces并没有自制另一个自定义图层,因此无法在其上使用DOMContentLoaded
。另请参阅minified
警告:如果您之后在同一视图中添加新的PrimeFaces组件并且遇到JavaScript <o:deferredScript>
错误,那么新组件也随附的可能性很大它自己的JS文件也应该推迟,因为它取决于undefined
。找出正确脚本的快捷方法是检查生成的HTML primefaces.js
以查找新脚本,然后根据上述说明为其添加另一个<head>
。
<o:deferredScript>
识别CombinedResourceHandler
如果您碰巧使用OmniFaces what is the resource library and how should it be used?,那么很高兴知道它透明地识别<o:deferredScript>
并将具有相同<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" ... />
属性是可选的。如果您没有,那么它们将全部合并为一个延期资源。
作为实例,请查看CombinedResourceHandler
网站group
的底部。所有与PrimeFaces相关的基本脚本和一些特定于站点的脚本在第一个延迟脚本中组合在一起,所有非必要的社交媒体相关脚本在第二个延迟脚本中组合在一起。至于ZEEF的性能改进,在现代硬件上的测试JBoss EAP服务器上,<body>
的时间从~3s变为~1s。
在任何情况下,如果您已经在使用OmniFaces,那么您始终可以使用ZEEF通过DOMContentLoaded
中的以下上下文参数将PrimeFaces jQuery资源委派给真正的CDN:< / p>
web.xml
请注意,与PrimeFaces 4.0内部使用的jQuery 1.11在1.10上有一些主要的性能改进,并且它完全向后兼容。在初始化ZEEF上的拖拽时,它节省了几百毫秒。
答案 1 :(得分:3)
最初发布为Defer primefaces.js loading
的答案为遇到相同问题的其他人添加此问题的其他解决方案。
您需要自定义素数HeadRenderer
以实现pagespeed建议的排序。虽然这可以由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
组件移动到渲染器的开头可能更简洁。让我知道是否有兴趣,我将创建一个分支来演示如何。
这种方法是“未来证明”,因为它不会因为新的资源依赖关系被添加到组件中,或者随着新组件添加到视图中而中断。