#frontend #aem
#интерфейс #aem
Вопрос:
Пожалуйста, дайте проверенное и проверенное решение для этого, с правильными шагами того, что нужно сделать. Заранее спасибо
Ответ №1:
Самым простым, вероятно, является просто написать тег включения 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 amp;amp; clientLibrary.allowProxy() amp;amp; 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
узлу.
Ответ №2:
Для этого вам необходимо создать новую папку clientlib-asyn внутри приложений, которые имеют clientlib.html и granite.html ClientLibUseObject.java затем вы можете вызвать свой компонент clientlib с новым clientlib-async, например:
<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
следуйте этому, это действительно полезно
Комментарии:
1. Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы только для ссылок могут стать недействительными, если связанная страница изменится.