延迟加载和解析 PrimeFaces JavaScript 文件

     2023-02-22     162

关键词:

【中文标题】延迟加载和解析 PrimeFaces JavaScript 文件【英文标题】:Defer loading and parsing of PrimeFaces JavaScript files 【发布时间】:2014-06-04 02:46:07 【问题描述】:

在使用 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,其中解释了延迟加载以及如何实现它。您基本上需要在windowonload 事件期间动态创建所需的&lt;script&gt;。最简单的形式是完全忽略旧的和有问题的浏览器,它看起来像这样:

<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.js 调用 PrimeFaces.xxx()。这意味着将它们真正推迟到onload 事件并不容易,因为您最终只会遇到$ is undefinedPrimeFaces is undefined 之类的错误。

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

【问题讨论】:

【参考方案1】:

使用&lt;o:deferredScript&gt;

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

UI 组件:DeferredScript HTML 渲染器:DeferredScriptRenderer JS 助手:deferred.unminified.js

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

渲染器将使用OmniFaces.DeferredScript.add() 写入&lt;script&gt; 元素,从而传递JSF 生成的资源URL。这个 JS 助手将依次收集资源 URL,并在 onload 事件期间为每个资源动态创建新的 &lt;script&gt; 元素。

用法相当简单,只需像&lt;h:outputScript&gt;一样使用&lt;o:deferredScript&gt;,加上libraryname。将组件放置在哪里并不重要,但大多数自文档将在 &lt;h:head&gt;end 中,如下所示:

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

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


如何在 PrimeFaces 中使用&lt;o:deferredScript&gt;

