Ответ 1
Используйте <o:deferredScript>
Да, возможно с компонентом <o:deferredScript>
, который является новым, поскольку OmniFaces 1.8.1. Для технически заинтересованных, здесь задействован исходный код:
- Компонент пользовательского интерфейса:
DeferredScript
- Средство визуализации HTML:
DeferredScriptRenderer
- Помощник JS:
deferred.unminified.js
В принципе, компонент будет во время события postAddToView
(таким образом, в течение времени построения представления) через UIViewRoot#addComponentResource()
добавить себя как новый ресурс script в конце <body>
и через Hacks#setScriptResourceRendered()
уведомить JSF о том, что ресурс script уже отображен (используя Hacks
, поскольку для этого не существует стандартного API API JSF (пока?)), так что JSF не будет принудительно автоматически включать/отображать ресурс script. В случае Mojarra и PrimeFaces должен быть установлен атрибут контекста с ключом name+library
и значение true
, чтобы отключить автоматическое включение ресурса.
Средство рендеринга будет писать элемент <script>
с OmniFaces.DeferredScript.add()
, посредством которого передается URL-адрес ресурса, генерируемого JSF. Этот помощник 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, но все же выполняемых с помощью помощника script и принятия того, что jquery.js
не будет отложен (однако он может быть передан через CDN, увидим позже). Чтобы охватить те встроенные PrimeFaces.xxx()
вызовы в файл primefaces.js
, размер которого почти 220KiB, необходимо создать вспомогательный script, который меньше 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()
(как вы можете найти в нижней части script) и отложить их до вызова DeferredPrimeFaces.apply()
(как вы можете найти на полпути script). Обратите внимание, что возможно больше функций PrimeFaces.xxx()
, которые нужно отложить, если это так в вашем приложении, то вы можете добавить их самостоятельно внутри window.PrimeFaces = {}
(нет, на JavaScript не возможно иметь "все-таки" "метод для покрытия неопределенных функций).
Прежде чем использовать эти script и <o:deferredScript>
, сначала нам нужно определить сценарии с автосохранением в сгенерированном HTML-выходе. Для тестовой страницы, как показано в вопросе, следующие сценарии автоматически включаются в сгенерированный HTML <head>
(вы можете найти это, щелкнув правой кнопкой страницу в веб-браузере и выбрав "Просмотр источника" ):
<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
из <o:deferredScript name="primefaces.js">
и что DeferredPrimeFaces.apply()
должен быть вызван в onsuccess
last <o:deferredScript library="primefaces">
.
Что касается улучшения производительности, важной точкой измерения является время DOMContentLoaded
, как вы можете найти в нижней части вкладки "Сеть" инструментов разработчика Chrome. С тестовой страницей, как показано в вопросе, обслуживаемом Tomcat на 3-летнем ноутбуке, он уменьшился с ~ 500 мс до ~ 270 мс. Это относительно огромно (почти половина!) И делает наибольшую разницу на мобильных телефонах/планшетах, поскольку они делают HTML относительно медленным, а события касания полностью блокируются до загрузки содержимого DOM.
Отмечается, что вы в случае (пользовательских) библиотек компонентов зависят от того, подчиняются ли они правилам/правилам управления ресурсами JSF или нет. Например, RichFaces не делал и не вызывал на нем другой пользовательский слой, что делало невозможным использование <o:deferredScript>
на нем. См. Также что такое библиотека ресурсов и как ее использовать?
Внимание:если вы добавляете новые компоненты PrimeFaces в одно и то же представление и сталкиваетесь с ошибками JavaScript undefined
, тогда вероятность большая, что новый компонент также имеет свой собственный JS файл, который также должен быть отложен, поскольку он зависит от primefaces.js
. Быстрый способ определить правильный script - проверить сгенерированный HTML <head>
для нового script, а затем добавить еще один <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
не является обязательным. Если у вас их нет, тогда все они будут объединены в один отложенный ресурс.
Как живой пример, проверьте нижнюю часть <body>
сайта ZEEF. Все существенные сценарии, связанные с PrimeFaces, и некоторые скрипты, специфичные для сайта, объединены в первом отложенном script, а все несущественные сценарии, связанные со средой, объединены во втором отложенном script. Что касается улучшения производительности ZEEF, то на тестовом сервере JBoss EAP на современном оборудовании время до DOMContentLoaded
перешло от ~ 3 с до ~ 1 с.
БонуС# 2: делегат PrimeFaces jQuery для CDN
В любом случае, если вы уже используете OmniFaces, вы всегда можете использовать CDNResourceHandler
для делегирования ресурса jQuery PrimeFaces true 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>
Обратите внимание, что jQuery 1.11 имеет некоторые основные улучшения производительности за 1.10, используемые внутри PrimeFaces 4.0 и полностью совместимые с ним. Он сохранил пару сотен миллисекунд при инициализации drag'n'drop на ZEEF.