推迟加载和解析PrimeFaces JavaScript文件 [英] Defer loading and parsing of PrimeFaces JavaScript files

查看:486
本文介绍了推迟加载和解析PrimeFaces JavaScript文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用 Google PageSpeed 分析JSF 2.1 + PrimeFaces 4.0网络应用程序的性能时,建议其他人推迟解析JavaScript文件。在包含< p:layout> 的测试页上,以及包含< p:watermark> 和<的表单code>< p:fileUpload> 如下所示......

While analyzing the performance of a JSF 2.1 + PrimeFaces 4.0 webapp with Google PageSpeed, it recommends among others to defer parsing of JavaScript files. On a test page with a <p:layout> and a form with <p:watermark> and <p:fileUpload> which looks like follows ...

<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文件:

... it lists the following JavaScript files which could be deferred:


  • 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)

  • 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文章其中解释了延迟加载以及如何实现它。您基本上需要在 onload 事件期间动态创建所需的< script> 窗口。最简单的形式是完全忽略旧的和错误的浏览器,它看起来像这样:

It links to this Google Developers article wherein deferred loading is explained as well as how to achieve it. You basically need to dynamically create the desired <script> during the onload event of the window. At its simplest form whereby old and buggy browsers are completely ignored, it looks like this:

<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.xxx()来自 primefaces.js 。这意味着很难将它们真正推迟到 onload 事件,因为你最终会遇到像 $未定义的错误 PrimeFaces未定义

Okay, this is doable if you have control over those scripts, but the listed scripts are all forcibly auto-included by JSF. Also, PrimeFaces renders a bunch of inline scripts to HTML output which are directly calling $(xxx) from jquery.js and PrimeFaces.xxx() from primefaces.js. This would mean that it would not easily be possible to really defer them to onload event as you would only end up with errors like $ is undefined and PrimeFaces is undefined.

但是,它应该在技术上可行。鉴于只有jQuery不需要延迟,因为许多网站的自定义脚本也依赖它,我怎么能阻止JSF强行自动包括PrimeFaces脚本以便我可以推迟它们,我怎么能处理那些内联 PrimeFaces.xxx()调用?

But, it should be technically possible. Given that only jQuery doesn't need to be deferred as many of the site's custom scripts also rely on it, how could I block JSF from forcibly auto-including the PrimeFaces scripts so that I can defer them, and how could I deal with those inline PrimeFaces.xxx() calls?

推荐答案

使用< o:deferredScript>



是的,可以使用< o:deferredScript> OmniFaces 1.8.1以来新增的组件。对于技术上感兴趣的,这里涉及的源代码:

Use <o:deferredScript>

Yes, it is possible with the <o:deferredScript> component which is new since OmniFaces 1.8.1. For the technically interested, here's the involved source code:

  • The UI component: DeferredScript
  • The HTML renderer: DeferredScriptRenderer
  • The JS helper: deferred.unminified.js

基本上,该组件将在 postAddToView期间事件(因此,在视图构建期间)通过 UIViewRoot #addComponentResource() 将自己添加为< body> 结尾的新脚本资源,并通过 Hacks #setScriptResourceRendered() 通知JSF脚本资源已经呈现(使用 Hacks 类,因为没有标准的JSF API方法(但是?) ),以便JSF不再强制自动包含/呈现脚本资源。对于Mojarra和PrimeFaces,键值为 name + library 且值为 true 的上下文属性必须为设置为禁用自动包含资源。

Basically, the component will during the postAddToView event (thus, during the view build time) via UIViewRoot#addComponentResource() add itself as a new script resource in end of <body> and via Hacks#setScriptResourceRendered() notify JSF that the script resource is already rendered (using Hacks class as there's no standard JSF API approach for that (yet?)), so that JSF won't forcibly auto-include/render the script resource anymore. In case of Mojarra and PrimeFaces, a context attribute with key of name+library and a value of true has to be set in order to disable auto-inclusion of the resource.

渲染器将写入< script> 元素使用 OmniFaces.DeferredScript.add(),传递JSF生成的资源URL。这个JS帮助器将依次收集资源URL并在 onload < script> 元素。 c>事件。

