Using Accelerated Mobile Pages (AMP) in Jahia
Accelerated Mobile Pages (AMP) is an open source project supported by Google to improve mobile phone web browsing. Simply put, it's an HTML page with dedicated scripts and tags.
Currently AMP technology is mostly used to display news and articles, content that are read on a cellular phone and that needs to be loaded fast. This kind of content match well with Jahia “content templates” approach.
We describe here how to integrate AMP technology with a simple example that creates AMP pages to display content element of a news type based on the Digitall demo site. This example can be transposed easily to any other kind of content
Prerequisites
To understand and reproduce this example, you need to run an instance of Jahia that contains the sample Digitall site.
Creating AMP content templates
As any other content rendering in Jahia, a dedicated AMP view needs to be created for the news. It can be done either with adding it to the template set or creating a dedicated module for it. To ease this example, we have created a sample module that can be found here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/1-create-module
Compile and deploy it to your Jahia instance.
mvn clean install
Creating the content template
The goal is to have an AMP content template for news, other type of content may need to get several content templates. First, create a template folder for the news where you will put the AMP content template. This will help to use the same template name for all the AMP content templates.
First, add a dependency to the news module
Next create a template, and a content template for the news content type:
Uncheck "Automatically synchronize name with title:"
Set the same to : amp
If you encounter any issue in this phase, please check out the result here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/2-content-template
Creating views
AMP needs a dedicated structure for its page. You can find more information on it here:
https://www.ampproject.org/docs/get_started/create/basic_markup
In Jahia, it means to create views. First, create the template view, then the view of the news nodetype.
Template view
From the Jahia studio, select the file system tab, and right click on the root folder, select Add view
In the selector, type "template" then pick Template. Choose Template as a parent type of "Content template" and pageTemplate.
Copy the content from https://www.ampproject.org/docs/get_started/create/basic_markup and save the view naming it: amp-sample
The view is now available in the file system tab:
Create an area name pagecontent in this view to be able to create content.
To do so, replace the inner <body> section to a Jahia area tag that defines a pagecontent area that displays the main resource:
<template:area editable="true" moduleType="existingNode" view="detail-amp"/>
This is the equivalent of creating a standard area, and drag and drop a main resource area component in it, setting the view to detail-amp.
News nodetype view
As for template, create a new view from the news nodetype but this time, copy the content from the detail view of the news that you can get here:
Create the view as done for the template but selecting "News Entry" instead of template in the nodetypes list, copy the detail view content of the new in it, then save it naming it detail-amp.
You should get the following result:
After copying the detail view of the news from github, remove the following lines from the code of your view:
<%@ taglib prefix="bootstrap" uri="http://www.jahia.org/tags/bootstrapLib" %>
<bootstrap:addCSS/>
Now the views are set, compile and deploy your module to get the view working
Click on "compile and deploy", the first icon in the file system tab.
Setting the content template view
Now assign the views to the previously created template. Edit the news content template to set the view to amp-sample.
Everything is almost set. Let's compile the module once again, and enable it on the Digitall Demo site.
Click on module detail in the top bar, then enable the module on Digitall Demo site.
If you encounter any trouble to get it work, you can get the source code of this step here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/3-create-views
Cleaning up the view
Now exists an AMP view for our news. But this view must be clean up to match the AMP constraints. You can find more detail on the AMP format here:
https://www.ampproject.org/docs/guides/responsive_amp
To summarize, no external javascript or css are allowed except the ones provided by AMP, so the view needs to be cleaned to only keep what is allowed by AMP.
Assuming Jahia is installed in root context with an Digitall imported site, the relevant URL displaying a news is:
And the AMP view of the news: http://localhost:8080/cms/render/default/en/sites/digitall/home/newsroom/news-entry/article/movies-can-determine-your-succes.amp.html
Validating the AMP view
This validation can be done using the debugging tools of AMP to debug as described here:
https://www.ampproject.org/docs/get_started/create/preview_and_validate
In this case, enable the validation activating Chrome dev tools and using the URL
http://localhost:8080/jahia/cms/render/default/en/sites/digitall/home/newsroom/news-entry/article/movies-can-determine-your-succes.amp.html#development=1 to get the following result:
As displayed, there are 2 errors that need to get fixed to validate the AMP page. An AMP page must be valid to be served by a cache.
Removing StaticAssetFilter unwanted output
The first error is due to the StaticAssetFilter that manage the resources that the modules can provide. As AMP does not support external resource, the entries added by this filter must be removed. There are 2 possibilities:
- disabling the filter and override it to define a custom filter that not do the import.
- Create a custom filter that remove unwanted entries.
In this example, only the 2nd filter is described.
Creating a newJahia Render Filter
Add a java class named AssetsAMPFilter in the package org.jahia.modules.digitall.filters
A default Filter contains
package org.jahia.modules.digitall.filters;
import org.jahia.services.render.RenderContext;
import org.jahia.services.render.Resource;
import org.jahia.services.render.filter.AbstractFilter;
import org.jahia.services.render.filter.RenderChain;
/**
* Render Filter that cleans up the StaticAssetFilter for AMP rendering
*/
public class AssetsAMPFilter extends AbstractFilter{
@Override
public String execute(String previousOut, RenderContext renderContext, Resource resource, RenderChain chain) throws Exception {
return super.execute(previousOut, renderContext, resource, chain);
}
}
The bean declaration in the spring module file must be set (you have to remove the disabled extension)
src/main/resources/META-INF/spring/amp-sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="cleanUpAssetsAMPFilter" class="org.jahia.modules.digitall.filters.AssetsAMPFilter">
<property name="priority" value="-1"/>
<property name="applyOnEditMode" value="false"/>
<property name="applyOnConfigurations" value="page"/>
<property name="applyOnNodeTypes" value="jnt:news, jnt:article"/>
</bean>
</beans>
Values explanation:
- priority: The StaticAssetFilter must be changed, this operation must be performed after its execution, meaning that the filter must be before it, less than 0)
- applyOnEditMode: Due to its constraints, the AMP view is not working in edit mode.
- applyOnConfigurations: Set to page, as the StaticAssetFilter.
- applyOnNodeTypes: The applied filter is restricted to the news only.
Excluding unwanted content
<script type="text/javascript">
var contextJsParameters={contextPath:"/jahia",lang:"en",uilang:"en",siteUuid:"40ef8fce-72e8-41ec-9d93-32a2fe9807ef",wcag:true,ckeCfg:""}; var CKEDITOR_BASEPATH="/jahia/modules/ckeditor/javascript/"; var scayt_custom_params=new Array(); scayt_custom_params['sLang']='en_US';
</script>
Using the following code in the AssetsAMPFilter removes it:
private final static String MARKER = "macro LineToRemove not found";
@Override
public String execute(String previousOut, RenderContext renderContext, Resource resource, RenderChain chain) throws Exception {
String out = super.execute(previousOut, renderContext, resource, chain);
// Remove unsuported scripts and link lines added by the staticAssetFilter
if (StringUtils.equals(resource.getTemplate(), "amp")) {
String[] lines = out.split(System.getProperty("line.separator"));
for (int i = 0; i < lines.length; i++) {
if (StringUtils.contains(lines[i], "contextJsParameters")) {
lines[i] = lines[i - 1] = lines[i + 1] = MARKER;
} else if (StringUtils.contains(lines[i], "<link id=\"staticAssetCSS")) {
lines[i] = MARKER;
}
}
StringBuilder result = new StringBuilder();
for (String s : lines) {
if (!StringUtils.equals(s, MARKER)) {
result.append(s).append(System.getProperty("line.separator"));
}
}
out = result.toString();
}
return out;
}
Once compiled and deploy, only one error remains in AMP validation
If you encounter any difficulties to get to that point, check-out the source code from here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/4-create-render-filter
Fixing the news view
The remaining error is due to the image tag that is not supported by AMP as described here:
https://www.ampproject.org/docs/guides/amp_replacements
The news view needs to be updated to fit the AMP usage, changing the <img> tag to <amp-img>
<figure><img src="<c:url value="${image.node.url}" context="/"/>" alt="${image.node.displayableName}"></figure>
With
<c:set var="width" value="${image.node.properties['j:width'].long}"/>
<c:set var="height" value="${image.node.properties['j:height'].long}"/>
<figure><amp-img src="<c:url value="${image.node.url}" context="/"/>" width="${width}" height="${height}">
<noscript>
<img src="<c:url value="${image.node.url}" context="/"/>" alt="${image.node.displayableName}">
</noscript>
</amp-img>
</figure>
Code cleanup can also be performed to remove unsupported tag like <template:addResource/>
Now the page is validated.
If you get any trouble to get to that point, you can download the source of this step here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/5-fix-image
Header links
To be discoverable, a page must declare that an AMP version is available. Add this information in the html version of the amp page. A link to the html version can be set in the AMP page.
https://www.ampproject.org/docs/guides/discovery
For this, adopt the previously used filter. Depending of the context, the corresponding link will be added in the header section.
Editing the filter
String out = super.execute(previousOut, renderContext, resource, chain);
// Build the amp links that will be used in the header
URLGenerator url = renderContext.getURLGenerator();
String resourceUrl = url.getServer() + url.getContext() + url.getBase() + resource.getNode().getPath();
String ampLink = "<link rel=\"ampType\" href=\"" + resourceUrl + "TEMPLATE.html\" />";
// Remove unsuported scripts and link lines added by the staticAssetFilter
if (StringUtils.equals(resource.getTemplate(), "amp")) {
String[] lines = out.split(System.getProperty("line.separator"));
for (int i = 0; i < lines.length; i++) {
if (StringUtils.contains(lines[i], "contextJsParameters")) {
lines[i] = lines[i - 1] = lines[i + 1] = MARKER;
} else if (StringUtils.contains(lines[i], "<link id=\"staticAssetCSS")) {
lines[i] = MARKER;
}
}
StringBuilder result = new StringBuilder();
for (String s : lines) {
if (!StringUtils.equals(s, MARKER)) {
result.append(s).append(System.getProperty("line.separator"));
}
}
out = result.toString();
// Set the values for AMP Link
ampLink = StringUtils.replace(ampLink, "ampType", "canonical");
ampLink = StringUtils.replace(ampLink, "TEMPLATE", "");
} else {
// Set the values for AMP Link
ampLink = StringUtils.replace(ampLink, "ampType", "amphtml");
ampLink = StringUtils.replace(ampLink, "TEMPLATE", ".amp");
}
// Add amp link
out = StringUtils.replace(out, "</head>", ampLink + "\n</head>");
return out;
Removing the hardcoded link
In the header section of template.amp-sample.jsp is a hard-coded link from the example that has to be removed
<link rel="canonical" href="http://example.ampproject.org/article-metadata.html" />
Compiling and testing
Once compiled, go to the html page to see in the header section the following link:
<link rel="amphtml" href="http://localhost:8080/cms/render/default/en/sites/digitall/home/newsroom/news-entry/article/movies-can-determine-your-succes.amp.html" />
And in the AMP page this one:
<link rel="canonical" href="http://localhost:8080/cms/render/default/en/sites/digitall/home/newsroom/news-entry/article/movies-can-determine-your-succes.html" />
If you get any trouble to get this result, you can get the source code from:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/6-add-header-links
Making it nicer
Creating custom styles
It is possible to declare custom style. This has to be done in the header section of the page, using the amp-custom style attribute.
https://www.ampproject.org/docs/guides/responsive_amp
<style amp-custom>
.btn-primary {
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
background-color: #6600cc;
background-image: -moz-linear-gradient(top, #6600cc, #6600cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6600cc), to(#6600cc));
background-image: -webkit-linear-gradient(top, #6600cc, #6600cc);
background-image: -o-linear-gradient(top, #6600cc, #6600cc);
background-image: linear-gradient(to bottom, #6600cc, #6600cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff6600cc', endColorstr='#ff6600cc', GradientType=0);
border-color: #6600cc #6600cc #400080;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #ffffff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 10px;
font-size: 14px;
font-weight: bold;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-color: #ffffff;
border: 1px solid #cccccc;
border-bottom-color: #b3b3b3;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
text-shadow: 0 1px rgba(0, 0, 0, 0.1);
}
</style>
Adding AMP scripts
Amp scripts can be added to enhance the rendering of the news. For example, using amp-fit-text.
First add in the header section of the template:
<script async custom-element="amp-fit-text" src="https://cdn.ampproject.org/v0/amp-fit-text-0.1.js"></script>
<amp-fit-text width="300" height="45" layout="responsive" max-font-size="45">
${title.string}
</amp-fit-text>
Instead of the <h1>
tag
Expected result
You can get the source code from here:
https://github.com/dgriffon/sample-AMP-DX-integration/tree/7-styles-and-scripts
And get the compiled module from:
Going further
Support for files in rich text
This example assumes that the rich text used for the news is pure valid HTML with no image in it. The filter can be improved to change all the <img> tags to the corresponding AMP tags. The same way for video or other resources
Support for links
The goal of an AMP page is to be cached, meaning that the links within a page are not known until other pages are in cache. Google provides an API to get available links from its cache. You can find more information here:
https://developers.google.com/amp/cache/use-amp-url
Further Jahia integration
All the views for the component you want to use in your amp page must be currently created. One of our goal is to provide filters and tools to help content manager build AMP pages from an already stored content and concentrate on their content and let Jahia manage the rendering.