编辑注:以下客座文章由 Jung von Matt 的开发总监 Matthias Rohmer 撰写
太长不看:Jung von Matt 帮助宝马在 Adobe Experience Manager 上使用 AMP 实现了惊人的用户体验。
您自由选择用于为客户项目实施的技术的情况相当罕见。已选择企业解决方案的客户通常会寻求补充这些选择的产品和服务,同时允许未来增长。
当宝马在 2017 年寻找一家公司合作重建品牌网站 www.bmw.com 时,他们正在寻找这种补充解决方案。由于几乎所有宝马网站都是使用 Adobe Experience Manager (AEM) 构建的,因此他们正在寻找一个既能够处理此类高端系统,又能够利用其功能构建易于维护的高性能网站的团队。
这是宝马在其现有网站上错失的一个目标。尽管共享相同的后端,但它们基于各种前端框架和库。在这个过程中相当早的时候,我们决定为这个堆栈带来一些新鲜空气。因此,我们希望引入 AMP 作为管理前端技术。那时我们开始头晕目眩:将 AMP 与 AEM 集成,尤其是与对前端开发做出如此多假设的 CMS 集成,哪种方式最好?
将 AMP 与 AEM 集成时需要解决的问题
经过一些研究,我们意识到有三个主要挑战需要解决,以便从 AEM 呈现有效的 AMP 页面:
- 由于 AMP 要求文档的所有 CSS 都内联在
<head>
中,因此我们需要找到一种方法来呈现我们的 CSS,而不是 AEM 内置的 ClientLib 功能 - 为了使我们所有的页面都符合 AMP 规范,我们需要一种机制,仅为页面上实际使用的 AMP 组件呈现资源提示(
<script async custom-element="amp-carousel" src="https://cdn.ampproject.org/v0/amp-carousel-0.2.js"></script>
) - 为了能够为回头客逐步增强网站,AEM 需要能够同时呈现 AMP 和非 AMP 页面
AMP 中的内联 CSS
AEM 采用了一种相当简化的方式来处理页面样式:ClientLib 机制将负责根据所谓的类别合并页面所需的所有 CSS。根据这些类别,您随后可以将 AEM 创建的 <link>
标记添加到您的模板中。这些标记将指向包含您所有样式的已构建样式表。
借助 AEM 内置的 重写管道,我们可以使用那些 <link>
元素将那些样式表合并到一个通用的 <style amp-custom>
标记中。以下(高度压缩的)代码片段应让您了解此类转换器可能是什么样子
StringBuilder styles = new StringBuilder();
boolean writeStyles = false;
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (localName.equalsIgnoreCase("link")) {
// If the element currently in queue is a link tag inspect it
String href = atts.getValue("href");
String rel = atts.getValue("rel");
if (rel.equalsIgnoreCase("stylesheet")) {
String css = "";
// TODO: Load the stylesheet from the JCR, store it with others loaded
// so far and append to styles
}
return;
}
if (localName.equalsIgnoreCase("style")) {
if (atts.getIndex("amp-custom")) {
writeStyles = true;
// TODO: Use this flag to emit all styles gathered in styles
// in the transformer's characters method
}
return;
}
contentHandler.startElement(uri, localName, qName, atts);
}
Code language: JavaScript (javascript)
仅添加所需的资源提示
为了确保 AMP 有效性,我们需要解决的另一个挑战是仅将那些实际上需要的 <script>
元素添加到页面中。为此,我们在项目中引入了自定义节点类型
<ampJS
jcr:primaryType="bmw:ampJSResource"
bmw:ampCustomElementTag="[amp-video]"/>
Code language: HTML, XML (xml)
我们的每个 AEM 组件都有一个该类型的子组件,其中包含有关它所依赖的 AMP 组件的信息(如上例中的 amp-video)。在此处使用自定义节点类型的优势在于,当页面被呈现以确定所需的 AMP 组件时,我们可以安全且快速地查询那些节点。在代码中,这类似于以下内容
final PageManager pageManager = resource.getResourceResolver().adaptTo(PageManager.class);
final String currentPage = pageManager.getContainingPage(resource).getPath() + "/jcr:content";
final String query = String.format("SELECT * FROM [bmw:ampResourceHint] AS s WHERE ISDESCENDANTNODE(s,'%s')", currentPage);
final Iterator<Resource> result = resource.getResourceResolver().findResources(query, Query.JCR_SQL2);
while (result.hasNext()) {
Resource queryResource = result.next();
final String type = queryResource.getParent().getResourceType();
ValueMap properties = queryResource.adaptTo(ValueMap.class);
String[] usedComponents = properties.get("bmw:usedAmpComponents", String[].class);
if (usedComponents != null && usedComponents.length != 0) {
// TODO: Store all used components somewhere for later rendering
}
}
Code language: JavaScript (javascript)
然后,可以通过在 HTML 模板中使用 data-sly-use
属性与 data-sly-repeat
结合使用来轻松调用此逻辑片段,以将所有必需的资源提示打印到页面的头部。
与 PWA 一起提供 AMP
对于 www.bmw.com,我们希望确保网站能够尽可能快地显示在用户屏幕上。为实现此目的,每个首次访问者都将收到我们页面的 AMP 版本。与此同时,我们希望实现 AMP 本身无法提供但 PWA(它仍然 基于 AMP!)可以提供的一些功能。
这意味着我们的应用程序需要能够提供同一文档的两个版本。幸运的是,借助 Sling 选择器,AEM 已提供此功能。
要建立一个选择器,您需要做的就是并排实现两个模板。Sling 引擎应默认解析到的模板简单地称为 html.html
。另一个模板以您的选择器命名。在我们的案例中,它是 pwa.html
,它使我们的所有文章都可以通过 brooklyn-beckham-car-photography.html 以纯 AMP 版本访问,或者通过 brooklyn-beckham-car-photography.pwa.html 结合我们的 PWA 功能访问。
使用此方法,我们找到了一种独立提供有效 AMP 页面和 PWA 的方法。但用户如何最终访问我们的渐进式网络应用程序?这就是 amp-install-service-worker
大放异彩的地方。通过使用此 AMP 组件,www.bmw.com 在用户访问其在任何 AMP 缓存中的页面之一时立即安装 Service Worker。从那时起,我们能够将所有请求重写为 brooklyn-beckham-car-photography.html,改为 brooklyn-beckham-car-photography.pwa.html,以便在不影响用户体验的情况下对其进行增强。
对于我们而言,在构建宝马新的国际营销网站时,这三个是主要挑战。最终,所有这些问题都可以在不重新发明轮子的情况下得到解决,方法是以创造性的方式使用已内置于 AEM 和 AMP 中的功能。
AMP 和 Adobe 已与 Bounteous 合作,以便在 Adobe Experience Manager 上构建 AMP 网站变得更加容易。了解更多信息并开始 此处。
作者:Jung von Matt 的开发总监 Matthias Rohmer