【发布时间】:2020-12-11 09:24:19
【问题描述】:
请为此提供久经考验的解决方案,并提供需要完成的正确步骤。提前致谢
【问题讨论】:
请为此提供久经考验的解决方案,并提供需要完成的正确步骤。提前致谢
【问题讨论】:
最简单的可能只是手动编写clientlib包含标签(例如,只需编写<script defer src="/path/to/clientlib.js"></script>)。
到目前为止,我发现的唯一其他解决方案是创建一个重写器工厂来执行此操作。代码大多抄自acs-commons。
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;
import lombok.ToString;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.rewriter.ProcessingComponentConfiguration;
import org.apache.sling.rewriter.ProcessingContext;
import org.apache.sling.rewriter.Transformer;
import org.apache.sling.rewriter.TransformerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.FieldOption;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceRanking;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.adobe.granite.ui.clientlibs.ClientLibrary;
import com.adobe.granite.ui.clientlibs.HtmlLibrary;
import com.adobe.granite.ui.clientlibs.HtmlLibraryManager;
import com.adobe.granite.ui.clientlibs.LibraryType;
@Component(
property = "pipeline.type=clientlib-async-include-transformer",
service = TransformerFactory.class
)
@ServiceDescription(
"Add defer/async to clientlib include script tags."
)
@ServiceRanking(2)
@Slf4j
@ToString
public final class ClientlibIncludeRewriterTransformerFactory implements TransformerFactory {
private static final String PROXY_PREFIX = "/etc.clientlibs/";
private static final String MIN_SELECTOR = "min";
private static final String SELECTOR_EXTENSION_SEPARATOR = ".";
private static final String MIN_SELECTOR_SEGMENT = SELECTOR_EXTENSION_SEPARATOR + MIN_SELECTOR;
@Reference
private HtmlLibraryManager htmlLibraryManager;
@ToString.Exclude
private Map<String, ClientLibrary> clientLibrariesCache;
@Override
public Transformer createTransformer() {
return new ClientlibIncludeRewriterTransformer();
}
private Attributes rewrite(
final String elementName,
final String linkAttribute,
final Attributes attrs,
final SlingHttpServletRequest request
) {
final String linkedPath = attrs.getValue(StringUtils.EMPTY, linkAttribute);
final HtmlLibrary clientLibrary = getLibrary(linkedPath, request);
if (clientLibrary == null) {
log.info("{} include {} does not point to a client library", elementName, linkedPath);
return attrs;
}
final AttributesImpl newAttributes = new AttributesImpl(attrs);
if (clientLibrary.getType() != LibraryType.JS) {
return attrs;
}
final Resource library = request.getResourceResolver().getResource(clientLibrary.getLibraryPath());
if (library == null) {
return attrs;
}
if (library.getValueMap().get("useAsync", Boolean.FALSE)) {
newAttributes.addAttribute(StringUtils.EMPTY, "async", "async", "CDATA", null);
}
if (library.getValueMap().get("useDefer", Boolean.FALSE)) {
newAttributes.addAttribute(StringUtils.EMPTY, "defer", "defer", "CDATA", null);
}
return newAttributes;
}
private HtmlLibrary getLibrary(final String linkedPath, final SlingHttpServletRequest request) {
final String contextPath = request.getContextPath();
String libraryPath = linkedPath;
if (StringUtils.isNotBlank(contextPath)) {
libraryPath = libraryPath.substring(contextPath.length());
}
final String extension = StringUtils.substringAfterLast(libraryPath, SELECTOR_EXTENSION_SEPARATOR);
final LibraryType libraryType = getLibraryTypeFromExtension(extension);
if (libraryType == null) {
return null;
}
libraryPath = StringUtils.substringBeforeLast(libraryPath, SELECTOR_EXTENSION_SEPARATOR);
if (libraryPath.endsWith(MIN_SELECTOR_SEGMENT)) {
libraryPath = StringUtils.removeEnd(libraryPath, MIN_SELECTOR_SEGMENT);
}
final ResourceResolver resourceResolver = request.getResourceResolver();
final String resolvedLibraryPath = resolvePathIfProxied(libraryType, libraryPath, resourceResolver);
if (resolvedLibraryPath == null) {
return null;
}
return htmlLibraryManager.getLibrary(libraryType, resolvedLibraryPath);
}
private LibraryType getLibraryTypeFromExtension(final String extension) {
for (LibraryType libraryType : LibraryType.values()) {
if (StringUtils.equals(libraryType.extension, SELECTOR_EXTENSION_SEPARATOR + extension)) {
return libraryType;
}
}
return null;
}
private String resolvePathIfProxied(
final LibraryType libraryType,
final String libraryPath,
final ResourceResolver resourceResolver
) {
if (!libraryPath.startsWith(PROXY_PREFIX)) {
return libraryPath;
}
return resolveProxiedClientLibrary(libraryType, libraryPath, resourceResolver, true);
}
private String resolveProxiedClientLibrary(
final LibraryType libraryType,
final String proxiedPath,
final ResourceResolver resourceResolver,
final boolean refreshCacheIfNotFound
) {
final String relativePath = proxiedPath.substring(PROXY_PREFIX.length());
for (final String prefix : resourceResolver.getSearchPath()) {
final String absolutePath = prefix + relativePath;
// check whether the ClientLibrary exists before calling HtmlLibraryManager#getLibrary in order
// to avoid WARN log messages that are written when an unknown HtmlLibrary is requested
if (hasProxyClientLibrary(libraryType, absolutePath)) {
return absolutePath;
}
}
if (refreshCacheIfNotFound) {
// maybe the library has appeared and our copy of the cache is stale
log.info("Refreshing client libraries cache, because {} could not be found", proxiedPath);
clientLibrariesCache = null;
return resolveProxiedClientLibrary(libraryType, proxiedPath, resourceResolver, false);
}
return null;
}
private boolean hasProxyClientLibrary(final LibraryType type, final String path) {
final ClientLibrary clientLibrary = getClientLibrary(path);
return clientLibrary != null && clientLibrary.allowProxy() && clientLibrary.getTypes().contains(type);
}
private ClientLibrary getClientLibrary(String path) {
if (clientLibrariesCache == null) {
clientLibrariesCache = Collections.unmodifiableMap(htmlLibraryManager.getLibraries());
}
return clientLibrariesCache.get(path);
}
private interface StartElement {
void startElement(
String namespaceURI,
String localName,
String qName,
Attributes attrs
) throws SAXException;
}
private class ClientlibIncludeRewriterTransformer implements Transformer, StartElement {
public static final String STYLESHEET_LINK_ATTR = "href";
public static final String SCRIPT_LINK_ATTR = "src";
private SlingHttpServletRequest request;
@Delegate(types = ContentHandler.class, excludes = StartElement.class)
private ContentHandler contentHandler;
@Override
public void init(ProcessingContext context, ProcessingComponentConfiguration config) {
request = context.getRequest();
}
@Override
public void dispose() {
contentHandler = null;
}
@Override
public void startElement(
final String namespaceURI,
final String localName,
final String qName,
final Attributes attrs
) throws SAXException {
Attributes nextAttributes = attrs;
final String linkAttribute = getApplicableAttribute(localName, attrs);
if (linkAttribute != null) {
nextAttributes = rewrite(localName, linkAttribute, attrs, request);
}
contentHandler.startElement(namespaceURI, localName, qName, nextAttributes);
}
/**
* Determines if the element given is applicable to be transformed and
* returns the name of the attribute that contains the clientlib reference.
* <p>
* The following elements are deemed applicable
* <p>
* – Script elements with a src attribute (src is returned)
* – Link elements with a rel set to stylesheet and a href attribute (href is returned)
*
* @param localName the element name with namespaces removed
* @param attrs the attributes currently on the element
* @return the name of the attribute to check or null if not an applicable element
*/
private String getApplicableAttribute(
final String localName,
final Attributes attrs
) {
if (isValidScriptLink(localName, attrs)) {
return SCRIPT_LINK_ATTR;
}
if (isValidStylesheetLink(localName, attrs)) {
return STYLESHEET_LINK_ATTR;
}
return null;
}
private boolean isValidScriptLink(final String localName, final Attributes attrs) {
if (!StringUtils.equalsIgnoreCase(localName, "script")) {
return false;
}
// Element is a valid script include if it has a src attribute
return StringUtils.isNotBlank(attrs.getValue(StringUtils.EMPTY, SCRIPT_LINK_ATTR));
}
private boolean isValidStylesheetLink(final String localName, final Attributes attrs) {
if (!StringUtils.equalsIgnoreCase(localName, "link")) {
return false;
}
// According to specs, the rel attribute is a whitespace-separated list of rel tokens
final String[] rels = StringUtils.defaultIfBlank(
attrs.getValue(StringUtils.EMPTY, "rel"),
StringUtils.EMPTY
).split("\\s+");
if (Stream.of(rels).noneMatch("stylesheet"::equals)) {
// Not a link to a stylesheet
return false;
}
// Element is a valid stylesheet include if it has an href attribute
return StringUtils.isNotBlank(attrs.getValue(StringUtils.EMPTY, STYLESHEET_LINK_ATTR));
}
@Override
public void setContentHandler(ContentHandler handler) {
contentHandler = handler;
}
}
}
这需要一个类似于为 acs-commons 描述的重写器配置:https://adobe-consulting-services.github.io/acs-aem-commons/features/versioned-clientlibs/index.html#rewriter-configuration-node /apps/*/config/rewriter 下的任何位置:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:Folder"
contentTypes="[text/html]"
enabled="{Boolean}true"
generatorType="htmlparser"
order="{Long}1"
serializerType="htmlwriter"
transformerTypes="[linkchecker,clientlib-async-include-transformer]"/>
然后,您所要做的就是将 useAsync 或 useDefer 布尔属性添加到您的 cq:ClientLibraryFolder 节点。
【讨论】:
为此,您需要在具有 clientlib.html 和 granite.html ClientLibUseObject.java 的应用程序中创建新的 clientlib-asyn 文件夹,然后您可以使用新的 clientlib-async 调用组件 clientlib 例如:
<sly data-sly-use.clientLibAsync="/apps/clientlib-async/sightly/templates/clientlib.html" data-sly-call="${clientLibAsync.js @ categories='clientlib-async-sample.async-sample', loading='async', onload='sayHello()'}" data-sly-unwrap/>
https://github.com/nateyolles/aem-clientlib-async 关注这个真的很有帮助
【讨论】: