Odroczenie ładowania i parsowania plików JavaScript PrimeFaces

Analizując wydajność aplikacji webowej JSF 2.1 + PrimeFaces 4.0 z Google PageSpeed , zaleca m.in. odroczenie parsowania plików JavaScript. Na stronie testowej z <p:layout> i Formularzu z <p:watermark> i <p:fileUpload>, który wygląda następująco ...

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

... zawiera listę następujących plików JavaScript, które mogą zostać odroczone:

  • primefaces.js (219.5 KiB)
  • jquery-plugins.js (191.8 KiB)
  • jquery.js (95.3 KiB)
  • layout.js (76.4 KiB)
  • fileupload.js (23.8 KiB)
  • watermark.js (4.7 KiB)

To linki do Ten artykuł programistów Google w którym odroczone ładowanie jest wyjaśnione, jak to osiągnąć. W zasadzie musisz dynamicznie utworzyć żądaną <script> podczas onload zdarzenia window. W najprostszej formie, w której stare i popsute przeglądarki są całkowicie ignorowane, wygląda to tak: {]}

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

Ok, jest to wykonalne, jeśli masz kontrolę nad tymi skryptami, ale wszystkie wymienione skrypty są automatycznie dołączane przez JSF. Ponadto PrimeFaces renderuje kilka skryptów wbudowanych do wyjścia HTML, które bezpośrednio wywołują $(xxx) z jquery.js i PrimeFaces.xxx() z primefaces.js. Oznaczałoby to, że nie byłoby łatwo przełożyć ich na zdarzenie onload, ponieważ skończyłyby się tylko na błędach takich jak $ is undefined i PrimeFaces is undefined.

Ale to powinno być technicznie możliwe. Biorąc pod uwagę, że tylko jQuery nie musi być odroczony, ponieważ wiele niestandardowych skryptów witryny również na tym polega, Jak mogę zablokować JSF przed automatycznym włączaniem skryptów PrimeFaces, aby móc je odroczyć i jak poradzić sobie z tymi inline PrimeFaces.xxx() wywołaniami?
Author: BalusC, 2014-04-19

2 answers

Użyj <o:deferredScript>

Tak, jest to możliwe dzięki komponentowi <o:deferredScript>, który jest nowy od OmniFaces 1.8.1. Dla zainteresowanych technicznie, oto kod źródłowy:

Zasadniczo komponent będzie podczas zdarzenia postAddToView (a więc w czasie budowania widoku) poprzez UIViewRoot#addComponentResource() dodaj się jako nowy script resource in end of <body> and via Hacks#setScriptResourceRendered() powiadom JSF, że zasób skryptów jest już renderowany (używając klasy Hacks, ponieważ nie ma dla tego standardowego podejścia JSF API (jeszcze?)), dzięki czemu JSF nie będzie już automatycznie włączał / renderował zasobów skryptów. W przypadku Mojarra i PrimeFaces należy ustawić atrybut context z kluczem name+library i wartością true, aby wyłączyć automatyczne włączanie zasobu.

Renderer napisze <script> element z OmniFaces.DeferredScript.add(), w którym przekazywany jest adres URL zasobów wygenerowany przez JSF. Ten JS helper będzie z kolei zbierać adresy URL zasobów i dynamicznie tworzyć nowe elementy <script> dla każdego z nich podczas zdarzenia onload.

Użycie jest dość proste, wystarczy użyć <o:deferredScript> w taki sam sposób jak <h:outputScript>, Z library i name. Nie ma znaczenia, gdzie umieścisz komponent, ale większość samokontroli będzie w końcu <h:head> w następujący sposób:

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

Możesz mieć ich wiele i ostatecznie zostaną załadowane w tej samej kolejności, w jakiej zostały zadeklarowane.


Jak używać <o:deferredScript> z PrimeFaces?

Jest to trochę trudne, rzeczywiście z powodu tych wszystkich skryptów wbudowanych generowanych przez PrimeFaces, ale nadal wykonalne ze skryptem pomocniczym i akceptowanie, że jquery.js nie będzie odroczone (to może być jednak obsługiwane przez CDN, zobacz później). Aby pokryć te wewnętrzne wywołania PrimeFaces.xxx() do pliku primefaces.js o wielkości prawie 220 KB, należy utworzyć skrypt pomocniczy który jest mniejszy niż 0,5 KiB minifigurowany :

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

Zapisz jako /resources/yourapp/scripts/primefaces.deferred.js. Zasadniczo wszystko, co robi, to przechwytywanie PrimeFaces.ab(), cw() i focus() wywołania (jak można znaleźć w dolnej części skryptu) i odroczenie ich do wywołania DeferredPrimeFaces.apply() (Jak można znaleźć w połowie skryptu). Zauważ, że istnieje prawdopodobnie więcej PrimeFaces.xxx() funkcji, które należy odroczyć, jeśli tak jest w Twojej aplikacji, możesz dodać je samodzielnie wewnątrz window.PrimeFaces = {} (Nie, w JavaScript nie można mieć metoda "catch-all" Na pokrycie nieokreślonych funkcji).

Przed użyciem tego skryptu i <o:deferredScript>, musimy najpierw określić automatycznie dołączone skrypty w wygenerowanym wyjściu HTML. Dla strony testowej, jak pokazano w pytaniu, następujące skrypty są automatycznie dołączane do generowanego HTML <head> (można to znaleźć, klikając prawym przyciskiem myszy stronę w webbrowser i wybierając Zobacz źródło):

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

