延迟加载和解析PrimeFaces JavaScript文件

42
在分析使用JSF 2.1 + PrimeFaces 4.0的Web应用程序性能时,Google PageSpeed建议推迟解析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文章,其中解释了延迟加载以及如何实现它。基本上,您需要在windowonload事件期间动态创建所需的<script>。最简单的形式是完全忽略旧版本和有缺陷的浏览器,看起来像这样:

<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()调用?

3个回答

35

使用<o:deferredScript>

是的,使用<o:deferredScript>组件是可能的,这个组件是自从OmniFaces 1.8.1版本后新出现的。对于技术上感兴趣的人,这里是涉及到的源代码:

基本上,该组件将在postAddToView事件期间(因此,在视图构建时)通过UIViewRoot#addComponentResource()将自身作为新的脚本资源添加到<body>的末尾,并通过Hacks#setScriptResourceRendered()通知JSF脚本资源已经被渲染(使用Hacks类,因为还没有标准的JSF API方法来实现这一点),这样JSF就不会再强制自动包含/渲染脚本资源了。对于Mojarra和PrimeFaces,必须设置一个键为name+library,值为true的上下文属性,以禁用资源的自动包含。
渲染器将写入一个带有OmniFaces.DeferredScript.add()的

非常好的功能和有用的解釋。謝謝。可以通過CDNResourceHandler檢索的資源也可以在web.xml中標記為延遲加載嗎? - djmj
在PrimeFaces 6中,primefaces.js被分为core.js和components.js。您能否考虑这个分割更新答案?谢谢。 - FkJ
有人知道如何在Primefaces 6.2上应用这个吗? - codyLine
我想通了。在Primefaces 6中更短。 <h:outputScript library="js" name="primefaces.deferred.min.js" target="head" /> <o:deferredScript group="essential" library="primefaces" name="jquery/jquery-plugins.js" /> <o:deferredScript group="essential" library="primefaces" name="core.js" onbegin="DeferredPrimeFaces.begin()" /> <o:deferredScript group="essential" library="primefaces" name="components.js" onsuccess="DeferredPrimeFaces.apply()" /> - codyLine

4

这篇文章最初是作为回答 Defer primefaces.js loading 的一种解决方案。


对于遇到同样问题的其他人,我提供了另一个解决方案。

您需要自定义PrimeFaces的HeadRenderer以实现页面速度推荐的排序。虽然这是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"的facets,并将脚本资源作为新的deferredScript组件移动到其开头。如果有兴趣,请告诉我,我会创建一个fork来演示如何操作。

这种方法是“未来证明”的,因为随着新的资源依赖关系被添加到组件中或者新的组件被添加到视图中,它不会破坏任何内容。


这并不推迟PrimeFaces JS文件的加载。重新排序!= 推迟。该问题包含一个示例JS代码片段,可以实现真正的推迟加载。 - BalusC
@BalusC:实际上是这样的。我在“head”组件中实现了重新排序,并将延迟委托给“deferredScript”组件。 - Timir
没问题。[这里是](https://github.com/thazarik/omnifaces/commit/71f0bf072a9d74a144d71770e70c44fb5f7d74fa#diff-ef9e51241f47affca1d49aea4ba96cad)使用Omnifaces的示例。 - Timir
哦,现在我终于明白了!从答案中并没有直接清楚地表明你会用OmniFaces的“DeferredScript”组件来替换它们。您可能希望在答案中澄清这一点,以及相关的代码片段。毕竟,这也是一个不错的方法。但它与“CombinedResourceHandler”不兼容。 - BalusC

0

当涉及到PrimeFaces 12时,js脚本中存在一个小bug。

必须在应用循环之前设置window.PrimeFaces.settings = settings;(否则,例如dataview的分页器将无法工作,并且会出现运行时错误)

primefaces.deferred.js文件:

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() {
window.PrimeFaces.settings = settings;

    if (window.PrimeFaces) {
        for (var i = 0; i < calls.length; i++) {
            window.PrimeFaces[calls[i].name].apply(window.PrimeFaces, calls[i].args);
        }

    
    }

    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;  }();

这是一个在头部部分的xhtml文件的示例: 不要忘记为第一个和最后一个延迟脚本添加onbegin和onsuccess属性。
    <h:outputScript name="javascript/primefaces.deferred.js"
    target="head" />

<o:deferredScript group="essential" library="primefaces"
    name="jquery/jquery-plugins.js" />
    
<o:deferredScript group="essential" library="primefaces" name="core.js" 
    onbegin="DeferredPrimeFaces.begin()" />

<o:deferredScript group="essential" library="primefaces"
    name="components.js" />

<o:deferredScript group="mygroup"
    name="javascript/myscripts" target="head" onsuccess="DeferredPrimeFaces.apply()" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接