Load client side resources in a content centric application

  • Matthieu Cornut

We saw in a previous post, best-practices to optimize the client-side resources load, and by the way, reduce our web page's loading time. Let's keep in mind these concepts and solutions and assume that we develop a content-centric application in Java/JSP.

This application contains components that have many renditions for multi-channel usage. (As explained in our previous webmardi about content-centric application.)

Let's take a component with a “in page” rendition, and a “standalone” rendition. So this component can be displayed integrated to a page, for example by sling or the JCR api, and as standalone HTML by a direct HTTP request. And in both cases, this component requires some client-side resources to be well displayed.

In the “standalone” usage of the component: We must refer the  resources inside the component's “standalone” rendition script:

<%-- Draws a carousel standalone --%>
<%@include file="${appRoot}/global.jsp"%>

<ul class="carousel">
    <c:forEach items="${slides}" var="slide">
        <li class="slide">
            <a href="${slide.path}.html">
                <img src="${slide.image}" alt="${slide .title}" />
            </a>
        </li>
    </c:forEach>
</ul>

<script src="${staticPath}/external-libs/carousel.js"></script>
<script src="${staticPath}/external-libs/modernizr.js"></script>
<script src="${staticPath}/internal-libs/carousel-init.js"></script>

Then in the “in-page” usage: We refer the  resource s in our “container” page rendition's script.

<%-- Container page --%>
<%@include file="${appRoot}/global.jsp"%>
[...]
<sling:include path="/content/carousel.in-page" />
[...]
<script src="${staticPath}/external-libs/carousel.js"></script>
<script src="${staticPath}/external-libs/modernizr.js"></script>
<script src="${staticPath}/internal-lib.js"></script>
<script src="${staticPath}/internal-libs/carousel-init.js"></script>
[...]

We see in this inclusion that we must specify a “in-page” rendition that doesn't include the _resource _again. And here is this “in-page” rendition:

<%-- Draws a carousel in-page --%>
<%@include file="${appRoot}/global.jsp"%>

<ul class="carousel">
    <c:forEach items="${slides}" var="slide">
        <li class="slide">
            <a href="${slide.path}.html">
                <img src="${slide.image}" alt="${slide .title}" />
            </a>
        </li>
    </c:forEach>
</ul>

Again we include our script in our container page rather than in the component, exactly to manage coherence of the resource s inclusions.

This content centric application model, doesn't solve the problem described in previous post: We still have to find the golden mean between too much or not enough granularity in the categories of the  client-side resources.

But we see that renditions of a component are not dependent each other. Meaning, we can choose what resource to load in “standalone”, “in-page”, or any else renditions .

Note that if it's not a problem to make “standalone” and “in-line” renditions dependent each other, you can put all the common code (the ul and foreach loop) in a common script, and include it with standard JSP methods.

AEM ClientLibs

The JCR/Sling based CMS “Adobe Experience Manager” comes with a great feature, designed to automate all these inclusions and to ensure a good coherence of your _resources _categories. This is the “clientlib” system.

First we create somewhere in the JCR, a node with the primary type “cq:ClientLibraryFolder”. This node will have a property “categories” where we'll name one or many categories (for example “my-internal-lib”). In this node, we put our scripts (JS and CSS), that we want to be loaded, when we call (in a rendition jsp) the previously named category. That is the basic concept.

Added to this, “cq:ClientLibraryFolder” have these very useful properties:

PropertyTypeDescription
dependenciesString[]List categories this one depends on
categoriesString[]List the categories that this folder constitute
embedString[]Embed the code of listed categories, in the categories this folder constitute.
channelsString[]List the channels (related to devices or sites) for which the constituted categories are valid

Here's a client-library-folder for our carousel component example:

  • carousel-clientlib – cq:ClientLibraryFolder

dependencies: modernizr, internallib → any dependencies you need.

categories: carousel → We call it “carousel”.

* carousel.js → any external library you need)
* carousel-init.js → any custom specific code you need).

In the rendition JSP, I call this category with a jsp tag:

<%-- Draws a carousel standalone and in-page --%>
<%@include file="${appRoot}/global.jsp"%>
<ul class="carousel">
    <c:forEach items="${slides}" var="slide">
        <li class="slide">
            <a href="${slide.path}.html">
                <img src="${slide.image}" alt="${slide .title}" />
            </a>
        </li>
    </c:forEach>
</ul>
<ui:includeClientLib categories="carousel" >
  • The HTML will contain inclusions of all dependent scripts.
  • The call order is automatically calculated by the system, base on the dependencies chain.
  • A script will never be included more than once.
  • We don't need two different renditions anymore. This one works fine for “standalone” and “in-page” usage of the component.

Use of clientlib system is not required by AEM, but at Liip in the AEM squad, we find the feature great. So we use it.

Unfortunately there's a little performance problem with this. We know that JS should be loaded at the end of the body. To stay compliant to the content-centric model while providing these features, or for any reason I don't know, AEM decided to print the inclusions right where the tag is used.

The easy solution we found at Liip to resolve this last problem is just to encapsulate the AEM tag in a custom tag:

<liip:includeClientLib categories="carousel" >

In this encapsulation, we buffered the output generated by AEM's clientlib system, until we print this output with:

<liip:printClientLibs />

A way to do big, clean, and scalable content-centric application, with very good client side performance.


Qu’en pensez-vous?