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.