这有点棘手,确实是因为所有由 PrimeFaces 生成的内联脚本,但仍然可以使用辅助脚本并接受 jquery.js 不会被推迟(它可以通过 CDN 提供服务,见下文)。为了覆盖那些几乎 220KiB 大的对 primefaces.js 文件的内联 PrimeFaces.xxx() 调用,需要创建一个小于 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() 函数需要延迟,如果您的应用程序中出现这种情况,那么您可以自己将它们添加到 window.PrimeFaces = 中(不,在 JavaScript 中不可能有“包罗万象” " 方法来覆盖未确定的函数)。

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

<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 文件并以完全相同的顺序为其余脚本创建&lt;o:deferredScripts&gt;。资源名称是/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() 必须在&lt;o:deferredScript name="primefaces.js"&gt;onbegin 中调用,DeferredPrimeFaces.apply() 必须在最后一个 &lt;o:deferredScript library="primefaces"&gt;onsuccess 中调用。

如果您使用的是 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 开发者工具的 Network 标签底部找到。使用 Tomcat 在 3 年旧笔记本电脑上提供的问题中所示的测试页面,它从 ~500ms 减少到 ~270ms。这是相对较大的(几乎是一半!),并且在手机/平板电脑上的差异最大,因为它们呈现 HTML 相对较慢,并且触摸事件在加载 DOM 内容之前被完全阻止。

请注意,您是否使用(自定义)组件库取决于它们是否遵守 JSF 资源管理规则/指南。例如,RichFaces 并没有在其上自制另一个自定义层,因此无法在其上使用 &lt;o:deferredScript&gt;。另见what is the resource library and how should it be used?

警告:如果您之后在同一个视图上添加新的 PrimeFaces 组件并且遇到 JavaScript undefined 错误,那么新组件也有自己的 JS 文件的可能性很大这也应该被推迟,因为它取决于primefaces.js。找出正确脚本的一种快速方法是检查生成的 HTML &lt;head&gt; 是否有新脚本,然后根据上述说明为其添加另一个 &lt;o:deferredScript&gt;


奖励:CombinedResourceHandler 识别 &lt;o:deferredScript&gt;

如果您碰巧使用了 OmniFaces CombinedResourceHandler,那么很高兴知道它可以透明地识别 &lt;o:deferredScript&gt; 并将所有具有相同 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 网站的&lt;body&gt; 底部。所有基本的 PrimeFaces 相关脚本和一些特定于站点的脚本都组合在第一个延迟脚本中,所有非必要的社交媒体相关脚本组合在第二个延迟脚本中。至于 ZEEF 的性能提升,在现代硬件上的测试 JBoss EAP 服务器上,DOMContentLoaded 的时间从 ~3s 变为 ~1s。


奖励 #2:将 PrimeFaces jQuery 委托给 CDN

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

<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 上初始化拖放时节省了几百毫秒。

【讨论】:

非常好的功能和有用的解释。谢谢你。通过CDNResourceHandler 检索到的资源是否也可以标记为延迟加载?在web.xml? 在 PrimeFaces 6 上,primefaces.js 分为 core.js 和 components.js。考虑到这个部门,你能更新答案吗?谢谢。 有人知道如何在 Primefaces 6.2 上应用这个吗? 我想通了。它在 Primefaces 6 中更短。&lt;h:outputScript library="js" name="primefaces.deferred.min.js" target="head" /&gt; &lt;o:deferredScript group="essential" library="primefaces" name="jquery/jquery-plugins.js" /&gt; &lt;o:deferredScript group="essential" library="primefaces" name="core.js" onbegin="DeferredPrimeFaces.begin()" /&gt; &lt;o:deferredScript group="essential" library="primefaces" name="components.js" onsuccess="DeferredPrimeFaces.apply()" /&gt;【参考方案2】:

最初作为Defer primefaces.js loading的答案发布


为遇到相同问题的其他人添加此问题的另一个解决方案。

您需要自定义 primefaces 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 组件在您的渲染器中移动到它的开头可能会更简洁。如果有兴趣,请告诉我,我将创建一个 fork 来演示。

这种方法是“面向未来的”,因为它不会随着新的资源依赖项添加到组件或新组件添加到视图而中断。

【讨论】:

这不会延迟加载 PrimeFaces JS 文件。重新排序!=推迟。该问题包含一个示例JS代码sn-p,它实现了真正的延迟加载。 @BalusC:确实如此。我正在实现head 组件中的重新排序并将延迟委托给deferredScript 组件。 没问题。 Here's 使用omnifaces 的示例。 哦,现在我终于明白你了!从答案中并没有直接清楚地表明您将用 OmniFaces DeferredScript 组件替换它们。您可能想在答案中澄清这一点,以及相关的代码 sn-p。毕竟这确实也是一个不错的方法。它只不适用于CombinedResourceHandler

primefaces 实时滚动是不是与延迟加载兼容

】primefaces实时滚动是不是与延迟加载兼容【英文标题】:IsprimefaceslivescrollingcompatiblewithLazyloadingprimefaces实时滚动是否与延迟加载兼容【发布时间】:2014-04-0708:20:42【问题描述】:我有一个需要显示大量数据集的数据表。所以我... 查看详情

Primefaces 动态列不适用于延迟加载

】Primefaces动态列不适用于延迟加载【英文标题】:PrimefacesDynamiccolumnsnotworkingwithlazyloading【发布时间】:2014-12-2013:47:14【问题描述】:我正在使用PF5.0、JSF2.0(Mojarra)、SpringWebflow2.3。我在使用Datatable延迟加载时遇到了多个问题。a.)... 查看详情

当方法执行更新时刷新延迟加载的 Primefaces 数据表

】当方法执行更新时刷新延迟加载的Primefaces数据表【英文标题】:refreshlazyloadedPrimefacesdatatablewhenamethodperformsanupdate【发布时间】:2015-01-3005:16:42【问题描述】:当我通过JDBC方法更新数据库中的数据时,它会被持久化,但primefaces... 查看详情

PrimeFaces 从延迟加载数据表中导出数据

】PrimeFaces从延迟加载数据表中导出数据【英文标题】:PrimeFacesExportdatafromaLazyloadingDataTable【发布时间】:2013-06-1709:23:25【问题描述】:我想在报告生成期间导出具有LazyLoadDataModel的数据表(带分页)。问题:当我导出时,报告从... 查看详情

如何在 JSF 上延迟 Primefaces AjaxStatus?

】如何在JSF上延迟PrimefacesAjaxStatus?【英文标题】:HowtodelayPrimefacesAjaxStatusonJSF?【发布时间】:2016-10-1311:17:18【问题描述】:如何在Primefaces的AjaxStatus显示时添加延迟(例如300毫秒)。现在,当有一个Ajax请求挂起时,它总是立即... 查看详情

与 primefaces 集成时无法加载 jsf.page

