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:
Property | Type | Description |
dependencies | String[] | List categories this one depends on |
categories | String[] | List the categories that this folder constitute |
embed | String[] | Embed the code of listed categories, in the categories this folder constitute. |
channels | String[] | 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.