推迟加载和解析PrimeFaces JavaScript文件

BalusC:

在使用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.jsPrimeFaces.xxx()从中调用primefaces.js这将意味着将它们真正推迟到onload事件上是不容易的,因为您最终只会遇到诸如$ is undefined和的错误PrimeFaces is undefined

但是,这在技术上应该是可能的。鉴于只需要推迟jQuery,因为许多站点的自定义脚本也都依赖它,我如何才能阻止JSF强制自动包含PrimeFaces脚本,以便我可以推迟它们,以及如何处理这些问题内联PrimeFaces.xxx()电话?

BalusC:

<o:deferredScript>

是的,OmniFaces 1.8.1 <o:deferredScript>以后的新组件有可能对于技术上感兴趣的人,这里是相关的源代码:

基本上,该组件将在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>,具有libraryname不要紧,你放置元件,但大多数自文档将在月底<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&amp;v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/jquery/jquery-plugins.js.xhtml?ln=primefaces&amp;v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/primefaces.js.xhtml?ln=primefaces&amp;v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/layout/layout.js.xhtml?ln=primefaces&amp;v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/watermark/watermark.js.xhtml?ln=primefaces&amp;v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/fileupload/fileupload.js.xhtml?ln=primefaces&amp;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()必须onsuccesslast中 调用<o:deferredScript library="primefaces">

如果您使用的是PrimeFaces 6.0或更高版本,其中的primefaces.js替换为core.jscomponents.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


奖金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内部使用的jQuery 1.11在1.10上有一些重大的性能改进,并且它向后完全兼容。在ZEEF上初始化拖放操作时,它节省了几百毫秒的时间。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章