Musisz pominąć plik jquery.js i utworzyć <o:deferredScripts> W dokładnie tej samej kolejności dla Pozostałe Skrypty. Nazwa zasobu jest częścią po /javax.faces.resource/ z wyłączeniem mapowania JSF (.xhtml w moim przypadku). Nazwa biblioteki jest reprezentowana przez parametr ln request.

Tak więc powinno być:

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

Teraz wszystkie skrypty o łącznym rozmiarze około 516KiB są odroczone do zdarzenia onload. Należy pamiętać, że {[46] } musi być wywołane w onbegin z <o:deferredScript name="primefaces.js"> i że DeferredPrimeFaces.apply() musi być wywołane w onsuccess z ostatni <o:deferredScript library="primefaces">.

Co do poprawy wydajności, ważnym punktem pomiarowym jest DOMContentLoaded czas, który można znaleźć w dolnej części Network w narzędziach programistycznych Chrome. Strona testowa jak pokazano w pytaniu zadanym przez Tomcata na 3-letnim laptopie, zmniejszyła się z ~500ms do ~270ms. jest to stosunkowo ogromna (prawie połowa!) i robi największą różnicę na telefonach komórkowych/tabletach, ponieważ renderują HTML stosunkowo wolno, A zdarzenia dotykowe są całkowicie blokowane, dopóki zawartość DOM nie zostanie załadowana.

Należy zauważyć, że jesteś w przypadku (niestandardowe) biblioteki komponentów zależne od tego, czy są zgodne z zasadami/wytycznymi JSF resource management rules / guidelines, czy też nie. Na przykład RichFaces nie i homebrewed inną warstwę niestandardową na nim, co uniemożliwia użycie <o:deferredScript> na nim. Zobacz także co to jest Biblioteka Zasobów i jak powinna być używana?

Warning: jeśli dodajesz nowe komponenty PrimeFaces w tym samym widoku i napotkasz błędy JavaScript undefined, wtedy szansa jest duża, że nowy komponent ma również swój własny plik JS, który również powinien zostać odroczony, ponieważ zależy od primefaces.js. Szybkim sposobem na ustalenie właściwego skryptu byłoby sprawdzenie wygenerowanego HTML <head> dla nowego skryptu, a następnie dodanie innego <o:deferredScript> dla niego na podstawie powyższych instrukcji.


Bonus: CombinedResourceHandler rozpoznaje <o:deferredScript>

Jeśli zdarzy ci się użyć OmniFaces CombinedResourceHandler, dobrze wiedzieć, że rozpoznaje <o:deferredScript> i łączy wszystkie skrypty z ten sam group atrybut do pojedynczego zasobu odroczonego. Np. to ...

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

... kończy się w dwóch połączonych skryptów odroczonych, które są ładowane synchronicznie po sobie. Uwaga: atrybut group jest opcjonalny. Jeśli ich nie masz, wszystkie zostaną połączone w jeden odroczony zasób.

Jako przykład na żywo sprawdź dno <body> strony ZEEF . Wszystkie niezbędne Skrypty związane z PrimeFaces i niektóre skrypty site-specific są połączone w pierwszy skrypt odroczony i wszystkie nieistotne Skrypty związane z mediami społecznościowymi są połączone w drugi skrypt odroczony. W 2008 roku firma ZEEF wprowadziła do swojej oferty nowy model, który został zaprojektowany z myślą o klientach z całego świata.]}


Bonus # 2: deleguj PrimeFaces jQuery do CDN

W każdym razie, jeśli używasz już OmniFaces, zawsze możesz użyć CDNResourceHandler aby przekazać zasób jQuery PrimeFaces do prawdziwego CDN, wykonaj następujące czynności 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>

Zauważ, że jQuery 1.11 ma kilka znaczących ulepszeń wydajności w stosunku do 1.10 jako wewnętrznie używany przez PrimeFaces 4.0 i że jest w pełni kompatybilny wstecz. Zapisał on kilkaset milisekund podczas inicjalizacji drag ' n ' drop na ZEEF.

 32
Author: BalusC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:02

Początkowo opublikowane jako odpowiedź na odroczyć primefaces.js loading


Dodanie innego rozwiązania tego pytania dla każdego, kto napotyka to samo.

Będziesz musiał dostosować primefaces HeadRenderer aby osiągnąć zamawianie pagespeed zaleca. Chociaż jest to coś, co mogło zostać zaimplementowane przez PrimeFaces, nie widzę tego w v5.2.RC2. Są to Linie encodeBegin, które wymagają zmiany:

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

Po prostu napisz Niestandardowy komponent dla head tag, a następnie powiązać go z rendererem, który nadpisuje powyższe zachowanie.

Teraz nie chcesz powielać całej metody tylko dla tej zmiany, może być czystsze dodanie aspektu o nazwie "last" i przeniesienie zasobów skryptu do jego początku w rendererze jako nowe komponenty deferredScript. Daj mi znać, jeśli będzie zainteresowanie, a stworzę widelec, aby zademonstrować, jak.

To podejście jest "przyszłościowe" w tym sensie, że nie psuje się, gdy nowe zależności zasobów są dodawane do komponentów lub jako nowe komponenty są dodawane do widoku.

 3
Author: Timir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:02