The renderer will write a <script> element with OmniFaces.DeferredScript.add() whereby the JSF-generated resource URL is passed. This JS helper will in turn collect the resource URLs and dynamically create new <script> elements for each of them during the onload event.

使用相当简单,只需使用< o:deferredScript> ,方法与< h:outputScript> ,带有名称。放置组件的位置无关紧要,但大多数自我记录都位于< h:head> end 中这个:

The usage is fairly simple, just use <o:deferredScript> the same way as <h:outputScript>, with a library and name. It doesn't matter where you place the component, but most self-documenting would be in the end of the <h:head> like this:

<h:head>
    ...
    <o:deferredScript library="libraryname" name="resourcename.js" />
</h:head>

您可以拥有其中的多个,并且最终将按照它们声明的顺序加载它们。

You can have multiple of them and they will ultimately be loaded in the same order as they're declared.

这有点棘手,确实是因为PrimeFaces生成的所有内联脚本,但仍然可以使用帮助脚本并接受 jquery.js 不会被延期(可以但是可以通过CDN提供,见后面)。为了覆盖那些内联 PrimeFaces.xxx()调用 primefaces.js 文件,这个文件差不多是220KiB,需要创建一个小于0.5KiB的辅助脚本缩小

This is a little tricky, indeed because of all those inline scripts generated by PrimeFaces, but still doable with a helper script and accepting that jquery.js won't be deferred (it can however be served via a CDN, see later). In order to cover those inline PrimeFaces.xxx() calls to primefaces.js file which is almost 220KiB large, a helper script needs to be created which is less than 0.5KiB minified:

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()函数需要延迟,如果您的应用程序就是这种情况,那么您可以在<$ c中自己添加它们$ c> window.PrimeFaces = {} (不,它在JavaScript中不可能有一个全能方法来覆盖未确定的函数)。

