superior página pantalla pagina mitad funcion eliminar ejecutar despues contenido cargar cargando carga asincrona aplazar análisis antes javascript jsf jsf-2 primefaces deferred-loading

página - Aplazar la carga y el análisis de los archivos JavaScript de PrimeFaces



javascript cargando (2)

Utilice <o:deferredScript>

Sí, es posible con el componente <o:deferredScript> que es nuevo desde OmniFaces 1.8.1. Para los interesados ​​técnicamente, aquí está el código fuente involucrado:

Básicamente, el componente durante el evento postAddToView (por lo tanto, durante el tiempo de compilación de la vista) a través de UIViewRoot#addComponentResource() agrega como un nuevo recurso de script al final de <body> y a través de Hacks#setScriptResourceRendered() notifica a JSF que el recurso de script ya se procesa (utilizando la clase Hacks ya que no existe un enfoque API JSF estándar para eso (¿todavía?)), por lo que JSF ya no incluirá / procesará automáticamente el recurso de script. En el caso de Mojarra y PrimeFaces, se debe establecer un atributo de contexto con clave de name+library y un valor de true para inhabilitar la inclusión automática del recurso.

El procesador escribirá un elemento <script> con OmniFaces.DeferredScript.add() mediante el cual se pasa el URL de recursos generado por JSF. Este JS helper a su vez recogerá las URL de recursos y creará dinámicamente nuevos elementos <script> para cada uno de ellos durante el evento de onload .

El uso es bastante simple, solo use <o:deferredScript> la misma manera que <h:outputScript> , con una library y un name . No importa dónde coloque el componente, pero la mayoría de los autodocumentados estarían al final de la <h:head> así:

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

Puede tener varios y finalmente se cargarán en el mismo orden en que se declararon.

¿Cómo se usa <o:deferredScript> con PrimeFaces?

Esto es un poco complicado, de hecho debido a todos los scripts en línea generados por PrimeFaces, pero todavía es factible con un script de ayuda y aceptando que jquery.js no será diferido (sin embargo, puede ser servido a través de un CDN, ver más adelante). Para cubrir esas llamadas PrimeFaces.xxx() línea al archivo primefaces.js que tiene un tamaño de casi 220Ki, se necesita crear un script auxiliar que sea menos de 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 como /resources/yourapp/scripts/primefaces.deferred.js . Básicamente, todo lo que hace es capturar las llamadas PrimeFaces.ab() , cw() y focus() (como puedes encontrar en la parte inferior del script) y diferirlas a la llamada DeferredPrimeFaces.apply() (como puedas encontrar la mitad del guión). Tenga en cuenta que posiblemente haya más funciones PrimeFaces.xxx() que deban diferirse, si ese es el caso en su aplicación, entonces puede agregarlas usted mismo dentro de window.PrimeFaces = {} (no, está en JavaScript no es posible tener un método "catch-all" para cubrir las funciones indeterminadas).

Antes de utilizar este script y <o:deferredScript> , primero debemos determinar los scripts incluidos automáticamente en el resultado HTML generado. Para la página de prueba como se muestra en la pregunta, las siguientes secuencias de comandos se incluyen automáticamente en HTML <head> generado (puede encontrarlo haciendo clic derecho en la página en el navegador web y seleccionando Ver origen ):

<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 omitir el archivo jquery.js y crear <o:deferredScripts> exactamente en el mismo orden para los scripts restantes. El nombre del recurso es la parte después de /javax.faces.resource/ excluyendo el mapeo JSF ( .xhtml en mi caso). El nombre de la biblioteca está representado por el parámetro ln request.

Por lo tanto, esto debería hacer:

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

Ahora todos los scripts con un tamaño total de aproximadamente 516 KB se difieren para el evento de carga. Tenga en cuenta que debe llamarse a DeferredPrimeFaces.begin() en el onbegin de <o:deferredScript name="primefaces.js"> onbegin <o:deferredScript name="primefaces.js"> y que se debe onsuccess DeferredPrimeFaces.apply() en el onsuccess del último <o:deferredScript library="primefaces"> .

En cuanto a la mejora del rendimiento, el punto de medición importante es el tiempo de DOMContentLoaded del contenido de DOMContentLoaded como puede encontrar en la parte inferior de la pestaña Red de las herramientas para desarrolladores de Chrome. Con la página de prueba como se muestra en la pregunta de Tomcat en una computadora portátil de 3 años, disminuyó de ~ 500 ms a ~ 270 ms. Esto es relativamente grande (¡casi la mitad!) Y hace la mayor diferencia en teléfonos móviles / tabletas, ya que hacen que HTML sea relativamente lento y los eventos táctiles están completamente bloqueados hasta que se carga el contenido de DOM.

Debe tenerse en cuenta que usted está en el caso de que las bibliotecas de componentes (personalizadas) dependan de si obedecen las reglas o pautas de administración de recursos de JSF o no. RichFaces, por ejemplo, no lo hizo y creó otra capa personalizada sobre él, por lo que es imposible usar <o:deferredScript> en él. Consulte también ¿qué es la biblioteca de recursos y cómo se debe usar?