】与primefaces集成时无法加载jsf.page【英文标题】:can\'tloadjsf.pagewhenintegratedwithprimefaces【发布时间】:2012-05-2614:29:36【问题描述】:当我想实现jsf2.0、primefaces3.1.1和springframework3.0.2时遇到问题,我无法在身份验证登录页面成功后加... 查看详情

使用 PrimeFaces 手动添加/加载 jQuery 会导致未捕获的 TypeErrors

】使用PrimeFaces手动添加/加载jQuery会导致未捕获的TypeErrors【英文标题】:Manuallyadding/loadingjQuerywithPrimeFacesresultsinUncaughtTypeErrors【发布时间】:2013-04-1611:20:07【问题描述】:我正在使用PrimeFaces3.5和JSF2.0。我想使用一个jQuery插件,... 查看详情

primefaces keyup 或其他 ajax 事件延迟

】primefaceskeyup或其他ajax事件延迟【英文标题】:primefaceskeyuporotherajaxeventdelay【发布时间】:2012-01-1403:10:20【问题描述】:我有类似的东西:<p:inputText...><p:ajaxevent="keyup"update="somefield"listener="#someBean.doSomething"/></p:inp 查看详情

上传文件的fileUpload和primefaces空值[重复]

】上传文件的fileUpload和primefaces空值[重复]【英文标题】:fileUploadandprimefacesnullvalueforuploadedfile[duplicate]【发布时间】:2013-06-0614:07:44【问题描述】:我是primefaces的新手,我正在尝试在我的项目中实现fileUpload组件。我已经打开了... 查看详情

UICollectionView 加载延迟并且滚动非常颠簸

】UICollectionView加载延迟并且滚动非常颠簸【英文标题】:UICollectionViewloadinglateandscrollingverybumpy【发布时间】:2016-01-0812:59:06【问题描述】:我正在设计一个集合视图,我试图在其中加载存储在数组中的字典中的图像和文本。我... 查看详情

hightperformancejavascript——脚本加载和运行

...交互是被完全阻塞的。将脚本放在底部合并脚本减少个数延迟脚本(defer)&l 查看详情

Primefaces graphicsImage仅在第一次加载时显示图像[重复]

】PrimefacesgraphicsImage仅在第一次加载时显示图像[重复]【英文标题】:PrimefacesgraphicImageonlyshowtheimagethefirsttimeitloads[duplicate]【发布时间】:2021-10-2122:40:54【问题描述】:环境:Primefaces10JSF2.3我正在尝试在模板jsf页面上加载个人资料... 查看详情

mybatis探究之延迟加载和缓存

mybatis探究之延迟加载和缓存一、什么是延迟加载1.延迟加载的概念在mybatis进行多表查询时,并非所有的查询都需要立即进行。例如在查询带有账户信息的用户信息时,我们们并不需要总是在加载用户信息时就一定要加载他的账... 查看详情

Primefaces p:graphicImage 动态内容无法加载 404

】Primefacesp:graphicImage动态内容无法加载404【英文标题】:Primefacesp:graphicImagedynamicContentfailedtoload404【发布时间】:2021-04-1511:42:51【问题描述】:显示p:graphicImage动态内容让我已经忙了好几天了。谁能帮帮我,我将不胜感激。我有... 查看详情

mybatis延迟加载和缓存

一、延迟加载1.主对象的加载:根本没有延迟的概念,都是直接加载。2.关联对象的加载时机:01.直接加载:访问主对象,关联对象也要加载02.侵入式延迟:访问主对象,并不加载关联对象访问主对象属性的属性的时候,关联对... 查看详情

hibernate延迟加载和立即加载

概念什么是延迟加载:所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。可以简单理解为,只有在使用的时候,才会发出sql语句进行查询,数据是分N次读取。什么是立即加载:所谓立即加载既是所有的相... 查看详情

hibernate延迟加载和立即加载

概念什么是延迟加载:所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。可以简单理解为,只有在使用的时候,才会发出sql语句进行查询,数据是分N次读取。什么是立即加载:所谓立即加载既是所有的相... 查看详情

hibernate检索策略之延迟加载和立即加载

延迟加载:延迟加载(lazyload懒加载)是当在真正需要数据时,才执行SQL语句进行查询。避免了无谓的性能开销。延迟加载分类:   1.类级别的查询策略  2.一对多和多对多关联的查询策略  3.多对一关联的查询策略什... 查看详情