Save it as /resources/yourapp/scripts/primefaces.deferred.js. Basically, all what it does is capturing the PrimeFaces.ab(), cw() and focus() calls (as you can find in the bottom of the script) and deferring them to the DeferredPrimeFaces.apply() call (as you can find halfway the script). Note that there are possibly more PrimeFaces.xxx() functions which need to be deferred, if that is the case in your app, then you can add them yourself inside window.PrimeFaces = {} (no, it's in JavaScript not possible to have a "catch-all" method to cover the undetermined functions).

在使用此脚本和< o:deferredScript> 之前,我们首先需要在生成的HTML输出中确定自动包含的脚本。对于问题中显示的测试页面,以下脚本会自动包含在生成的HTML < head> 中(您可以通过右键单击webbrowser中的页面并选择查看来源):

Before using this script and <o:deferredScript>, we first need to determine the auto-included scripts in the generated HTML output. For the test page as shown in the question, the following scripts are auto-included in generated HTML <head> (you can find this by rightclicking the page in webbrowser and choosing 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 请求参数表示。

You need to skip the jquery.js file and create <o:deferredScripts> in exactly the same order for the remaining scripts. The resource name is the part after /javax.faces.resource/ excluding the JSF mapping (.xhtml in my case). The library name is represented by ln request parameter.

因此,这应该是:

<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> 并且必须在 onsuccess DeferredPrimeFaces.apply() c $ c> 最后 < o:deferredScript library =primefaces>

Now all those scripts with a total size of about 516KiB are deferred to onload event. Note that DeferredPrimeFaces.begin() must be called in onbegin of <o:deferredScript name="primefaces.js"> and that DeferredPrimeFaces.apply() must be called in onsuccess of the last <o:deferredScript library="primefaces">.

关于性能改进,重要的测量点是 DOMContentLoaded 时间,您可以在Chrome开发者工具的 Network 标签的底部找到。 Tomcat在一台3岁的笔记本电脑上提供的问题中显示的测试页面,从~500ms减少到~270ms。这是相对巨大的(差不多一半!)并且在移动设备/平板电脑上产生最大的差异,因为它们呈现HTML相对较慢并且触摸事件被完全阻止,直到加载DOM内容。

As to performance improvement, important measuring point is the DOMContentLoaded time as you can find in bottom of Network tab of Chrome's developer tools. With the test page as shown in the question served by Tomcat on a 3 year old laptop, it decreased from ~500ms to ~270ms. This is relatively huge (almost the half!) and makes the most difference on mobiles/tablets as they render HTML relatively slow and touch events are fully blocked until the DOM content is loaded.

注意应该是(自定义)组件库取决于它们是否遵守JSF资源管理规则/指南。例如,RichFaces没有和自制的另一个自定义层,因此无法在其上使用< o:deferredScript> 。另请参见什么是资源库以及应该如何使用?

Noted should be that you're in case of (custom) component libraries dependent on whether they obey the JSF resource management rules/guidelines or not. RichFaces for example didn't and homebrewed another custom layer over it, making it impossible to use <o:deferredScript> on it. See also what is the resource library and how should it be used?

警告:如果您要在其上添加新的PrimeFaces组件之后相同的视图并且面临JavaScript undefined 错误,那么新组件也带有自己的JS文件的机会很大,这个文件也应该延迟,因为它取决于 primefaces.js 。确定正确脚本的快速方法是检查生成的HTML < head> 以获取新脚本,然后添加另一个< o: deferredScript> 基于上述说明。

Warning: if you're adding new PrimeFaces components on the same view afterwards and are facing JavaScript undefined errors, then the chance is big that the new component also comes with its own JS file which should also be deferred, because it's depending on primefaces.js. A quick way to figure the right script would be to check the generated HTML <head> for the new script and then add another <o:deferredScript> for it based on the above instructions.

如果您碰巧使用OmniFaces CombinedResourceHandler ,这很好知道它透明地识别< o:deferredScript> 并将所有延迟脚本与相同的属性合并为一个延迟资源。例如。这...

If you happen to use OmniFaces CombinedResourceHandler, then it's good to know that it transparently recognizes <o:deferredScript> and combines all deferred scripts with the same group attribute into a single deferred resource. E.g. this ...

<o:deferredScript group="essential" ... />
<o:deferredScript group="essential" ... />
<o:deferredScript group="essential" ... />
...
<o:deferredScript group="non-essential" ... />
<o:deferredScript group="non-essential" ... />

...将以两个组合的延迟脚本结束,这些脚本会相互同步加载。注意:属性是可选的。如果你没有,那么它们将被合并为一个延期资源。

... will end up in two combined deferred scripts which are loaded synchronously after each other. Note: the group attribute is optional. If you don't have any, then they will just all be combined into a single deferred resource.

作为一个实例,检查 ZEEF 网站的< body> 。所有与PrimeFaces相关的基本脚本和一些特定于站点的脚本在第一个延迟脚本中组合在一起,所有非必要的社交媒体相关脚本在第二个延迟脚本中组合在一起。至于ZEEF的性能改进,在现代硬件上的测试JBoss EAP服务器上, DOMContentLoaded 的时间从~3s变为~1s。

As a live example, check the bottom of <body> of the ZEEF site. All essential PrimeFaces-related scripts and some site-specific scripts are combined in the first deferred script and all non-essential social media related scripts are combined in the second deferred script. As to performance improvement of ZEEF, on a test JBoss EAP server on modern hardware, the time to DOMContentLoaded went from ~3s to ~1s.

无论如何,如果你是已经使用OmniFaces,那么你总是可以使用 CDNResourceHandler 通过 web.xml 中的以下上下文参数将PrimeFaces jQuery资源委托给真正的CDN:

In any case, if you're already using OmniFaces, then you can always use CDNResourceHandler to delegate the PrimeFaces jQuery resource to a true CDN by the following context param in 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上初始化拖放时,它节省了几百毫秒。

Note that jQuery 1.11 has some major performance improvements over 1.10 as internally used by PrimeFaces 4.0 and that it's fully backwards compatible. It saved a couple of hundred milliseconds when initializing drag'n'drop on ZEEF.

这篇关于推迟加载和解析PrimeFaces JavaScript文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