Advertencia: si agrega componentes PrimeFaces nuevos en la misma vista posteriormente y se enfrentan a errores undefined JavaScript, entonces existe la posibilidad de que el nuevo componente también incluya su propio archivo JS, que también debe posponerse, ya que depende de las primefaces.js . Una forma rápida de calcular el script correcto sería verificar el HTML <head> generado para el nuevo script y luego agregar otro <o:deferredScript> para él basado en las instrucciones anteriores.

Bonus: CombinedResourceHandler reconoce <o:deferredScript>

Si usa OmniFaces CombinedResourceHandler , entonces es bueno saber que reconoce de forma transparente <o:deferredScript> y combina todos los scripts diferidos con el mismo atributo de group en un solo recurso diferido. Por ejemplo, esto ...

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

... terminará en dos scripts combinados diferidos que se cargan sincronizados uno después del otro. Nota: el atributo de group es opcional. Si no tiene ninguno, entonces todos se combinarán en un solo recurso diferido.

Como ejemplo en vivo, verifique la parte inferior de <body> del sitio de ZEEF . Todos los scripts esenciales relacionados con PrimeFaces y algunos scripts específicos del sitio se combinan en el primer guión diferido y todos los guiones relacionados con medios sociales no esenciales se combinan en el segundo guión diferido. En cuanto a la mejora del rendimiento de ZEEF, en un servidor JBoss EAP de prueba en hardware moderno, el tiempo para DOMContentLoaded pasó de ~ 3s a ~ 1s.

Bono # 2: delegue PrimeFaces jQuery a CDN

En cualquier caso, si ya está utilizando OmniFaces, siempre puede usar CDNResourceHandler para delegar el recurso PrimeFaces jQuery en un CDN verdadero mediante el siguiente parámetro de contexto en 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>

Tenga en cuenta que jQuery 1.11 tiene algunas mejoras de rendimiento importantes por encima de 1.10 según lo utilizado internamente por PrimeFaces 4.0 y que es totalmente compatible con versiones anteriores. Se guardó un par de cientos de milisegundos al inicializar arrastrar y soltar en ZEEF.

Mientras se analiza el rendimiento de una aplicación de Internet JSF 2.1 + PrimeFaces 4.0 con Google PageSpeed , se recomienda, entre otras cosas, posponer el análisis sintáctico de archivos JavaScript. En una página de prueba con un <p:layout> y un formulario con <p:watermark> y <p:fileUpload> que se ve como sigue ...

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

... enumera los siguientes archivos JavaScript que se pueden aplazar:

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

Se vincula a este artículo de Google Developers donde se explica la carga diferida y cómo lograrlo. Básicamente, es necesario crear dinámicamente el <script> deseado durante el evento de onload de la window . En su forma más simple por la que los navegadores viejos y con errores son ignorados por completo, se ve así:

<script> window.addEventListener("load", function() { var script = document.createElement("script"); script.src = "filename.js"; document.head.appendChild(script); }, false); </script>

De acuerdo, esto es factible si tienes control sobre esos scripts, pero los scripts listados son forzados a ser incluidos automáticamente por JSF. Además, PrimeFaces representa un conjunto de scripts en línea para el resultado HTML que están llamando directamente a $(xxx) desde jquery.js y PrimeFaces.xxx() desde primefaces.js . Esto significaría que no sería posible diferirlos para onload evento ya que solo terminaría con errores como $ is undefined y PrimeFaces is undefined .

Pero, debería ser técnicamente posible. Dado que solo jQuery no necesita ser diferido ya que muchas de las secuencias de comandos personalizadas del sitio también dependen de ella, ¿cómo podría bloquear JSF para que no incluya automáticamente las secuencias de comandos de PrimeFaces para poder posponerlas y cómo podría manejarlas? llamadas PrimeFaces.xxx() línea?


Publicada inicialmente como una respuesta para posponer la carga de primefaces.js

Agregando otra solución a esta pregunta para cualquier persona que encuentre lo mismo.

Tendrá que personalizar las HeadRenderer para lograr el orden que recomienda pagespeed. Si bien esto es algo que podría haber sido implementado por PrimeFaces, no lo veo en v5.2.RC2. Estas son las líneas en encodeBegin que necesitan cambio:

96 //Registered Resources 97 UIViewRoot viewRoot = context.getViewRoot(); 98 for (UIComponent resource : viewRoot.getComponentResources(context, "head")) { 99 resource.encodeAll(context); 100 }

Simplemente escriba un componente personalizado para la etiqueta de head y luego vincule a un representador que anule el comportamiento anterior.

Ahora no querrá duplicar el método completo solo para este cambio, puede ser más limpio agregar una faceta llamada "última" y mover los recursos de script a su inicio en su procesador como nuevos componentes deferredScript Script. Avíseme si hay interés y crearé un tenedor para demostrar cómo.

Este enfoque es "a prueba de futuro" en el sentido de que no se rompe a medida que se agregan nuevas dependencias de recursos a los componentes o cuando se agregan nuevos componentes a la vista.