利用<o:deferredScript>
是的,可以使用<o:deferredScript>
自OmniFaces 1.8.1 以来的新组件。对于技术上感兴趣的人,这里是涉及的源代码:
基本上,组件将在postAddToView
事件期间(因此,在视图构建期间)通过UIViewRoot#addComponentResource()
将自身添加为末尾的新脚本资源<body>
并通过Hacks#setScriptResourceRendered()
通知 JSF 脚本资源已经呈现(使用Hacks
类,因为没有标准的 JSF API 方法那个(还没有?)),这样 JSF 就不会再强制自动包含/呈现脚本资源了。在 Mojarra 和 PrimeFaces 的情况下,必须设置具有 key ofname+library
和 value 的上下文属性,true
以禁用资源的自动包含。
渲染器将编写一个<script>
元素,通过该元素OmniFaces.DeferredScript.add()
传递 JSF 生成的资源 URL。这个 JS 助手将依次收集资源 URL 并在事件<script>
期间为每个资源动态创建新元素。onload
用法相当简单,只需使用与,和<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 提供,见下文)。为了覆盖那些几乎 220KiB 大的PrimeFaces.xxx()
对文件的内联调用,primefaces.js
需要创建一个小于 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
of中调用,<o:deferredScript name="primefaces.js">
并且DeferredPrimeFaces.apply()
必须在onsuccess
last中 <o:deferredScript library="primefaces">
调用。
如果您使用的是 PrimeFaces 6.0 或更高版本,其中primefaces.js
已被替换为core.js
and 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 组件并且遇到 JavaScriptundefined
错误,那么新组件还带有自己的 JS 文件的可能性很大,该文件也应该被延迟,因为它依赖于primefaces.js
. 找出正确脚本的一种快速方法是检查<head>
为新脚本生成的 HTML,然后<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 的性能提升,在现代硬件上的测试 JBoss EAP 服务器上,时间从 ~3s 到 ~1s。DOMContentLoaded
奖励 #2:将 PrimeFaces jQuery 委托给 CDN
在任何情况下,如果您已经在使用 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 内部使用的 1.10 相比,jQuery 1.11 有一些主要的性能改进,并且它完全向后兼容。在 ZEEF 上初始化拖放时,它节省了几百毫秒。